diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml index a9a38e7d8..969d1cea2 100644 --- a/.github/workflows/ci.yml +++ b/.github/workflows/ci.yml @@ -25,7 +25,7 @@ jobs: strategy: matrix: os: [ "ubuntu-20.04", "ubuntu-22.04", "ubuntu-24.04" ] - python-version: ["3.9", "3.10", "3.11", "3.12"] + python-version: ["3.10", "3.11", "3.12"] steps: - uses: actions/checkout@v4 @@ -68,11 +68,17 @@ jobs: python build_helpers/freqtrade_client_version_align.py - name: Tests + if: (!(runner.os == 'Linux' && matrix.python-version == '3.12' && matrix.os == 'ubuntu-22.04')) + run: | + pytest --random-order + + - name: Tests with Coveralls + if: (runner.os == 'Linux' && matrix.python-version == '3.12' && matrix.os == 'ubuntu-22.04') run: | pytest --random-order --cov=freqtrade --cov=freqtrade_client --cov-config=.coveragerc - name: Coveralls - if: (runner.os == 'Linux' && matrix.python-version == '3.10' && matrix.os == 'ubuntu-22.04') + if: (runner.os == 'Linux' && matrix.python-version == '3.12' && matrix.os == 'ubuntu-22.04') env: # Coveralls token. Not used as secret due to github not providing secrets to forked repositories COVERALLS_REPO_TOKEN: 6D1m0xupS3FgutfuGao8keFf9Hc0FpIXu @@ -138,11 +144,8 @@ jobs: runs-on: ${{ matrix.os }} strategy: matrix: - os: [ "macos-12", "macos-13", "macos-14" ] - python-version: ["3.9", "3.10", "3.11", "3.12"] - exclude: - - os: "macos-14" - python-version: "3.9" + os: [ "macos-13", "macos-14", "macos-15" ] + python-version: ["3.10", "3.11", "3.12"] steps: - uses: actions/checkout@v4 @@ -263,7 +266,7 @@ jobs: strategy: matrix: os: [ windows-latest ] - python-version: ["3.9", "3.10", "3.11", "3.12"] + python-version: ["3.10", "3.11", "3.12"] steps: - uses: actions/checkout@v4 @@ -537,12 +540,12 @@ jobs: - name: Publish to PyPI (Test) - uses: pypa/gh-action-pypi-publish@v1.10.2 + uses: pypa/gh-action-pypi-publish@v1.10.3 with: repository-url: https://test.pypi.org/legacy/ - name: Publish to PyPI - uses: pypa/gh-action-pypi-publish@v1.10.2 + uses: pypa/gh-action-pypi-publish@v1.10.3 deploy-docker: diff --git a/.pre-commit-config.yaml b/.pre-commit-config.yaml index 57a5ad437..6048e6d45 100644 --- a/.pre-commit-config.yaml +++ b/.pre-commit-config.yaml @@ -9,17 +9,17 @@ repos: # stages: [push] - repo: https://github.com/pre-commit/mirrors-mypy - rev: "v1.11.2" + rev: "v1.13.0" hooks: - id: mypy exclude: build_helpers additional_dependencies: - types-cachetools==5.5.0.20240820 - types-filelock==3.2.7 - - types-requests==2.32.0.20240914 + - types-requests==2.32.0.20241016 - types-tabulate==0.9.0.20240106 - - types-python-dateutil==2.9.0.20240906 - - SQLAlchemy==2.0.35 + - types-python-dateutil==2.9.0.20241003 + - SQLAlchemy==2.0.36 # stages: [push] - repo: https://github.com/pycqa/isort @@ -31,13 +31,13 @@ repos: - repo: https://github.com/charliermarsh/ruff-pre-commit # Ruff version. - rev: 'v0.6.7' + rev: 'v0.7.1' hooks: - id: ruff - id: ruff-format - repo: https://github.com/pre-commit/pre-commit-hooks - rev: v4.6.0 + rev: v5.0.0 hooks: - id: end-of-file-fixer exclude: | diff --git a/Dockerfile b/Dockerfile index bc2fc4635..76249919b 100644 --- a/Dockerfile +++ b/Dockerfile @@ -1,4 +1,4 @@ -FROM python:3.12.6-slim-bookworm as base +FROM python:3.12.7-slim-bookworm as base # Setup env ENV LANG C.UTF-8 diff --git a/README.md b/README.md index 317a6cfdf..9f4fc51fb 100644 --- a/README.md +++ b/README.md @@ -61,7 +61,7 @@ Please find the complete documentation on the [freqtrade website](https://www.fr ## Features -- [x] **Based on Python 3.9+**: For botting on any operating system - Windows, macOS and Linux. +- [x] **Based on Python 3.10+**: For botting on any operating system - Windows, macOS and Linux. - [x] **Persistence**: Persistence is achieved through sqlite. - [x] **Dry-run**: Run the bot without paying money. - [x] **Backtesting**: Run a simulation of your buy/sell strategy. @@ -218,7 +218,7 @@ To run this bot we recommend you a cloud instance with a minimum of: ### Software requirements -- [Python >= 3.9](http://docs.python-guide.org/en/latest/starting/installation/) +- [Python >= 3.10](http://docs.python-guide.org/en/latest/starting/installation/) - [pip](https://pip.pypa.io/en/stable/installing/) - [git](https://git-scm.com/book/en/v2/Getting-Started-Installing-Git) - [TA-Lib](https://ta-lib.github.io/ta-lib-python/) diff --git a/build_helpers/TA_Lib-0.4.32-cp39-cp39-linux_armv7l.whl b/build_helpers/TA_Lib-0.4.32-cp39-cp39-linux_armv7l.whl deleted file mode 100644 index 03bc79df8..000000000 Binary files a/build_helpers/TA_Lib-0.4.32-cp39-cp39-linux_armv7l.whl and /dev/null differ diff --git a/build_helpers/TA_Lib-0.4.32-cp39-cp39-win_amd64.whl b/build_helpers/TA_Lib-0.4.32-cp39-cp39-win_amd64.whl deleted file mode 100644 index f0c46dafe..000000000 Binary files a/build_helpers/TA_Lib-0.4.32-cp39-cp39-win_amd64.whl and /dev/null differ diff --git a/build_helpers/schema.json b/build_helpers/schema.json index 8438dc3a0..6227829d9 100644 --- a/build_helpers/schema.json +++ b/build_helpers/schema.json @@ -579,57 +579,6 @@ ] } }, - "protections": { - "description": "Configuration for various protections.", - "type": "array", - "items": { - "type": "object", - "properties": { - "method": { - "description": "Method used for the protection.", - "type": "string", - "enum": [ - "CooldownPeriod", - "LowProfitPairs", - "MaxDrawdown", - "StoplossGuard" - ] - }, - "stop_duration": { - "description": "Duration to lock the pair after a protection is triggered, in minutes.", - "type": "number", - "minimum": 0.0 - }, - "stop_duration_candles": { - "description": "Duration to lock the pair after a protection is triggered, in number of candles.", - "type": "number", - "minimum": 0 - }, - "unlock_at": { - "description": "Time when trading will be unlocked regularly. Format: HH:MM", - "type": "string" - }, - "trade_limit": { - "description": "Minimum number of trades required during lookback period.", - "type": "number", - "minimum": 1 - }, - "lookback_period": { - "description": "Period to look back for protection checks, in minutes.", - "type": "number", - "minimum": 1 - }, - "lookback_period_candles": { - "description": "Period to look back for protection checks, in number of candles.", - "type": "number", - "minimum": 1 - } - }, - "required": [ - "method" - ] - } - }, "telegram": { "description": "Telegram settings.", "type": "object", @@ -1434,6 +1383,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/configuration.md b/docs/configuration.md index b05b1dcaa..074c9a577 100644 --- a/docs/configuration.md +++ b/docs/configuration.md @@ -229,7 +229,6 @@ Mandatory parameters are marked as **Required**, which means that they are requi | | **Plugins** | `edge.*` | Please refer to [edge configuration document](edge.md) for detailed explanation of all possible configuration options. | `pairlists` | Define one or more pairlists to be used. [More information](plugins.md#pairlists-and-pairlist-handlers).
*Defaults to `StaticPairList`.*
**Datatype:** List of Dicts -| `protections` | Define one or more protections to be used. [More information](plugins.md#protections).
**Datatype:** List of Dicts | | **Telegram** | `telegram.enabled` | Enable the usage of Telegram.
**Datatype:** Boolean | `telegram.token` | Your Telegram bot token. Only required if `telegram.enabled` is `true`.
**Keep it in secret, do not disclose publicly.**
**Datatype:** String diff --git a/docs/deprecated.md b/docs/deprecated.md index 6719ce56d..5357acc62 100644 --- a/docs/deprecated.md +++ b/docs/deprecated.md @@ -75,7 +75,10 @@ Webhook terminology changed from "sell" to "exit", and from "buy" to "entry", re * `webhooksellfill`, `webhookexitfill` -> `exit_fill` * `webhooksellcancel`, `webhookexitcancel` -> `exit_cancel` - ## Removal of `populate_any_indicators` version 2023.3 saw the removal of `populate_any_indicators` in favor of split methods for feature engineering and targets. Please read the [migration document](strategy_migration.md#freqai-strategy) for full details. + +## Removal of `protections` from configuration + + Setting protections from the configuration via `"protections": [],` has been removed in 2024.10, after having raised deprecation warnings for over 3 years. diff --git a/docs/developer.md b/docs/developer.md index 127e8e5d5..4e7d4f2f4 100644 --- a/docs/developer.md +++ b/docs/developer.md @@ -116,7 +116,7 @@ A similar setup can also be taken for Pycharm - using `freqtrade` as module name ![Pycharm debug configuration](assets/pycharm_debug.png) !!! Note "Startup directory" - This assumes that you have the repository checked out, and the editor is started at the repository root level (so setup.py is at the top level of your repository). + This assumes that you have the repository checked out, and the editor is started at the repository root level (so pyproject.toml is at the top level of your repository). ## ErrorHandling @@ -241,7 +241,6 @@ No protection should use datetime directly, but use the provided `date_now` vari !!! Tip "Writing a new Protection" Best copy one of the existing Protections to have a good example. - Don't forget to register your protection in `constants.py` under the variable `AVAILABLE_PROTECTIONS` - otherwise it will not be selectable. #### Implementation of a new protection 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 f88928344..6788f681a 100644 --- a/docs/hyperopt.md +++ b/docs/hyperopt.md @@ -445,7 +445,6 @@ While this strategy is most likely too simple to provide consistent profit, it s Whether you are using `.range` functionality or the alternatives above, you should try to use space ranges as small as possible since this will improve CPU/RAM usage. - ## Optimizing protections Freqtrade can also optimize protections. How you optimize protections is up to you, and the following should be considered as example only. @@ -589,14 +588,15 @@ Currently, the following loss functions are builtin: * `ShortTradeDurHyperOptLoss` - (default legacy Freqtrade hyperoptimization loss function) - Mostly for short trade duration and avoiding losses. * `OnlyProfitHyperOptLoss` - takes only amount of profit into consideration. -* `SharpeHyperOptLoss` - optimizes Sharpe Ratio calculated on trade returns relative to standard deviation. -* `SharpeHyperOptLossDaily` - optimizes Sharpe Ratio calculated on **daily** trade returns relative to standard deviation. -* `SortinoHyperOptLoss` - optimizes Sortino Ratio calculated on trade returns relative to **downside** standard deviation. +* `SharpeHyperOptLoss` - Optimizes Sharpe Ratio calculated on trade returns relative to standard deviation. +* `SharpeHyperOptLossDaily` - Optimizes Sharpe Ratio calculated on **daily** trade returns relative to standard deviation. +* `SortinoHyperOptLoss` - Optimizes Sortino Ratio calculated on trade returns relative to **downside** standard deviation. * `SortinoHyperOptLossDaily` - optimizes Sortino Ratio calculated on **daily** trade returns relative to **downside** standard deviation. * `MaxDrawDownHyperOptLoss` - Optimizes Maximum absolute drawdown. * `MaxDrawDownRelativeHyperOptLoss` - Optimizes both maximum absolute drawdown while also adjusting for maximum relative drawdown. * `CalmarHyperOptLoss` - Optimizes Calmar Ratio calculated on trade returns relative to max drawdown. * `ProfitDrawDownHyperOptLoss` - Optimizes by max Profit & min Drawdown objective. `DRAWDOWN_MULT` variable within the hyperoptloss file can be adjusted to be stricter or more flexible on drawdown purposes. +* `MultiMetricHyperOptLoss` - Optimizes by several key metrics to achieve balanced performance. The primary focus is on maximizing Profit and minimizing Drawdown, while also considering additional metrics such as Profit Factor, Expectancy Ratio and Winrate. Moreover, it applies a penalty for epochs with a low number of trades, encouraging strategies with adequate trade frequency. Creation of a custom loss function is covered in the [Advanced Hyperopt](advanced-hyperopt.md) part of the documentation. diff --git a/docs/includes/protections.md b/docs/includes/protections.md index a4cb9d3cc..c32846165 100644 --- a/docs/includes/protections.md +++ b/docs/includes/protections.md @@ -1,24 +1,16 @@ ## Protections -!!! Warning "Beta feature" - This feature is still in it's testing phase. Should you notice something you think is wrong please let us know via Discord or via Github Issue. - Protections will protect your strategy from unexpected events and market conditions by temporarily stop trading for either one pair, or for all pairs. All protection end times are rounded up to the next candle to avoid sudden, unexpected intra-candle buys. -!!! Note +!!! Tip "Usage tips" Not all Protections will work for all strategies, and parameters will need to be tuned for your strategy to improve performance. -!!! Tip Each Protection can be configured multiple times with different parameters, to allow different levels of protection (short-term / long-term). !!! Note "Backtesting" Protections are supported by backtesting and hyperopt, but must be explicitly enabled by using the `--enable-protections` flag. -!!! Warning "Setting protections from the configuration" - Setting protections from the configuration via `"protections": [],` key should be considered deprecated and will be removed in a future version. - It is also no longer guaranteed that your protections apply to the strategy in cases where the strategy defines [protections as property](hyperopt.md#optimizing-protections). - ### Available Protections * [`StoplossGuard`](#stoploss-guard) Stop trading if a certain amount of stoploss occurred within a certain time window. diff --git a/docs/index.md b/docs/index.md index f2d1482c9..d6dca4880 100644 --- a/docs/index.md +++ b/docs/index.md @@ -85,7 +85,7 @@ To run this bot we recommend you a linux cloud instance with a minimum of: Alternatively -- Python 3.9+ +- Python 3.10+ - pip (pip3) - git - TA-Lib diff --git a/docs/installation.md b/docs/installation.md index f86043fb3..02cbb7f3e 100644 --- a/docs/installation.md +++ b/docs/installation.md @@ -24,7 +24,7 @@ The easiest way to install and run Freqtrade is to clone the bot Github reposito The `stable` branch contains the code of the last release (done usually once per month on an approximately one week old snapshot of the `develop` branch to prevent packaging bugs, so potentially it's more stable). !!! Note - Python3.9 or higher and the corresponding `pip` are assumed to be available. The install-script will warn you and stop if that's not the case. `git` is also needed to clone the Freqtrade repository. + Python3.10 or higher and the corresponding `pip` are assumed to be available. The install-script will warn you and stop if that's not the case. `git` is also needed to clone the Freqtrade repository. Also, python headers (`python-dev` / `python-devel`) must be available for the installation to complete successfully. !!! Warning "Up-to-date clock" @@ -42,7 +42,7 @@ These requirements apply to both [Script Installation](#script-installation) and ### Install guide -* [Python >= 3.9](http://docs.python-guide.org/en/latest/starting/installation/) +* [Python >= 3.10](http://docs.python-guide.org/en/latest/starting/installation/) * [pip](https://pip.pypa.io/en/stable/installing/) * [git](https://git-scm.com/book/en/v2/Getting-Started-Installing-Git) * [virtualenv](https://virtualenv.pypa.io/en/stable/installation.html) (Recommended) @@ -54,7 +54,7 @@ We've included/collected install instructions for Ubuntu, MacOS, and Windows. Th OS Specific steps are listed first, the common section below is necessary for all systems. !!! Note - Python3.9 or higher and the corresponding pip are assumed to be available. + Python3.10 or higher and the corresponding pip are assumed to be available. === "Debian/Ubuntu" #### Install necessary dependencies @@ -69,7 +69,7 @@ OS Specific steps are listed first, the common section below is necessary for al === "RaspberryPi/Raspbian" The following assumes the latest [Raspbian Buster lite image](https://www.raspberrypi.org/downloads/raspbian/). - This image comes with python3.9 preinstalled, making it easy to get freqtrade up and running. + This image comes with python3.11 preinstalled, making it easy to get freqtrade up and running. Tested using a Raspberry Pi 3 with the Raspbian Buster lite image, all updates applied. @@ -169,7 +169,7 @@ You can as well update, configure and reset the codebase of your bot with `./scr ** --install ** With this option, the script will install the bot and most dependencies: -You will need to have git and python3.9+ installed beforehand for this to work. +You will need to have git and python3.10+ installed beforehand for this to work. * Mandatory software as: `ta-lib` * Setup your virtualenv under `.venv/` diff --git a/docs/requirements-docs.txt b/docs/requirements-docs.txt index 141490354..c757bf951 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.36 +mkdocs-material==9.5.42 mdx_truly_sane_lists==1.3 -pymdown-extensions==10.10.1 +pymdown-extensions==10.11.2 jinja2==3.1.4 mike==2.1.3 diff --git a/docs/strategy-101.md b/docs/strategy-101.md new file mode 100644 index 000000000..e7618d90f --- /dev/null +++ b/docs/strategy-101.md @@ -0,0 +1,196 @@ +# Freqtrade Strategies 101: A Quick Start for Strategy Development + +For the purposes of this quick start, we are assuming you are familiar with the basics of trading, and have read the +[Freqtrade basics](bot-basics.md) page. + +## Required Knowledge + +A strategy in Freqtrade is a Python class that defines the logic for buying and selling cryptocurrency `assets`. + +Assets are defined as `pairs`, which represent the `coin` and the `stake`. The coin is the asset you are trading using another currency as the stake. + +Data is supplied by the exchange in the form of `candles`, which are made up of a six values: `date`, `open`, `high`, `low`, `close` and `volume`. + +`Technical analysis` functions analyse the candle data using various computational and statistical formulae, and produce secondary values called `indicators`. + +Indicators are analysed on the asset pair candles to generate `signals`. + +Signals are turned into `orders` on a cryptocurrency `exchange`, i.e. `trades`. + +We use the terms `entry` and `exit` instead of `buying` and `selling` because Freqtrade supports both `long` and `short` trades. + +- **long**: You buy the coin based on a stake, e.g. buying the coin BTC using USDT as your stake, and you make a profit by selling the coin at a higher rate than you paid for. In long trades, profits are made by the coin value going up versus the stake. +- **short**: You borrow capital from the exchange in the form of the coin, and you pay back the stake value of the coin later. In short trades profits are made by the coin value going down versus the stake (you pay the loan off at a lower rate). + +Whilst Freqtrade supports spot and futures markets for certain exchanges, for simplicity we will focus on spot (long) trades only. + +## Structure of a Basic Strategy + +### Main dataframe + +Freqtrade strategies use a tabular data structure with rows and columns known as a `dataframe` to generate signals to enter and exit trades. + +Each pair in your configured pairlist has its own dataframe. Dataframes are indexed by the `date` column, e.g. `2024-06-31 12:00`. + +The next 5 columns represent the `open`, `high`, `low`, `close` and `volume` (OHLCV) data. + +### Populate indicator values + +The `populate_indicators` function adds columns to the dataframe that represent the technical analysis indicator values. + +Examples of common indicators include Relative Strength Index, Bollinger Bands, Money Flow Index, Moving Average, and Average True Range. + +Columns are added to the dataframe by calling technical analysis functions, e.g. ta-lib's RSI function `ta.RSI()`, and assigning them to a column name, e.g. `rsi` + +```python +dataframe['rsi'] = ta.RSI(dataframe) +``` + +??? Hint "Technical Analysis libraries" + Different libraries work in different ways to generate indicator values. Please check the documentation of each library to understand + how to integrate it into your strategy. You can also check the [Freqtrade example strategies](https://github.com/freqtrade/freqtrade-strategies) to give you ideas. + +### Populate entry signals + +The `populate_entry_trend` function defines conditions for an entry signal. + +The dataframe column `enter_long` is added to the dataframe, and when a value of `1` is in this column, Freqtrade sees an entry signal. + +??? Hint "Shorting" + To enter short trades, use the `enter_short` column. + +### Populate exit signals + +The `populate_exit_trend` function defines conditions for an exit signal. + +The dataframe column `exit_long` is added to the dataframe, and when a value of `1` is in this column, Freqtrade sees an exit signal. + +??? Hint "Shorting" + To exit short trades, use the `exit_short` column. + +## A simple strategy + +Here is a minimal example of a Freqtrade strategy: + +```python +from freqtrade.strategy import IStrategy +from pandas import DataFrame +import talib.abstract as ta + +class MyStrategy(IStrategy): + + # set the initial stoploss to -10% + stoploss = -0.10 + + # exit profitable positions at any time when the profit is greater than 1% + minimal_roi = {"0": 0.01} + + def populate_indicators(self, dataframe: DataFrame, metadata: dict) -> DataFrame: + # generate values for technical analysis indicators + dataframe['rsi'] = ta.RSI(dataframe, timeperiod=14) + + return dataframe + + def populate_entry_trend(self, dataframe: DataFrame, metadata: dict) -> DataFrame: + # generate entry signals based on indicator values + dataframe.loc[ + (dataframe['rsi'] < 30), + 'enter_long'] = 1 + + return dataframe + + def populate_exit_trend(self, dataframe: DataFrame, metadata: dict) -> DataFrame: + # generate exit signals based on indicator values + dataframe.loc[ + (dataframe['rsi'] > 70), + 'exit_long'] = 1 + + return dataframe +``` + +## Making trades + +When a signal is found (a `1` in an entry or exit column), Freqtrade will attempt to make an order, i.e. a `trade` or `position`. + +Each new trade position takes up a `slot`. Slots represent the maximum number of concurrent new trades that can be opened. + +The number of slots is defined by the `max_open_trades` [configuration](configuration.md) option. + +However, there can be a range of scenarios where generating a signal does not always create a trade order. These include: + +- not enough remaining stake to buy an asset, or funds in your wallet to sell an asset (including any fees) +- not enough remaining free slots for a new trade to be opened (the number of positions you have open equals the `max_open_trades` option) +- there is already an open trade for a pair (Freqtrade cannot stack positions - however it can [adjust existing positions](strategy-callbacks.md#adjust-trade-position)) +- if an entry and exit signal is present on the same candle, they are considered as [colliding](strategy-customization.md#colliding-signals), and no order will be raised +- the strategy actively rejects the trade order due to logic you specify by using one of the relevant [entry](strategy-callbacks.md#trade-entry-buy-order-confirmation) or [exit](strategy-callbacks.md#trade-exit-sell-order-confirmation) callbacks + +Read through the [strategy customization](strategy-customization.md) documentation for more details. + +## Backtesting and forward testing + +Strategy development can be a long and frustrating process, as turning our human "gut instincts" into a working computer-controlled +("algo") strategy is not always straightforward. + +Therefore a strategy should be tested to verify that it is going to work as intended. + +Freqtrade has two testing modes: + +- **backtesting**: using historical data that you [download from an exchange](data-download.md), backtesting is a quick way to assess performance of a strategy. However, it can be very easy to distort results so a strategy will look a lot more profitable than it really is. Check the [backtesting documentation](backtesting.md) for more information. +- **dry run**: often referred to as _forward testing_, dry runs use real time data from the exchange. However, any signals that would result in trades are tracked as normal by Freqtrade, but do not have any trades opened on the exchange itself. Forward testing runs in real time, so whilst it takes longer to get results it is a much more reliable indicator of **potential** performance than backtesting. + +Dry runs are enabled by setting `dry_run` to true in your [configuration](configuration.md#using-dry-run-mode). + +!!! Warning "Backtests can be very inaccurate" + There are many reasons why backtest results may not match reality. Please check the [backtesting assumptions](backtesting.md#assumptions-made-by-backtesting) and [common strategy mistakes](strategy-customization.md#common-mistakes-when-developing-strategies) documentation. + Some websites that list and rank Freqtrade strategies show impressive backtest results. Do not assume these results are achieveable or realistic. + +??? Hint "Useful commands" + Freqtrade includes two useful commands to check for basic flaws in strategies: [lookahead-analysis](lookahead-analysis.md) and [recursive-analysis](recursive-analysis.md). + +### Assessing backtesting and dry run results + +Always dry run your strategy after backtesting it to see if backtesting and dry run results are sufficiently similar. + +If there is any significant difference, verify that your entry and exit signals are consistent and appear on the same candles between the two modes. However, there will always be differences between dry runs and backtests: + +- Backtesting assumes all orders fill. In dry runs this might not be the case if using limit orders or there is no volume on the exchange. +- Following an entry signal on candle close, backtesting assumes trades enter at the next candle's open price (unless you have custom pricing callbacks in your strategy). In dry runs, there is often a delay between signals and trades opening. + This is because when new candles come in on your main timeframe, e.g. every 5 minutes, it takes time for Freqtrade to analyse all pair dataframes. Therefore, Freqtrade will attempt to open trades a few seconds (ideally a small a delay as possible) + after candle open. +- As entry rates in dry runs might not match backtesting, this means profit calculations will also differ. Therefore, it is normal if ROI, stoploss, trailing stoploss and callback exits are not identical. +- The more computational "lag" you have between new candles coming in and your signals being raised and trades being opened will result in greater price unpredictability. Make sure your computer is powerful enough to process the data for the number + of pairs you have in your pairlist within a reasonable time. Freqtrade will warn you in the logs if there are significant data processing delays. + +## Controlling or monitoring a running bot + +Once your bot is running in dry or live mode, Freqtrade has five mechanisms to control or monitor a running bot: + +- **[FreqUI](freq-ui.md)**: The easiest to get started with, FreqUI is a web interface to see and control current activity of your bot. +- **[Telegram](telegram-usage.md)**: On mobile devices, Telegram integration is available to get alerts about your bot activity and to control certain aspects. +- **[FTUI](https://github.com/freqtrade/ftui)**: FTUI is a terminal (command line) interface to Freqtrade, and allows monitoring of a running bot only. +- **[REST API](rest-api.md)**: The REST API allows programmers to develop their own tools to interact with a Freqtrade bot. +- **[Webhooks](webhook-config.md)**: Freqtrade can send information to other services, e.g. discord, by webhooks. + +### Logs + +Freqtrade generates extensive debugging logs to help you understand what's happening. Please familiarise yourself with the information and error messages you might see in your bot logs. + +## Final Thoughts + +Algo trading is difficult, and most public strategies are not good performers due to the time and effort to make a strategy work profitably in multiple scenarios. + +Therefore, taking public strategies and using backtests as a way to assess performance is often problematic. However, Freqtrade provides useful ways to help you make decisions and do your due diligence. + +There are many different ways to achieve profitability, and there is no one single tip, trick or config option that will fix a poorly performing strategy. + +Freqtrade is an open source platform with a large and helpful community - make sure to visit our [discord channel](https://discord.gg/p7nuUNVfP7) to discuss your strategy with others! + +As always, only invest what you are willing to lose. + +## Conclusion + +Developing a strategy in Freqtrade involves defining entry and exit signals based on technical indicators. By following the structure and methods outlined above, you can create and test your own trading strategies. + +Common questions and answers are available on our [FAQ](faq.md). + +To continue, refer to the more in-depth [Freqtrade strategy customization documentation](strategy-customization.md). diff --git a/docs/strategy-callbacks.md b/docs/strategy-callbacks.md index ce1b9907c..6ba225007 100644 --- a/docs/strategy-callbacks.md +++ b/docs/strategy-callbacks.md @@ -975,7 +975,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 c7a66b03b..5e3c65759 100644 --- a/docs/strategy-customization.md +++ b/docs/strategy-customization.md @@ -2,52 +2,93 @@ This page explains how to customize your strategies, add new indicators and set up trading rules. -Please familiarize yourself with [Freqtrade basics](bot-basics.md) first, which provides overall info on how the bot operates. +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 bot basics](bot-basics.md), which provides overall info on how the bot operates ## Develop your own strategy The bot includes a default strategy file. + Also, several other strategies are available in the [strategy repository](https://github.com/freqtrade/freqtrade-strategies). You will however most likely have your own idea for a strategy. -This document intends to help you convert your strategy idea into your own strategy. -To get started, use `freqtrade new-strategy --strategy AwesomeStrategy` (you can obviously use your own naming for your strategy). -This will create a new strategy file from a template, which will be located under `user_data/strategies/AwesomeStrategy.py`. +This document intends to help you convert your ideas into a working strategy. + +### Generating a strategy template + +To get started, you can use the command: + +```bash +freqtrade new-strategy --strategy AwesomeStrategy +``` + +This will create a new strategy called `AwesomeStrategy` from a template, which will be located using the filename `user_data/strategies/AwesomeStrategy.py`. !!! Note - This is just a template file, which will most likely not be profitable out of the box. + There is a difference between the *name* of the strategy and the filename. In most commands, Freqtrade uses the *name* of the strategy, *not the filename*. + +!!! Note + The `new-strategy` command generates starting examples which will not be profitable out of the box. ??? Hint "Different template levels" - `freqtrade new-strategy` has an additional parameter, `--template`, which controls the amount of pre-build information you get in the created strategy. Use `--template minimal` to get an empty strategy without any indicator examples, or `--template advanced` to get a template with most callbacks defined. + `freqtrade new-strategy` has an additional parameter, `--template`, which controls the amount of pre-build information you get in the created strategy. Use `--template minimal` to get an empty strategy without any indicator examples, or `--template advanced` to get a template with more complicated features defined. ### Anatomy of a strategy -A strategy file contains all the information needed to build a good strategy: +A strategy file contains all the information needed to build the strategy logic: +- Candle data in OHLCV format - Indicators -- Entry strategy rules -- Exit strategy rules -- Minimal ROI recommended -- Stoploss strongly recommended +- Entry logic + - Signals +- Exit logic + - Signals + - Minimal ROI + - Callbacks ("custom functions") +- Stoploss + - Fixed/absolute + - Trailing + - Callbacks ("custom functions") +- Pricing [optional] +- Position adjustment [optional] -The bot also include a sample strategy called `SampleStrategy` you can update: `user_data/strategies/sample_strategy.py`. -You can test it with the parameter: `--strategy SampleStrategy` +The bot includes a sample strategy called `SampleStrategy` that you can use as a basis: `user_data/strategies/sample_strategy.py`. +You can test it with the parameter: `--strategy SampleStrategy`. Remember that you use the strategy class name, not the filename. Additionally, there is an attribute called `INTERFACE_VERSION`, which defines the version of the strategy interface the bot should use. The current version is 3 - which is also the default when it's not set explicitly in the strategy. -Future versions will require this to be set. +You may see older strategies set to interface version 2, and these will need to be updated to v3 terminology as future versions will require this to be set. + +Starting the bot in dry or live mode is accomplished using the `trade` command: ```bash freqtrade trade --strategy AwesomeStrategy ``` +### Bot modes + +Freqtrade strategies can be processed by the Freqtrade bot in 5 main modes: + +- backtesting +- hyperopting +- dry ("forward testing") +- live +- FreqAI (not covered here) + +Check the [configuration documentation](configuration.md) about how to set the bot to dry or live mode. + +**Always use dry mode when testing as this gives you an idea of how your strategy will work in reality without risking capital.** + +## Diving in deeper **For the following section we will use the [user_data/strategies/sample_strategy.py](https://github.com/freqtrade/freqtrade/blob/develop/freqtrade/templates/sample_strategy.py) file as reference.** !!! Note "Strategies and Backtesting" - To avoid problems and unexpected differences between Backtesting and dry/live modes, please be aware + To avoid problems and unexpected differences between backtesting and dry/live modes, please be aware that during backtesting the full time range is passed to the `populate_*()` methods at once. It is therefore best to use vectorized operations (across the whole dataframe, not loops) and avoid index referencing (`df.iloc[-1]`), but instead use `df.shift()` to get to the previous candle. @@ -57,14 +98,22 @@ file as reference.** needs to take care to avoid having the strategy utilize data from the future. Some common patterns for this are listed in the [Common Mistakes](#common-mistakes-when-developing-strategies) section of this document. +??? Hint "Lookahead and recursive analysis" + Freqtrade includes two helpful commands to help assess common lookahead (using future data) and + recursive bias (variance in indicator values) issues. Before running a strategy in dry or live more, + you should always use these commands first. Please check the relevant documentation for + [lookahead](lookahead-analysis.md) and [recursive](recursive-analysis.md) analysis. + ### Dataframe Freqtrade uses [pandas](https://pandas.pydata.org/) to store/provide the candlestick (OHLCV) data. -Pandas is a great library developed for processing large amounts of data. +Pandas is a great library developed for processing large amounts of data in tabular format. -Each row in a dataframe corresponds to one candle on a chart, with the latest candle always being the last in the dataframe (sorted by date). +Each row in a dataframe corresponds to one candle on a chart, with the latest complete candle always being the last in the dataframe (sorted by date). -``` output +If we were to look at the first few rows of the main dataframe using the pandas `head()` function, we would see: + +```output > dataframe.head() date open high low close volume 0 2021-11-09 23:25:00+00:00 67279.67 67321.84 67255.01 67300.97 44.62253 @@ -74,20 +123,16 @@ Each row in a dataframe corresponds to one candle on a chart, with the latest ca 4 2021-11-09 23:45:00+00:00 67160.48 67160.48 66901.26 66943.37 111.39292 ``` -Pandas provides fast ways to calculate metrics. To benefit from this speed, it's advised to not use loops, but use vectorized methods instead. - -Vectorized operations perform calculations across the whole range of data and are therefore, compared to looping through each row, a lot faster when calculating indicators. - -As a dataframe is a table, simple python comparisons like the following will not work +A dataframe is a table where columns are not single values, but a series of data values. As such, simple python comparisons like the following will not work: ``` python if dataframe['rsi'] > 30: dataframe['enter_long'] = 1 ``` -The above section will fail with `The truth value of a Series is ambiguous. [...]`. +The above section will fail with `The truth value of a Series is ambiguous [...]`. -This must instead be written in a pandas-compatible way, so the operation is performed across the whole dataframe. +This must instead be written in a pandas-compatible way, so the operation is performed across the whole dataframe, i.e. `vectorisation`. ``` python dataframe.loc[ @@ -97,13 +142,38 @@ This must instead be written in a pandas-compatible way, so the operation is per With this section, you have a new column in your dataframe, which has `1` assigned whenever RSI is above 30. +Freqtrade uses this new column as an entry signal, where it is assumed that a trade will subsequently open on the next open candle. + +Pandas provides fast ways to calculate metrics, i.e. "vectorisation". To benefit from this speed, it is advised to not use loops, but use vectorized methods instead. + +Vectorized operations perform calculations across the whole range of data and are therefore, compared to looping through each row, a lot faster when calculating indicators. + +??? Hint "Signals vs Trades" + - Signals are generated from indicators at candle close, and are intentions to enter a trade. + - Trades are orders that are executed (on the exchange in live mode) where a trade will then open as close to next candle open as possible. + +!!! Warning "Trade order assumptions" + In backtesting, signals are generated on candle close. Trades are then initiated immeditely on next candle open. + + In dry and live, this may be delayed due to all pair dataframes needing to be analysed first, then trade processing + for each of those pairs happens. This means that in dry/live you need to be mindful of having as low a computation + delay as possible, usually by running a low number of pairs and having a CPU with a good clock speed. + +#### Why can't I see "real time" candle data? + +Freqtrade does not store incomplete/unfinished candles in the dataframe. + +The use of incomplete data for making strategy decisions is called "repainting" and you might see other platforms allow this. + +Freqtrade does not. Only complete/finished candle data is available in the dataframe. + ### Customize Indicators -Buy and sell signals need indicators. You can add more indicators by extending the list contained in the method `populate_indicators()` from your strategy file. +Entry and exit signals need indicators. You can add more indicators by extending the list contained in the method `populate_indicators()` from your strategy file. You should only add the indicators used in either `populate_entry_trend()`, `populate_exit_trend()`, or to populate another indicator, otherwise performance may suffer. -It's important to always return the dataframe without removing/modifying the columns `"open", "high", "low", "close", "volume"`, otherwise these fields would contain something unexpected. +It's important to always return the dataframe from these three functions without removing/modifying the columns `"open", "high", "low", "close", "volume"`, otherwise these fields would contain something unexpected. Sample: @@ -124,7 +194,7 @@ def populate_indicators(self, dataframe: DataFrame, metadata: dict) -> DataFrame stoch = ta.STOCHF(dataframe) dataframe['fastd'] = stoch['fastd'] dataframe['fastk'] = stoch['fastk'] - dataframe['blower'] = ta.BBANDS(dataframe, nbdevup=2, nbdevdn=2)['lowerband'] + dataframe['bb_lower'] = ta.BBANDS(dataframe, nbdevup=2, nbdevdn=2)['lowerband'] dataframe['sma'] = ta.SMA(dataframe, timeperiod=40) dataframe['tema'] = ta.TEMA(dataframe, timeperiod=9) dataframe['mfi'] = ta.MFI(dataframe) @@ -145,6 +215,8 @@ def populate_indicators(self, dataframe: DataFrame, metadata: dict) -> DataFrame dataframe['plus_di'] = ta.PLUS_DI(dataframe) dataframe['minus_dm'] = ta.MINUS_DM(dataframe) dataframe['minus_di'] = ta.MINUS_DI(dataframe) + + # remember to always return the dataframe return dataframe ``` @@ -164,11 +236,13 @@ Additional technical libraries can be installed as necessary, or custom indicato ### Strategy startup period -Most indicators have an instable startup period, in which they are either not available (NaN), or the calculation is incorrect. This can lead to inconsistencies, since Freqtrade does not know how long this instable period should be. +Some indicators have an unstable startup period in which there isn't enough candle data to calculate any values (NaN), or the calculation is incorrect. This can lead to inconsistencies, since Freqtrade does not know how long this unstable period is and uses whatever indicator values are in the dataframe. + To account for this, the strategy can be assigned the `startup_candle_count` attribute. + This should be set to the maximum number of candles that the strategy requires to calculate stable indicators. In the case where a user includes higher timeframes with informative pairs, the `startup_candle_count` does not necessarily change. The value is the maximum period (in candles) that any of the informatives timeframes need to compute stable indicators. -You can use [recursive-analysis](recursive-analysis.md) to check and find the correct `startup_candle_count` to be used. +You can use [recursive-analysis](recursive-analysis.md) to check and find the correct `startup_candle_count` to be used. When recursive analysis shows a variance of 0%, then you can be sure that you have enough startup candle data. In this example strategy, this should be set to 400 (`startup_candle_count = 400`), since the minimum needed history for ema100 calculation to make sure the value is correct is 400 candles. @@ -195,19 +269,22 @@ Let's try to backtest 1 month (January 2019) of 5m candles using an example stra freqtrade backtesting --timerange 20190101-20190201 --timeframe 5m ``` -Assuming `startup_candle_count` is set to 400, backtesting knows it needs 400 candles to generate valid buy signals. It will load data from `20190101 - (400 * 5m)` - which is ~2018-12-30 11:40:00. -If this data is available, indicators will be calculated with this extended timerange. The instable startup period (up to 2019-01-01 00:00:00) will then be removed before starting backtesting. +Assuming `startup_candle_count` is set to 400, backtesting knows it needs 400 candles to generate valid entry signals. It will load data from `20190101 - (400 * 5m)` - which is ~2018-12-30 11:40:00. -!!! Note - If data for the startup period is not available, then the timerange will be adjusted to account for this startup period - so Backtesting would start at 2019-01-02 09:20:00. +If this data is available, indicators will be calculated with this extended timerange. The unstable startup period (up to 2019-01-01 00:00:00) will then be removed before backtesting is carried out. + +!!! Note "Unavailable startup candle data" + If data for the startup period is not available, then the timerange will be adjusted to account for this startup period. In our example, backtesting would then start from 2019-01-02 09:20:00. ### Entry signal rules Edit the method `populate_entry_trend()` in your strategy file to update your entry strategy. -It's important to always return the dataframe without removing/modifying the columns `"open", "high", "low", "close", "volume"`, otherwise these fields would contain something unexpected. +It's important to always return the dataframe without removing/modifying the columns `"open", "high", "low", "close", "volume"`, otherwise these fields would contain something unexpected. The strategy may then produce invalid values, or cease to work entirely. -This method will also define a new column, `"enter_long"` (`"enter_short"` for shorts), which needs to contain 1 for entries, and 0 for "no action". `enter_long` is a mandatory column that must be set even if the strategy is shorting only. +This method will also define a new column, `"enter_long"` (`"enter_short"` for shorts), which needs to contain `1` for entries, and `0` for "no action". `enter_long` is a mandatory column that must be set even if the strategy is shorting only. + +You can name your entry signals by using the `"enter_tag"` column, which can help debug and assess your strategy later. Sample from `user_data/strategies/sample_strategy.py`: @@ -232,12 +309,15 @@ def populate_entry_trend(self, dataframe: DataFrame, metadata: dict) -> DataFram ``` ??? Note "Enter short trades" - Short-entries can be created by setting `enter_short` (corresponds to `enter_long` for long trades). + Short entries can be created by setting `enter_short` (corresponds to `enter_long` for long trades). The `enter_tag` column remains identical. - Short-trades need to be supported by your exchange and market configuration! - Please make sure to set [`can_short`]() appropriately on your strategy if you intend to short. + Shorting needs to be supported by your exchange and market configuration! + Also, make sure you set [`can_short`](#can-short) appropriately on your strategy if you intend to short. ```python + # allow both long and short trades + can_short = True + def populate_entry_trend(self, dataframe: DataFrame, metadata: dict) -> DataFrame: dataframe.loc[ ( @@ -261,17 +341,21 @@ def populate_entry_trend(self, dataframe: DataFrame, metadata: dict) -> DataFram ``` !!! Note - Buying requires sellers to buy from - therefore volume needs to be > 0 (`dataframe['volume'] > 0`) to make sure that the bot does not buy/sell in no-activity periods. + Buying requires sellers to buy from. Therefore volume needs to be > 0 (`dataframe['volume'] > 0`) to make sure that the bot does not buy/sell in no-activity periods. ### Exit signal rules Edit the method `populate_exit_trend()` into your strategy file to update your exit strategy. + The exit-signal can be suppressed by setting `use_exit_signal` to false in the configuration or strategy. + `use_exit_signal` will not influence [signal collision rules](#colliding-signals) - which will still apply and can prevent entries. -It's important to always return the dataframe without removing/modifying the columns `"open", "high", "low", "close", "volume"`, otherwise these fields would contain something unexpected. +It's important to always return the dataframe without removing/modifying the columns `"open", "high", "low", "close", "volume"`, otherwise these fields would contain something unexpected. The strategy may then produce invalid values, or cease to work entirely. -This method will also define a new column, `"exit_long"` (`"exit_short"` for shorts), which needs to contain 1 for exits, and 0 for "no action". +This method will also define a new column, `"exit_long"` (`"exit_short"` for shorts), which needs to contain `1` for exits, and `0` for "no action". + +You can name your exit signals by using the `"exit_tag"` column, which can help debug and assess your strategy later. Sample from `user_data/strategies/sample_strategy.py`: @@ -295,11 +379,15 @@ def populate_exit_trend(self, dataframe: DataFrame, metadata: dict) -> DataFrame ``` ??? Note "Exit short trades" - Short-exits can be created by setting `exit_short` (corresponds to `exit_long`). + Short exits can be created by setting `exit_short` (corresponds to `exit_long`). The `exit_tag` column remains identical. - Short-trades need to be supported by your exchange and market configuration! + Shorting needs to be supported by your exchange and market configuration! + Also, make sure you set [`can_short`](#can-short) appropriately on your strategy if you intend to short. ```python + # allow both long and short trades + can_short = True + def populate_exit_trend(self, dataframe: DataFrame, metadata: dict) -> DataFrame: dataframe.loc[ ( @@ -322,9 +410,9 @@ def populate_exit_trend(self, dataframe: DataFrame, metadata: dict) -> DataFrame ### Minimal ROI -This dict defines the minimal Return On Investment (ROI) a trade should reach before exiting, independent from the exit signal. +The `minimal_roi` strategy variable defines the minimal Return On Investment (ROI) a trade should reach before exiting, independent from the exit signal. -It is of the following format, with the dict key (left side of the colon) being the minutes passed since the trade opened, and the value (right side of the colon) being the percentage. +It is of the following format, i.e. a python `dict`, with the dict key (left side of the colon) being the minutes passed since the trade opened, and the value (right side of the colon) being the percentage. ```python minimal_roi = { @@ -344,14 +432,19 @@ The above configuration would therefore mean: The calculation does include fees. +#### Disabling minimal ROI + To disable ROI completely, set it to an empty dictionary: ```python minimal_roi = {} ``` +#### Using calculations in minimal ROI + To use times based on candle duration (timeframe), the following snippet can be handy. -This will allow you to change the timeframe for the strategy, and ROI times will still be set as candles (e.g. after 3 candles ...) + +This will allow you to change the timeframe for the strategy, but the minimal ROI times will still be set as candles, e.g. after 3 candles. ``` python from freqtrade.exchange import timeframe_to_minutes @@ -368,9 +461,9 @@ class AwesomeStrategy(IStrategy): ``` ??? info "Orders that don't fill immediately" - `minimal_roi` will take the `trade.open_date` as reference, which is the time the trade was initialized / the first order for this trade was placed. - This will also hold true for limit orders that don't fill immediately (usually in combination with "off-spot" prices through `custom_entry_price()`), as well as for cases where the initial order is replaced through `adjust_entry_price()`. - The time used will still be from the initial `trade.open_date` (when the initial order was first placed), not from the newly placed order date. + `minimal_roi` will take the `trade.open_date` as reference, which is the time the trade was initialized, i.e. when the first order for this trade was placed. + This will also hold true for limit orders that don't fill immediately (usually in combination with "off-spot" prices through `custom_entry_price()`), as well as for cases where the initial order price is replaced through `adjust_entry_price()`. + The time used will still be from the initial `trade.open_date` (when the initial order was first placed), not from the newly placed or adjusted order date. ### Stoploss @@ -386,35 +479,44 @@ For the full documentation on stoploss features, look at the dedicated [stoploss ### Timeframe -This is the set of candles the bot should download and use for the analysis. +This is the periodicity of candles the bot should use in the strategy. + Common values are `"1m"`, `"5m"`, `"15m"`, `"1h"`, however all values supported by your exchange should work. -Please note that the same entry/exit signals may work well with one timeframe, but not with the others. +Please note that the same entry/exit signals may work well with one timeframe, but not with others. This setting is accessible within the strategy methods as the `self.timeframe` attribute. ### Can short -To use short signals in futures markets, you will have to let us know to do so by setting `can_short=True`. +To use short signals in futures markets, you will have to set `can_short = True`. + Strategies which enable this will fail to load on spot markets. -Disabling of this will have short signals ignored (also in futures markets). + +If you have `1` values in the `enter_short` column to raise short signals, setting `can_short = False` (which is the default) will mean that these short signals are ignored, even if you have specified futures markets in your configuration. ### Metadata dict -The metadata-dict (available for `populate_entry_trend`, `populate_exit_trend`, `populate_indicators`) contains additional information. -Currently this is `pair`, which can be accessed using `metadata['pair']` - and will return a pair in the format `XRP/BTC`. +The `metadata` dict (available for `populate_entry_trend`, `populate_exit_trend`, `populate_indicators`) contains additional information. +Currently this is `pair`, which can be accessed using `metadata['pair']`, and will return a pair in the format `XRP/BTC` (or `XRP/BTC:BTC` for futures markets). -The Metadata-dict should not be modified and does not persist information across multiple calls. -Instead, have a look at the [Storing information](strategy-advanced.md#storing-information-persistent) section. +The metadata dict should not be modified and does not persist information across multiple functions in your strategy. + +Instead, please check the [Storing information](strategy-advanced.md#storing-information-persistent) section. --8<-- "includes/strategy-imports.md" ## Strategy file loading -By default, freqtrade will attempt to load strategies from all `.py` files within `user_data/strategies`. +By default, freqtrade will attempt to load strategies from all `.py` files within the `userdir` (default `user_data/strategies`). -Assuming your strategy is called `AwesomeStrategy`, stored in the file `user_data/strategies/AwesomeStrategy.py`, then you can start freqtrade with `freqtrade trade --strategy AwesomeStrategy`. -Note that we're using the class-name, not the file name. +Assuming your strategy is called `AwesomeStrategy`, stored in the file `user_data/strategies/AwesomeStrategy.py`, then you can start freqtrade in dry (or live, depending on your configuration) mode with: + +```bash + freqtrade trade --strategy AwesomeStrategy` +``` + +Note that we're using the class name, not the file name. You can use `freqtrade list-strategies` to see a list of all strategies Freqtrade is able to load (all strategies in the correct folder). It will also include a "status" field, highlighting potential problems. @@ -426,9 +528,11 @@ It will also include a "status" field, highlighting potential problems. ### Get data for non-tradeable pairs -Data for additional, informative pairs (reference pairs) can be beneficial for some strategies. +Data for additional, informative pairs (reference pairs) can be beneficial for some strategies to see data on a wider timeframe. + OHLCV data for these pairs will be downloaded as part of the regular whitelist refresh process and is available via `DataProvider` just as other pairs (see below). -These parts will **not** be traded unless they are also specified in the pair whitelist, or have been selected by Dynamic Whitelisting. + +These pairs will **not** be traded unless they are also specified in the pair whitelist, or have been selected by Dynamic Whitelisting, e.g. `VolumePairlist`. The pairs need to be specified as tuples in the format `("pair", "timeframe")`, with pair as the first and timeframe as the second argument. @@ -468,10 +572,13 @@ A full sample can be found [in the DataProvider section](#complete-data-provider ### Informative pairs decorator (`@informative()`) -In most common case it is possible to easily define informative pairs by using a decorator. All decorated `populate_indicators_*` methods run in isolation, -not having access to data from other informative pairs, in the end all informative dataframes are merged and passed to main `populate_indicators()` method. -When hyperopting, use of hyperoptable parameter `.value` attribute is not supported. Please use `.range` attribute. See [optimizing an indicator parameter](hyperopt.md#optimizing-an-indicator-parameter) -for more information. +To easily define informative pairs, use the `@informative` decorator. All decorated `populate_indicators_*` methods run in isolation, +and do not have access to data from other informative pairs. However, all informative dataframes for each pair are merged and passed to main `populate_indicators()` method. + +!!! Note + Do not use the `@informative` decorator if you need to use data from one informative pair when generating another informative pair. Instead, define informative pairs manually as described [in the DataProvider section](#complete-data-provider-sample). + +When hyperopting, use of the hyperoptable parameter `.value` attribute is not supported. Please use the `.range` attribute. See [optimizing an indicator parameter](hyperopt.md#optimizing-an-indicator-parameter) for more information. ??? info "Full documentation" ``` python @@ -568,10 +675,6 @@ for more information. ``` -!!! Note - Do not use `@informative` decorator if you need to use data of one informative pair when generating another informative pair. Instead, define informative pairs - manually as described [in the DataProvider section](#complete-data-provider-sample). - !!! Note Use string formatting when accessing informative dataframes of other pairs. This will allow easily changing stake currency in config without having to adjust strategy code. @@ -592,18 +695,15 @@ for more information. Alternatively column renaming may be used to remove stake currency from column names: `@informative('1h', 'BTC/{stake}', fmt='{base}_{column}_{timeframe}')`. !!! Warning "Duplicate method names" - Methods tagged with `@informative()` decorator must always have unique names! Re-using same name (for example when copy-pasting already defined informative method) - will overwrite previously defined method and not produce any errors due to limitations of Python programming language. In such cases you will find that indicators - created in earlier-defined methods are not available in the dataframe. Carefully review method names and make sure they are unique! + Methods tagged with the `@informative()` decorator must always have unique names! Reusing the same name (for example when copy-pasting already defined informative methods) will overwrite previously defined methods and not produce any errors due to limitations of Python programming language. In such cases you will find that indicators created in methods higher up in the strategy file are not available in the dataframe. Carefully review method names and make sure they are unique! ### *merge_informative_pair()* -This method helps you merge an informative pair to a regular dataframe without lookahead bias. -It's there to help you merge the dataframe in a safe and consistent way. +This method helps you merge an informative pair to the regular main dataframe safely and consistently, without lookahead bias. Options: -- Rename the columns for you to create unique columns +- Rename the columns to create unique columns - Merge the dataframe without lookahead bias - Forward-fill (optional) @@ -654,20 +754,20 @@ All columns of the informative dataframe will be available on the returning data ``` !!! Warning "Informative timeframe < timeframe" - Using informative timeframes smaller than the dataframe timeframe is not recommended with this method, as it will not use any of the additional information this would provide. - To use the more detailed information properly, more advanced methods should be applied (which are out of scope for freqtrade documentation, as it'll depend on the respective need). + Using informative timeframes smaller than the main dataframe timeframe is not recommended with this method, as it will not use any of the additional information this would provide. + To use the more detailed information properly, more advanced methods should be applied (which are out of scope for this documentation). ## Additional data (DataProvider) The strategy provides access to the `DataProvider`. This allows you to get additional data to use in your strategy. -All methods return `None` in case of failure (do not raise an exception). +All methods return `None` in case of failure, i.e. failures do not raise an exception. -Please always check the mode of operation to select the correct method to get data (samples see below). +Please always check the mode of operation to select the correct method to get data (see below for examples). -!!! Warning "Hyperopt" - Dataprovider is available during hyperopt, however it can only be used in `populate_indicators()` within a strategy. - It is not available in `populate_buy()` and `populate_sell()` methods, nor in `populate_indicators()`, if this method located in the hyperopt file. +!!! Warning "Hyperopt Limitations" + The DataProvider is available during hyperopt, however it can only be used in `populate_indicators()` **within a strategy**, not within a hyperopt class file. + It is also not available in `populate_entry_trend()` and `populate_exit_trend()` methods. ### Possible options for DataProvider @@ -693,31 +793,31 @@ for pair, timeframe in self.dp.available_pairs: ### *current_whitelist()* -Imagine you've developed a strategy that trades the `5m` timeframe using signals generated from a `1d` timeframe on the top 10 volume pairs by volume. +Imagine you've developed a strategy that trades the `5m` timeframe using signals generated from a `1d` timeframe on the top 10 exchange pairs by volume. -The strategy might look something like this: +The strategy logic might look something like this: -*Scan through the top 10 pairs by volume using the `VolumePairList` every 5 minutes and use a 14 day RSI to buy and sell.* +*Scan through the top 10 pairs by volume using the `VolumePairList` every 5 minutes and use a 14 day RSI to enter and exit.* -Due to the limited available data, it's very difficult to resample `5m` candles into daily candles for use in a 14 day RSI. Most exchanges limit us to just 500-1000 candles which effectively gives us around 1.74 daily candles. We need 14 days at least! +Due to the limited available data, it's very difficult to resample `5m` candles into daily candles for use in a 14 day RSI. Most exchanges limit users to just 500-1000 candles which effectively gives us around 1.74 daily candles. We need 14 days at least! -Since we can't resample the data we will have to use an informative pair; and since the whitelist will be dynamic we don't know which pair(s) to use. +Since we can't resample the data we will have to use an informative pair, and since the whitelist will be dynamic we don't know which pair(s) to use! We have a problem! -This is where calling `self.dp.current_whitelist()` comes in handy. +This is where calling `self.dp.current_whitelist()` comes in handy to retrieve only those pairs in the whitelist. ```python def informative_pairs(self): # get access to all pairs available in whitelist. pairs = self.dp.current_whitelist() - # Assign tf to each pair so they can be downloaded and cached for strategy. + # Assign timeframe to each pair so they can be downloaded and cached for strategy. informative_pairs = [(pair, '1d') for pair in pairs] return informative_pairs ``` ??? Note "Plotting with current_whitelist" - Current whitelist is not supported for `plot-dataframe`, as this command is usually used by providing an explicit pairlist - and would therefore make the return values of this method misleading. - It's also not supported for freqUI visualization in [webserver mode](utils.md#webserver-mode) - as the configuration for webserver mode doesn't require a pairlist to be set. + Current whitelist is not supported for `plot-dataframe`, as this command is usually used by providing an explicit pairlist and would therefore make the return values of this method misleading. + It's also not supported for FreqUI visualization in [webserver mode](utils.md#webserver-mode), as the configuration for webserver mode doesn't require a pairlist to be set. ### *get_pair_dataframe(pair, timeframe)* @@ -758,7 +858,7 @@ if self.dp.runmode.value in ('live', 'dry_run'): dataframe['best_ask'] = ob['asks'][0][0] ``` -The orderbook structure is aligned with the order structure from [ccxt](https://github.com/ccxt/ccxt/wiki/Manual#order-book-structure), so the result will look as follows: +The orderbook structure is aligned with the order structure from [ccxt](https://github.com/ccxt/ccxt/wiki/Manual#order-book-structure), so the result will be formatted as follows: ``` js { @@ -776,7 +876,7 @@ The orderbook structure is aligned with the order structure from [ccxt](https:// } ``` -Therefore, using `ob['bids'][0][0]` as demonstrated above will result in using the best bid price. `ob['bids'][0][1]` would look at the amount at this orderbook position. +Therefore, using `ob['bids'][0][0]` as demonstrated above will use the best bid price. `ob['bids'][0][1]` would look at the amount at this orderbook position. !!! Warning "Warning about backtesting" The order book is not part of the historic data which means backtesting and hyperopt will not work correctly if this method is used, as the method will return up-to-date values. @@ -793,12 +893,12 @@ if self.dp.runmode.value in ('live', 'dry_run'): !!! Warning Although the ticker data structure is a part of the ccxt Unified Interface, the values returned by this method can - vary for different exchanges. For instance, many exchanges do not return `vwap` values, some exchanges - does not always fills in the `last` field (so it can be None), etc. So you need to carefully verify the ticker + vary for different exchanges. For instance, many exchanges do not return `vwap` values, and some exchanges + do not always fill in the `last` field (so it can be None), etc. So you need to carefully verify the ticker data returned from the exchange and add appropriate error handling / defaults. !!! Warning "Warning about backtesting" - This method will always return up-to-date values - so usage during backtesting / hyperopt without runmode checks will lead to wrong results. + This method will always return up-to-date / real-time values. As such, usage during backtesting / hyperopt without runmode checks will lead to wrong results, e.g. your whole dataframe will contain the same single value in all rows. ### Send Notification @@ -817,7 +917,7 @@ Notifications will only be sent in trading modes (Live/Dry-run) - so this method !!! Warning "Spamming" You can spam yourself pretty good by setting `always_send=True` in this method. Use this with great care and only in conditions you know will not happen throughout a candle to avoid a message every 5 seconds. -### Complete Data-provider sample +### Complete DataProvider sample ```python from freqtrade.strategy import IStrategy, merge_informative_pair @@ -884,14 +984,14 @@ class SampleStrategy(IStrategy): ## Additional data (Wallets) -The strategy provides access to the `wallets` object. This contains the current balances on the exchange. +The strategy provides access to the `wallets` object. This contains the current balances of your wallets/accounts on the exchange. !!! Note "Backtesting / Hyperopt" - Wallets behaves differently depending on the function it's called. + Wallets behaves differently depending on the function from which it is called. Within `populate_*()` methods, it'll return the full wallet as configured. Within [callbacks](strategy-callbacks.md), you'll get the wallet state corresponding to the actual simulated wallet at that point in the simulation process. -Please always check if `wallets` is available to avoid failures during backtesting. +Always check if `wallets` is available to avoid failures during backtesting. ``` python if self.wallets: @@ -910,15 +1010,15 @@ if self.wallets: ## Additional data (Trades) -A history of Trades can be retrieved in the strategy by querying the database. +A history of trades can be retrieved in the strategy by querying the database. -At the top of the file, import Trade. +At the top of the file, import the required object: ```python from freqtrade.persistence import Trade ``` -The following example queries for the current pair and trades from today, however other filters can easily be added. +The following example queries trades from today for the current pair (`metadata['pair']`). Other filters can easily be added. ``` python trades = Trade.get_trades_proxy(pair=metadata['pair'], @@ -936,7 +1036,9 @@ For a full list of available methods, please consult the [Trade object](trade-ob ## Prevent trades from happening for a specific pair -Freqtrade locks pairs automatically for the current candle (until that candle is over) when a pair is sold, preventing an immediate re-buy of that pair. +Freqtrade locks pairs automatically for the current candle (until that candle is over) when a pair exits, preventing an immediate re-entry of that pair. + +This is to prevent "waterfalls" of many and frequent trades within a single candle. Locked pairs will show the message `Pair is currently locked.`. @@ -947,7 +1049,7 @@ Sometimes it may be desired to lock a pair after certain events happen (e.g. mul Freqtrade has an easy method to do this from within the strategy, by calling `self.lock_pair(pair, until, [reason])`. `until` must be a datetime object in the future, after which trading will be re-enabled for that pair, while `reason` is an optional string detailing why the pair was locked. -Locks can also be lifted manually, by calling `self.unlock_pair(pair)` or `self.unlock_reason()` - providing reason the pair was locked with. +Locks can also be lifted manually, by calling `self.unlock_pair(pair)` or `self.unlock_reason()`, providing the reason the pair was unlocked. `self.unlock_reason()` will unlock all pairs currently locked with the provided reason. To verify if a pair is currently locked, use `self.is_pair_locked(pair)`. @@ -956,7 +1058,7 @@ To verify if a pair is currently locked, use `self.is_pair_locked(pair)`. Locked pairs will always be rounded up to the next candle. So assuming a `5m` timeframe, a lock with `until` set to 10:18 will lock the pair until the candle from 10:15-10:20 will be finished. !!! Warning - Manually locking pairs is not available during backtesting, only locks via Protections are allowed. + Manually locking pairs is not available during backtesting. Only locks via Protections are allowed. #### Pair locking example @@ -966,7 +1068,7 @@ from datetime import timedelta, datetime, timezone # Put the above lines a the top of the strategy file, next to all the other imports # -------- -# Within populate indicators (or populate_buy): +# Within populate indicators (or populate_entry_trend): if self.config['runmode'].value in ('live', 'dry_run'): # fetch closed trades for the last 2 days trades = Trade.get_trades_proxy( @@ -979,9 +1081,9 @@ if self.config['runmode'].value in ('live', 'dry_run'): self.lock_pair(metadata['pair'], until=datetime.now(timezone.utc) + timedelta(hours=12)) ``` -## Print created dataframe +## Print the main dataframe -To inspect the created dataframe, you can issue a print-statement in either `populate_entry_trend()` or `populate_exit_trend()`. +To inspect the current main dataframe, you can issue a print-statement in either `populate_entry_trend()` or `populate_exit_trend()`. You may also want to print the pair so it's clear what data is currently shown. ``` python @@ -1001,29 +1103,30 @@ def populate_entry_trend(self, dataframe: DataFrame, metadata: dict) -> DataFram return dataframe ``` -Printing more than a few rows is also possible (simply use `print(dataframe)` instead of `print(dataframe.tail())`), however not recommended, as that will be very verbose (~500 lines per pair every 5 seconds). +Printing more than a few rows is also possible by using `print(dataframe)` instead of `print(dataframe.tail())`. However this is not recommended, as can results in a lot of output (~500 lines per pair every 5 seconds). ## Common mistakes when developing strategies -### Peeking into the future while backtesting +### Looking into the future while backtesting -Backtesting analyzes the whole time-range at once for performance reasons. Because of this, strategy authors need to make sure that strategies do not look-ahead into the future. -This is a common pain-point, which can cause huge differences between backtesting and dry/live run methods, since they all use data which is not available during dry/live runs, so these strategies will perform well during backtesting, but will fail / perform badly in real conditions. +Backtesting analyzes the whole dataframe timerange at once for performance reasons. Because of this, strategy authors need to make sure that strategies do not lookahead into the future, i.e. using data that would not be available in dry or live mode. -The following lists some common patterns which should be avoided to prevent frustration: +This is a common pain-point, which can cause huge differences between backtesting and dry/live run methods. Strategies that look into the future will perform well during backtesting, often with incredible profits or winrates, but will fail or perform badly in real conditions. + +The following list contains some common patterns which should be avoided to prevent frustration: - don't use `shift(-1)` or other negative values. This uses data from the future in backtesting, which is not available in dry or live modes. - don't use `.iloc[-1]` or any other absolute position in the dataframe within `populate_` functions, as this will be different between dry-run and backtesting. Absolute `iloc` indexing is safe to use in callbacks however - see [Strategy Callbacks](strategy-callbacks.md). -- don't use `dataframe['volume'].mean()`. This uses the full DataFrame for backtesting, including data from the future. Use `dataframe['volume'].rolling().mean()` instead -- don't use `.resample('1h')`. This uses the left border of the interval, so moves data from an hour to the start of the hour. Use `.resample('1h', label='right')` instead. +- don't use functions that use all dataframe or column values, e.g. `dataframe['mean_volume'] = dataframe['volume'].mean()`. As backtesting uses the full dataframe, at any point in the dataframe, the `'mean_volume'` series would include data from the future. Use rolling() calculations instead, e.g. `dataframe['volume'].rolling().mean()`. +- don't use `.resample('1h')`. This uses the left border of the period interval, so moves data from an hour boundary to the start of the hour. Use `.resample('1h', label='right')` instead. !!! Tip "Identifying problems" - You may also want to check the 2 helper commands [lookahead-analysis](lookahead-analysis.md) and [recursive-analysis](recursive-analysis.md), which can each help you figure out problems with your strategy in different ways. - Please treat them as what they are - helpers to identify most common problems. A negative result of each does not guarantee that there's none of the above errors included. + You should always use the two helper commands [lookahead-analysis](lookahead-analysis.md) and [recursive-analysis](recursive-analysis.md), which can each help you figure out problems with your strategy in different ways. + Please treat them as what they are - helpers to identify most common problems. A negative result of each does not guarantee that there are none of the above errors included. ### Colliding signals -When conflicting signals collide (e.g. both `'enter_long'` and `'exit_long'` are 1), freqtrade will do nothing and ignore the entry signal. This will avoid trades that enter, and exit immediately. Obviously, this can potentially lead to missed entries. +When conflicting signals collide (e.g. both `'enter_long'` and `'exit_long'` are set to `1`), freqtrade will do nothing and ignore the entry signal. This will avoid trades that enter, and exit immediately. Obviously, this can potentially lead to missed entries. The following rules apply, and entry signals will be ignored if more than one of the 3 signals is set: @@ -1032,11 +1135,11 @@ The following rules apply, and entry signals will be ignored if more than one of ## Further strategy ideas -To get additional Ideas for strategies, head over to the [strategy repository](https://github.com/freqtrade/freqtrade-strategies). Feel free to use them as they are - but results will depend on the current market situation, pairs used etc. - therefore please backtest the strategy for your exchange/desired pairs first, evaluate carefully, use at your own risk. -Feel free to use any of them as inspiration for your own strategies. -We're happy to accept Pull Requests containing new Strategies to that repo. +To get additional ideas for strategies, head over to the [strategy repository](https://github.com/freqtrade/freqtrade-strategies). Feel free to use them as examples, but results will depend on the current market situation, pairs used, etc. Therefore, these strategies should be considered only for learning purposes, not real world trading. Please backtest the strategy for your exchange/desired pairs first, then dry run to evaluate carefully, and use at your own risk. -## Next step +Feel free to use any of them as inspiration for your own strategies. We're happy to accept Pull Requests containing new strategies to the repository. + +## Next steps Now you have a perfect strategy you probably want to backtest it. -Your next step is to learn [How to use the Backtesting](backtesting.md). +Your next step is to learn [how to use backtesting](backtesting.md). diff --git a/docs/telegram-usage.md b/docs/telegram-usage.md index 377479a90..8fc8b0cfa 100644 --- a/docs/telegram-usage.md +++ b/docs/telegram-usage.md @@ -231,7 +231,7 @@ Once all positions are sold, run `/stop` to completely stop the bot. `/reload_config` resets "max_open_trades" to the value set in the configuration and resets this command. !!! Warning - The stop-buy signal is ONLY active while the bot is running, and is not persisted anyway, so restarting the bot will cause this to reset. + The stop-buy signal is ONLY active while the bot is running, and is not persisted anyway, so restarting the bot will cause this to reset. ### /status diff --git a/docs/utils.md b/docs/utils.md index 5be380b40..dde6006ba 100644 --- a/docs/utils.md +++ b/docs/utils.md @@ -216,6 +216,45 @@ Example: Search dedicated strategy path. freqtrade list-strategies --strategy-path ~/.freqtrade/strategies/ ``` +## List Hyperopt-Loss functions + +Use the `list-hyperoptloss` subcommand to see all hyperopt loss functions available. + +It provides a quick list of all available loss functions in your environment. + +This subcommand can be useful for finding problems in your environment with loading loss functions: modules with Hyperopt-Loss functions that contain errors and failed to load are printed in red (LOAD FAILED), while hyperopt-Loss functions with duplicate names are printed in yellow (DUPLICATE NAME). + +``` +usage: freqtrade list-hyperoptloss [-h] [-v] [--logfile FILE] [-V] [-c PATH] + [-d PATH] [--userdir PATH] + [--hyperopt-path PATH] [-1] [--no-color] + +options: + -h, --help show this help message and exit + --hyperopt-path PATH Specify additional lookup path for Hyperopt Loss + functions. + -1, --one-column Print output in one column. + --no-color Disable colorization of hyperopt results. May be + useful if you are redirecting output to a file. + +Common arguments: + -v, --verbose Verbose mode (-vv for more, -vvv to get all messages). + --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 + -c PATH, --config PATH + Specify configuration file (default: + `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, --data-dir PATH + Path to directory with historical backtesting data. + --userdir PATH, --user-data-dir PATH + Path to userdata directory. +``` + ## List freqAI models Use the `list-freqaimodels` subcommand to see all freqAI models available. diff --git a/docs/windows_installation.md b/docs/windows_installation.md index 5e28d98fb..c824ee5de 100644 --- a/docs/windows_installation.md +++ b/docs/windows_installation.md @@ -5,7 +5,7 @@ We **strongly** recommend that Windows users use [Docker](docker_quickstart.md) If that is not possible, try using the Windows Linux subsystem (WSL) - for which the Ubuntu instructions should work. Otherwise, please follow the instructions below. -All instructions assume that python 3.9+ is installed and available. +All instructions assume that python 3.10+ is installed and available. ## Clone the git repository @@ -42,7 +42,7 @@ cd freqtrade Install ta-lib according to the [ta-lib documentation](https://github.com/TA-Lib/ta-lib-python#windows). -As compiling from source on windows has heavy dependencies (requires a partial visual studio installation), Freqtrade provides these dependencies (in the binary wheel format) for the latest 3 Python versions (3.9, 3.10, 3.11 and 3.12) and for 64bit Windows. +As compiling from source on windows has heavy dependencies (requires a partial visual studio installation), Freqtrade provides these dependencies (in the binary wheel format) for the latest 3 Python versions (3.10, 3.11 and 3.12) and for 64bit Windows. These Wheels are also used by CI running on windows, and are therefore tested together with freqtrade. Other versions must be downloaded from the above link. diff --git a/freqtrade/__init__.py b/freqtrade/__init__.py index dcb8a8616..0e69218fe 100644 --- a/freqtrade/__init__.py +++ b/freqtrade/__init__.py @@ -1,6 +1,6 @@ """Freqtrade bot""" -__version__ = "2024.9" +__version__ = "2024.10" if "dev" in __version__: from pathlib import Path diff --git a/freqtrade/__main__.py b/freqtrade/__main__.py index f39321c83..caa26ce0a 100755 --- a/freqtrade/__main__.py +++ b/freqtrade/__main__.py @@ -3,7 +3,7 @@ __main__.py for Freqtrade To launch Freqtrade as a module -> python -m freqtrade (with Python >= 3.9) +> python -m freqtrade (with Python >= 3.10) """ from freqtrade import main diff --git a/freqtrade/commands/__init__.py b/freqtrade/commands/__init__.py index ec145eb5f..969b8df09 100644 --- a/freqtrade/commands/__init__.py +++ b/freqtrade/commands/__init__.py @@ -27,6 +27,7 @@ from freqtrade.commands.hyperopt_commands import start_hyperopt_list, start_hype from freqtrade.commands.list_commands import ( start_list_exchanges, start_list_freqAI_models, + start_list_hyperopt_loss_functions, start_list_markets, start_list_strategies, start_list_timeframes, diff --git a/freqtrade/commands/analyze_commands.py b/freqtrade/commands/analyze_commands.py index 7d605a228..7966ae6e6 100644 --- a/freqtrade/commands/analyze_commands.py +++ b/freqtrade/commands/analyze_commands.py @@ -1,8 +1,7 @@ import logging from pathlib import Path -from typing import Any, Dict +from typing import Any -from freqtrade.configuration import setup_utils_configuration from freqtrade.enums import RunMode from freqtrade.exceptions import ConfigurationError, OperationalException @@ -10,13 +9,15 @@ from freqtrade.exceptions import ConfigurationError, OperationalException logger = logging.getLogger(__name__) -def setup_analyze_configuration(args: Dict[str, Any], method: RunMode) -> Dict[str, Any]: +def setup_analyze_configuration(args: dict[str, Any], method: RunMode) -> dict[str, Any]: """ Prepare the configuration for the entry/exit reason analysis module :param args: Cli args from Arguments() :param method: Bot running mode :return: Configuration """ + from freqtrade.configuration import setup_utils_configuration + config = setup_utils_configuration(args, method) no_unlimited_runmodes = { @@ -47,7 +48,7 @@ def setup_analyze_configuration(args: Dict[str, Any], method: RunMode) -> Dict[s return config -def start_analysis_entries_exits(args: Dict[str, Any]) -> None: +def start_analysis_entries_exits(args: dict[str, Any]) -> None: """ Start analysis script :param args: Cli args from Arguments() diff --git a/freqtrade/commands/arguments.py b/freqtrade/commands/arguments.py index 0bc3bc7f7..0bb572ebc 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, Dict, List, Optional, Union +from typing import Any, Optional, Union from freqtrade.commands.cli_options import AVAILABLE_CLI_OPTIONS from freqtrade.constants import DEFAULT_CONFIG @@ -23,7 +23,7 @@ ARGS_STRATEGY = [ ARGS_TRADE = ["db_url", "sd_notify", "dry_run", "dry_run_wallet", "fee"] -ARGS_WEBSERVER: List[str] = [] +ARGS_WEBSERVER: list[str] = [] ARGS_COMMON_OPTIMIZE = [ "timeframe", @@ -258,6 +258,7 @@ NO_CONF_REQURIED = [ "list-pairs", "list-strategies", "list-freqaimodels", + "list-hyperoptloss", "list-data", "hyperopt-list", "hyperopt-show", @@ -277,11 +278,11 @@ class Arguments: Arguments Class. Manage the arguments received by the cli """ - def __init__(self, args: Optional[List[str]]) -> None: + def __init__(self, args: Optional[list[str]]) -> None: self.args = args self._parsed_arg: Optional[Namespace] = None - def get_parsed_arg(self) -> Dict[str, Any]: + def get_parsed_arg(self) -> dict[str, Any]: """ Return the list of arguments :return: List[str] List of arguments @@ -322,7 +323,7 @@ class Arguments: return parsed_arg def _build_args( - self, optionlist: List[str], parser: Union[ArgumentParser, _ArgumentGroup] + self, optionlist: list[str], parser: Union[ArgumentParser, _ArgumentGroup] ) -> None: for val in optionlist: opt = AVAILABLE_CLI_OPTIONS[val] @@ -365,6 +366,7 @@ class Arguments: start_list_data, start_list_exchanges, start_list_freqAI_models, + start_list_hyperopt_loss_functions, start_list_markets, start_list_strategies, start_list_timeframes, @@ -566,6 +568,15 @@ class Arguments: list_strategies_cmd.set_defaults(func=start_list_strategies) self._build_args(optionlist=ARGS_LIST_STRATEGIES, parser=list_strategies_cmd) + # Add list-Hyperopt loss subcommand + list_hyperopt_loss_cmd = subparsers.add_parser( + "list-hyperoptloss", + help="Print available hyperopt loss functions.", + parents=[_common_parser], + ) + list_hyperopt_loss_cmd.set_defaults(func=start_list_hyperopt_loss_functions) + self._build_args(optionlist=ARGS_LIST_HYPEROPTS, parser=list_hyperopt_loss_cmd) + # Add list-freqAI Models subcommand list_freqaimodels_cmd = subparsers.add_parser( "list-freqaimodels", diff --git a/freqtrade/commands/build_config_commands.py b/freqtrade/commands/build_config_commands.py index cb64e4da9..63d313b70 100644 --- a/freqtrade/commands/build_config_commands.py +++ b/freqtrade/commands/build_config_commands.py @@ -1,261 +1,27 @@ import logging -import secrets from pathlib import Path -from typing import Any, Dict, List +from typing import Any -from questionary import Separator, prompt - -from freqtrade.configuration import sanitize_config -from freqtrade.configuration.config_setup import setup_utils_configuration -from freqtrade.configuration.detect_environment import running_in_docker -from freqtrade.configuration.directory_operations import chown_user_directory -from freqtrade.constants import UNLIMITED_STAKE_AMOUNT from freqtrade.enums import RunMode from freqtrade.exceptions import OperationalException -from freqtrade.exchange import MAP_EXCHANGE_CHILDCLASS, available_exchanges -from freqtrade.util import render_template logger = logging.getLogger(__name__) -def validate_is_int(val): - try: - _ = int(val) - return True - except Exception: - return False - - -def validate_is_float(val): - try: - _ = float(val) - return True - except Exception: - return False - - -def ask_user_overwrite(config_path: Path) -> bool: - questions = [ - { - "type": "confirm", - "name": "overwrite", - "message": f"File {config_path} already exists. Overwrite?", - "default": False, - }, - ] - answers = prompt(questions) - return answers["overwrite"] - - -def ask_user_config() -> Dict[str, Any]: - """ - Ask user a few questions to build the configuration. - Interactive questions built using https://github.com/tmbo/questionary - :returns: Dict with keys to put into template - """ - questions: List[Dict[str, Any]] = [ - { - "type": "confirm", - "name": "dry_run", - "message": "Do you want to enable Dry-run (simulated trades)?", - "default": True, - }, - { - "type": "text", - "name": "stake_currency", - "message": "Please insert your stake currency:", - "default": "USDT", - }, - { - "type": "text", - "name": "stake_amount", - "message": f"Please insert your stake amount (Number or '{UNLIMITED_STAKE_AMOUNT}'):", - "default": "unlimited", - "validate": lambda val: val == UNLIMITED_STAKE_AMOUNT or validate_is_float(val), - "filter": lambda val: ( - '"' + UNLIMITED_STAKE_AMOUNT + '"' if val == UNLIMITED_STAKE_AMOUNT else val - ), - }, - { - "type": "text", - "name": "max_open_trades", - "message": "Please insert max_open_trades (Integer or -1 for unlimited open trades):", - "default": "3", - "validate": lambda val: validate_is_int(val), - }, - { - "type": "select", - "name": "timeframe_in_config", - "message": "Time", - "choices": ["Have the strategy define timeframe.", "Override in configuration."], - }, - { - "type": "text", - "name": "timeframe", - "message": "Please insert your desired timeframe (e.g. 5m):", - "default": "5m", - "when": lambda x: x["timeframe_in_config"] == "Override in configuration.", - }, - { - "type": "text", - "name": "fiat_display_currency", - "message": ( - "Please insert your display Currency for reporting " - "(leave empty to disable FIAT conversion):" - ), - "default": "USD", - }, - { - "type": "select", - "name": "exchange_name", - "message": "Select exchange", - "choices": [ - "binance", - "binanceus", - "bingx", - "gate", - "htx", - "kraken", - "kucoin", - "okx", - Separator("------------------"), - "other", - ], - }, - { - "type": "confirm", - "name": "trading_mode", - "message": "Do you want to trade Perpetual Swaps (perpetual futures)?", - "default": False, - "filter": lambda val: "futures" if val else "spot", - "when": lambda x: x["exchange_name"] in ["binance", "gate", "okx", "bybit"], - }, - { - "type": "autocomplete", - "name": "exchange_name", - "message": "Type your exchange name (Must be supported by ccxt)", - "choices": available_exchanges(), - "when": lambda x: x["exchange_name"] == "other", - }, - { - "type": "password", - "name": "exchange_key", - "message": "Insert Exchange Key", - "when": lambda x: not x["dry_run"], - }, - { - "type": "password", - "name": "exchange_secret", - "message": "Insert Exchange Secret", - "when": lambda x: not x["dry_run"], - }, - { - "type": "password", - "name": "exchange_key_password", - "message": "Insert Exchange API Key password", - "when": lambda x: not x["dry_run"] and x["exchange_name"] in ("kucoin", "okx"), - }, - { - "type": "confirm", - "name": "telegram", - "message": "Do you want to enable Telegram?", - "default": False, - }, - { - "type": "password", - "name": "telegram_token", - "message": "Insert Telegram token", - "when": lambda x: x["telegram"], - }, - { - "type": "password", - "name": "telegram_chat_id", - "message": "Insert Telegram chat id", - "when": lambda x: x["telegram"], - }, - { - "type": "confirm", - "name": "api_server", - "message": "Do you want to enable the Rest API (includes FreqUI)?", - "default": False, - }, - { - "type": "text", - "name": "api_server_listen_addr", - "message": ( - "Insert Api server Listen Address (0.0.0.0 for docker, " - "otherwise best left untouched)" - ), - "default": "127.0.0.1" if not running_in_docker() else "0.0.0.0", # noqa: S104 - "when": lambda x: x["api_server"], - }, - { - "type": "text", - "name": "api_server_username", - "message": "Insert api-server username", - "default": "freqtrader", - "when": lambda x: x["api_server"], - }, - { - "type": "password", - "name": "api_server_password", - "message": "Insert api-server password", - "when": lambda x: x["api_server"], - }, - ] - answers = prompt(questions) - - if not answers: - # Interrupted questionary sessions return an empty dict. - raise OperationalException("User interrupted interactive questions.") - # Ensure default is set for non-futures exchanges - answers["trading_mode"] = answers.get("trading_mode", "spot") - answers["margin_mode"] = "isolated" if answers.get("trading_mode") == "futures" else "" - # Force JWT token to be a random string - answers["api_server_jwt_key"] = secrets.token_hex() - answers["api_server_ws_token"] = secrets.token_urlsafe(25) - - return answers - - -def deploy_new_config(config_path: Path, selections: Dict[str, Any]) -> None: - """ - Applies selections to the template and writes the result to config_path - :param config_path: Path object for new config file. Should not exist yet - :param selections: Dict containing selections taken by the user. - """ - from jinja2.exceptions import TemplateNotFound - - try: - exchange_template = MAP_EXCHANGE_CHILDCLASS.get( - selections["exchange_name"], selections["exchange_name"] - ) - - selections["exchange"] = render_template( - templatefile=f"subtemplates/exchange_{exchange_template}.j2", arguments=selections - ) - except TemplateNotFound: - selections["exchange"] = render_template( - templatefile="subtemplates/exchange_generic.j2", arguments=selections - ) - - config_text = render_template(templatefile="base_config.json.j2", arguments=selections) - - logger.info(f"Writing config to `{config_path}`.") - logger.info( - "Please make sure to check the configuration contents and adjust settings to your needs." - ) - - config_path.write_text(config_text) - - -def start_new_config(args: Dict[str, Any]) -> None: +def start_new_config(args: dict[str, Any]) -> None: """ Create a new strategy from a template Asking the user questions to fill out the template accordingly. """ + from freqtrade.configuration.deploy_config import ( + ask_user_config, + ask_user_overwrite, + deploy_new_config, + ) + from freqtrade.configuration.directory_operations import chown_user_directory + config_path = Path(args["config"][0]) chown_user_directory(config_path.parent) if config_path.exists(): @@ -271,7 +37,10 @@ def start_new_config(args: Dict[str, Any]) -> None: deploy_new_config(config_path, selections) -def start_show_config(args: Dict[str, Any]) -> None: +def start_show_config(args: dict[str, Any]) -> None: + from freqtrade.configuration import sanitize_config + from freqtrade.configuration.config_setup import setup_utils_configuration + config = setup_utils_configuration(args, RunMode.UTIL_EXCHANGE, set_dry=False) print("Your combined configuration is:") diff --git a/freqtrade/commands/data_commands.py b/freqtrade/commands/data_commands.py index a114444b3..c529e8dc7 100644 --- a/freqtrade/commands/data_commands.py +++ b/freqtrade/commands/data_commands.py @@ -1,24 +1,12 @@ import logging import sys from collections import defaultdict -from typing import Any, Dict +from typing import Any -from freqtrade.configuration import TimeRange, setup_utils_configuration from freqtrade.constants import DATETIME_PRINT_FORMAT, DL_DATA_TIMEFRAMES, Config -from freqtrade.data.converter import ( - convert_ohlcv_format, - convert_trades_format, - convert_trades_to_ohlcv, -) -from freqtrade.data.history import download_data_main from freqtrade.enums import CandleType, RunMode, TradingMode from freqtrade.exceptions import ConfigurationError -from freqtrade.exchange import timeframe_to_minutes -from freqtrade.misc import plural from freqtrade.plugins.pairlist.pairlist_helpers import dynamic_expand_pairlist -from freqtrade.resolvers import ExchangeResolver -from freqtrade.util import print_rich_table -from freqtrade.util.migrations import migrate_data logger = logging.getLogger(__name__) @@ -38,10 +26,13 @@ def _check_data_config_download_sanity(config: Config) -> None: ) -def start_download_data(args: Dict[str, Any]) -> None: +def start_download_data(args: dict[str, Any]) -> None: """ Download data (former download_backtest_data.py script) """ + from freqtrade.configuration import setup_utils_configuration + from freqtrade.data.history import download_data_main + config = setup_utils_configuration(args, RunMode.UTIL_EXCHANGE) _check_data_config_download_sanity(config) @@ -53,7 +44,11 @@ def start_download_data(args: Dict[str, Any]) -> None: sys.exit("SIGINT received, aborting ...") -def start_convert_trades(args: Dict[str, Any]) -> None: +def start_convert_trades(args: dict[str, Any]) -> None: + from freqtrade.configuration import TimeRange, setup_utils_configuration + from freqtrade.data.converter import convert_trades_to_ohlcv + from freqtrade.resolvers import ExchangeResolver + config = setup_utils_configuration(args, RunMode.UTIL_EXCHANGE) timerange = TimeRange() @@ -92,10 +87,14 @@ def start_convert_trades(args: Dict[str, Any]) -> None: ) -def start_convert_data(args: Dict[str, Any], ohlcv: bool = True) -> None: +def start_convert_data(args: dict[str, Any], ohlcv: bool = True) -> None: """ Convert data from one format to another """ + from freqtrade.configuration import setup_utils_configuration + from freqtrade.data.converter import convert_ohlcv_format, convert_trades_format + from freqtrade.util.migrations import migrate_data + config = setup_utils_configuration(args, RunMode.UTIL_NO_EXCHANGE) if ohlcv: migrate_data(config) @@ -114,10 +113,13 @@ def start_convert_data(args: Dict[str, Any], ohlcv: bool = True) -> None: ) -def start_list_data(args: Dict[str, Any]) -> None: +def start_list_data(args: dict[str, Any]) -> None: """ List available OHLCV data """ + from freqtrade.configuration import setup_utils_configuration + from freqtrade.exchange import timeframe_to_minutes + from freqtrade.util import print_rich_table if args["trades"]: start_list_trades_data(args) @@ -177,10 +179,13 @@ def start_list_data(args: Dict[str, Any]) -> None: ) -def start_list_trades_data(args: Dict[str, Any]) -> None: +def start_list_trades_data(args: dict[str, Any]) -> None: """ List available Trades data """ + from freqtrade.configuration import setup_utils_configuration + from freqtrade.misc import plural + from freqtrade.util import print_rich_table config = setup_utils_configuration(args, RunMode.UTIL_NO_EXCHANGE) diff --git a/freqtrade/commands/db_commands.py b/freqtrade/commands/db_commands.py index 98af38ca4..0d856e8a4 100644 --- a/freqtrade/commands/db_commands.py +++ b/freqtrade/commands/db_commands.py @@ -1,18 +1,17 @@ import logging -from typing import Any, Dict +from typing import Any -from sqlalchemy import func, select - -from freqtrade.configuration.config_setup import setup_utils_configuration from freqtrade.enums import RunMode logger = logging.getLogger(__name__) -def start_convert_db(args: Dict[str, Any]) -> None: +def start_convert_db(args: dict[str, Any]) -> None: + from sqlalchemy import func, select from sqlalchemy.orm import make_transient + from freqtrade.configuration.config_setup import setup_utils_configuration from freqtrade.persistence import Order, Trade, init_db from freqtrade.persistence.migrations import set_sequence_ids from freqtrade.persistence.pairlock import PairLock diff --git a/freqtrade/commands/deploy_commands.py b/freqtrade/commands/deploy_commands.py index 3a784bda9..0d188e514 100644 --- a/freqtrade/commands/deploy_commands.py +++ b/freqtrade/commands/deploy_commands.py @@ -1,16 +1,11 @@ import logging import sys from pathlib import Path -from typing import Any, Dict, Optional, Tuple +from typing import Any -import requests - -from freqtrade.configuration import setup_utils_configuration -from freqtrade.configuration.directory_operations import copy_sample_files, create_userdata_dir from freqtrade.constants import USERPATH_STRATEGIES from freqtrade.enums import RunMode from freqtrade.exceptions import ConfigurationError, OperationalException -from freqtrade.util import render_template, render_template_with_fallback logger = logging.getLogger(__name__) @@ -20,12 +15,14 @@ logger = logging.getLogger(__name__) req_timeout = 30 -def start_create_userdir(args: Dict[str, Any]) -> None: +def start_create_userdir(args: dict[str, Any]) -> None: """ Create "user_data" directory to contain user data strategies, hyperopt, ...) :param args: Cli args from Arguments() :return: None """ + from freqtrade.configuration.directory_operations import copy_sample_files, create_userdata_dir + if "user_data_dir" in args and args["user_data_dir"]: userdir = create_userdata_dir(args["user_data_dir"], create_dir=True) copy_sample_files(userdir, overwrite=args["reset"]) @@ -38,6 +35,8 @@ def deploy_new_strategy(strategy_name: str, strategy_path: Path, subtemplate: st """ Deploy new strategy from template to strategy_path """ + from freqtrade.util import render_template, render_template_with_fallback + fallback = "full" attributes = render_template_with_fallback( templatefile=f"strategy_subtemplates/strategy_attributes_{subtemplate}.j2", @@ -81,7 +80,9 @@ def deploy_new_strategy(strategy_name: str, strategy_path: Path, subtemplate: st strategy_path.write_text(strategy_text) -def start_new_strategy(args: Dict[str, Any]) -> None: +def start_new_strategy(args: dict[str, Any]) -> None: + from freqtrade.configuration import setup_utils_configuration + config = setup_utils_configuration(args, RunMode.UTIL_NO_EXCHANGE) if "strategy" in args and args["strategy"]: @@ -98,80 +99,14 @@ def start_new_strategy(args: Dict[str, Any]) -> None: raise ConfigurationError("`new-strategy` requires --strategy to be set.") -def clean_ui_subdir(directory: Path): - if directory.is_dir(): - logger.info("Removing UI directory content.") +def start_install_ui(args: dict[str, Any]) -> None: + from freqtrade.commands.deploy_ui import ( + clean_ui_subdir, + download_and_install_ui, + get_ui_download_url, + read_ui_version, + ) - for p in reversed(list(directory.glob("**/*"))): # iterate contents from leaves to root - if p.name in (".gitkeep", "fallback_file.html"): - continue - if p.is_file(): - p.unlink() - elif p.is_dir(): - p.rmdir() - - -def read_ui_version(dest_folder: Path) -> Optional[str]: - file = dest_folder / ".uiversion" - if not file.is_file(): - return None - - with file.open("r") as f: - return f.read() - - -def download_and_install_ui(dest_folder: Path, dl_url: str, version: str): - from io import BytesIO - from zipfile import ZipFile - - logger.info(f"Downloading {dl_url}") - resp = requests.get(dl_url, timeout=req_timeout).content - dest_folder.mkdir(parents=True, exist_ok=True) - with ZipFile(BytesIO(resp)) as zf: - for fn in zf.filelist: - with zf.open(fn) as x: - destfile = dest_folder / fn.filename - if fn.is_dir(): - destfile.mkdir(exist_ok=True) - else: - destfile.write_bytes(x.read()) - with (dest_folder / ".uiversion").open("w") as f: - f.write(version) - - -def get_ui_download_url(version: Optional[str] = None) -> Tuple[str, str]: - base_url = "https://api.github.com/repos/freqtrade/frequi/" - # Get base UI Repo path - - resp = requests.get(f"{base_url}releases", timeout=req_timeout) - resp.raise_for_status() - r = resp.json() - - if version: - tmp = [x for x in r if x["name"] == version] - if tmp: - latest_version = tmp[0]["name"] - assets = tmp[0].get("assets", []) - else: - raise ValueError("UI-Version not found.") - else: - latest_version = r[0]["name"] - assets = r[0].get("assets", []) - dl_url = "" - if assets and len(assets) > 0: - dl_url = assets[0]["browser_download_url"] - - # URL not found - try assets url - if not dl_url: - assets = r[0]["assets_url"] - resp = requests.get(assets, timeout=req_timeout) - r = resp.json() - dl_url = r[0]["browser_download_url"] - - return dl_url, latest_version - - -def start_install_ui(args: Dict[str, Any]) -> None: dest_folder = Path(__file__).parents[1] / "rpc/api_server/ui/installed/" # First make sure the assets are removed. dl_url, latest_version = get_ui_download_url(args.get("ui_version")) diff --git a/freqtrade/commands/deploy_ui.py b/freqtrade/commands/deploy_ui.py new file mode 100644 index 000000000..283834b12 --- /dev/null +++ b/freqtrade/commands/deploy_ui.py @@ -0,0 +1,84 @@ +import logging +from pathlib import Path +from typing import Optional + +import requests + + +logger = logging.getLogger(__name__) + +# Timeout for requests +req_timeout = 30 + + +def clean_ui_subdir(directory: Path): + if directory.is_dir(): + logger.info("Removing UI directory content.") + + for p in reversed(list(directory.glob("**/*"))): # iterate contents from leaves to root + if p.name in (".gitkeep", "fallback_file.html"): + continue + if p.is_file(): + p.unlink() + elif p.is_dir(): + p.rmdir() + + +def read_ui_version(dest_folder: Path) -> Optional[str]: + file = dest_folder / ".uiversion" + if not file.is_file(): + return None + + with file.open("r") as f: + return f.read() + + +def download_and_install_ui(dest_folder: Path, dl_url: str, version: str): + from io import BytesIO + from zipfile import ZipFile + + logger.info(f"Downloading {dl_url}") + resp = requests.get(dl_url, timeout=req_timeout).content + dest_folder.mkdir(parents=True, exist_ok=True) + with ZipFile(BytesIO(resp)) as zf: + for fn in zf.filelist: + with zf.open(fn) as x: + destfile = dest_folder / fn.filename + if fn.is_dir(): + destfile.mkdir(exist_ok=True) + else: + destfile.write_bytes(x.read()) + with (dest_folder / ".uiversion").open("w") as f: + f.write(version) + + +def get_ui_download_url(version: Optional[str] = None) -> tuple[str, str]: + base_url = "https://api.github.com/repos/freqtrade/frequi/" + # Get base UI Repo path + + resp = requests.get(f"{base_url}releases", timeout=req_timeout) + resp.raise_for_status() + r = resp.json() + + if version: + tmp = [x for x in r if x["name"] == version] + if tmp: + latest_version = tmp[0]["name"] + assets = tmp[0].get("assets", []) + else: + raise ValueError("UI-Version not found.") + else: + latest_version = r[0]["name"] + assets = r[0].get("assets", []) + dl_url = "" + if assets and len(assets) > 0: + dl_url = assets[0]["browser_download_url"] + + # URL not found - try assets url + if not dl_url: + assets = r[0]["assets_url"] + resp = requests.get(assets, timeout=req_timeout) + r = resp.json() + dl_url = r[0]["browser_download_url"] + + return dl_url, latest_version diff --git a/freqtrade/commands/hyperopt_commands.py b/freqtrade/commands/hyperopt_commands.py index d89d25796..4bb33c362 100644 --- a/freqtrade/commands/hyperopt_commands.py +++ b/freqtrade/commands/hyperopt_commands.py @@ -1,21 +1,20 @@ import logging from operator import itemgetter -from typing import Any, Dict +from typing import Any -from freqtrade.configuration import setup_utils_configuration -from freqtrade.data.btanalysis import get_latest_hyperopt_file from freqtrade.enums import RunMode from freqtrade.exceptions import OperationalException -from freqtrade.optimize.optimize_reports import show_backtest_result logger = logging.getLogger(__name__) -def start_hyperopt_list(args: Dict[str, Any]) -> None: +def start_hyperopt_list(args: dict[str, Any]) -> None: """ List hyperopt epochs previously evaluated """ + from freqtrade.configuration import setup_utils_configuration + from freqtrade.data.btanalysis import get_latest_hyperopt_file from freqtrade.optimize.hyperopt_output import HyperoptOutput from freqtrade.optimize.hyperopt_tools import HyperoptTools @@ -57,11 +56,14 @@ def start_hyperopt_list(args: Dict[str, Any]) -> None: HyperoptTools.export_csv_file(config, epochs, export_csv) -def start_hyperopt_show(args: Dict[str, Any]) -> None: +def start_hyperopt_show(args: dict[str, Any]) -> None: """ Show details of a hyperopt epoch previously evaluated """ + from freqtrade.configuration import setup_utils_configuration + from freqtrade.data.btanalysis import get_latest_hyperopt_file from freqtrade.optimize.hyperopt_tools import HyperoptTools + from freqtrade.optimize.optimize_reports import show_backtest_result config = setup_utils_configuration(args, RunMode.UTIL_NO_EXCHANGE) diff --git a/freqtrade/commands/list_commands.py b/freqtrade/commands/list_commands.py index af6e4571f..0279f82ee 100644 --- a/freqtrade/commands/list_commands.py +++ b/freqtrade/commands/list_commands.py @@ -1,33 +1,29 @@ import csv import logging import sys -from typing import Any, Dict, List, Union +from typing import Any, Union -import rapidjson -from rich.console import Console -from rich.table import Table -from rich.text import Text - -from freqtrade.configuration import setup_utils_configuration from freqtrade.enums import RunMode from freqtrade.exceptions import ConfigurationError, OperationalException -from freqtrade.exchange import list_available_exchanges, market_is_active from freqtrade.ft_types import ValidExchangesType -from freqtrade.misc import parse_db_uri_for_logging, plural -from freqtrade.resolvers import ExchangeResolver, StrategyResolver -from freqtrade.util import print_rich_table logger = logging.getLogger(__name__) -def start_list_exchanges(args: Dict[str, Any]) -> None: +def start_list_exchanges(args: dict[str, Any]) -> None: """ Print available exchanges :param args: Cli args from Arguments() :return: None """ - available_exchanges: List[ValidExchangesType] = list_available_exchanges( + from rich.console import Console + from rich.table import Table + from rich.text import Text + + from freqtrade.exchange import list_available_exchanges + + available_exchanges: list[ValidExchangesType] = list_available_exchanges( args["list_exchanges_all"] ) @@ -85,9 +81,13 @@ def start_list_exchanges(args: Dict[str, Any]) -> None: console.print(table) -def _print_objs_tabular(objs: List, print_colorized: bool) -> None: +def _print_objs_tabular(objs: list, print_colorized: bool) -> None: + from rich.console import Console + from rich.table import Table + 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, Union[Text, str]]] = [ { "name": Text(s["name"] if s["name"] else "--"), "location": s["location_rel"], @@ -125,10 +125,13 @@ def _print_objs_tabular(objs: List, print_colorized: bool) -> None: console.print(table) -def start_list_strategies(args: Dict[str, Any]) -> None: +def start_list_strategies(args: dict[str, Any]) -> None: """ Print files with Strategy custom classes available in the directory """ + from freqtrade.configuration import setup_utils_configuration + from freqtrade.resolvers import StrategyResolver + config = setup_utils_configuration(args, RunMode.UTIL_NO_EXCHANGE) strategy_objs = StrategyResolver.search_all_objects( @@ -148,13 +151,15 @@ def start_list_strategies(args: Dict[str, Any]) -> None: _print_objs_tabular(strategy_objs, config.get("print_colorized", False)) -def start_list_freqAI_models(args: Dict[str, Any]) -> None: +def start_list_freqAI_models(args: dict[str, Any]) -> None: """ Print files with FreqAI models custom classes available in the directory """ - config = setup_utils_configuration(args, RunMode.UTIL_NO_EXCHANGE) + from freqtrade.configuration import setup_utils_configuration from freqtrade.resolvers.freqaimodel_resolver import FreqaiModelResolver + config = setup_utils_configuration(args, RunMode.UTIL_NO_EXCHANGE) + model_objs = FreqaiModelResolver.search_all_objects(config, not args["print_one_column"]) # Sort alphabetically model_objs = sorted(model_objs, key=lambda x: x["name"]) @@ -164,10 +169,31 @@ def start_list_freqAI_models(args: Dict[str, Any]) -> None: _print_objs_tabular(model_objs, config.get("print_colorized", False)) -def start_list_timeframes(args: Dict[str, Any]) -> None: +def start_list_hyperopt_loss_functions(args: dict[str, Any]) -> None: + """ + Print files with FreqAI models custom classes available in the directory + """ + from freqtrade.configuration import setup_utils_configuration + from freqtrade.resolvers.hyperopt_resolver import HyperOptLossResolver + + config = setup_utils_configuration(args, RunMode.UTIL_NO_EXCHANGE) + + model_objs = HyperOptLossResolver.search_all_objects(config, not args["print_one_column"]) + # Sort alphabetically + model_objs = sorted(model_objs, key=lambda x: x["name"]) + if args["print_one_column"]: + print("\n".join([s["name"] for s in model_objs])) + else: + _print_objs_tabular(model_objs, config.get("print_colorized", False)) + + +def start_list_timeframes(args: dict[str, Any]) -> None: """ Print timeframes available on Exchange """ + from freqtrade.configuration import setup_utils_configuration + from freqtrade.resolvers import ExchangeResolver + config = setup_utils_configuration(args, RunMode.UTIL_EXCHANGE) # Do not use timeframe set in the config config["timeframe"] = None @@ -184,13 +210,19 @@ def start_list_timeframes(args: Dict[str, Any]) -> None: ) -def start_list_markets(args: Dict[str, Any], pairs_only: bool = False) -> None: +def start_list_markets(args: dict[str, Any], pairs_only: bool = False) -> None: """ Print pairs/markets on the exchange :param args: Cli args from Arguments() :param pairs_only: if True print only pairs, otherwise print all instruments (markets) :return: None """ + from freqtrade.configuration import setup_utils_configuration + from freqtrade.exchange import market_is_active + from freqtrade.misc import plural + from freqtrade.resolvers import ExchangeResolver + from freqtrade.util import print_rich_table + config = setup_utils_configuration(args, RunMode.UTIL_EXCHANGE) # Init exchange @@ -281,6 +313,8 @@ def start_list_markets(args: Dict[str, Any], pairs_only: bool = False) -> None: elif args.get("print_one_column", False): print("\n".join(pairs.keys())) elif args.get("list_pairs_print_json", False): + import rapidjson + print(rapidjson.dumps(list(pairs.keys()), default=str)) elif args.get("print_csv", False): writer = csv.DictWriter(sys.stdout, fieldnames=headers) @@ -296,12 +330,14 @@ def start_list_markets(args: Dict[str, Any], pairs_only: bool = False) -> None: print(f"{summary_str}.") -def start_show_trades(args: Dict[str, Any]) -> None: +def start_show_trades(args: dict[str, Any]) -> None: """ Show trades """ import json + from freqtrade.configuration import setup_utils_configuration + from freqtrade.misc import parse_db_uri_for_logging from freqtrade.persistence import Trade, init_db config = setup_utils_configuration(args, RunMode.UTIL_NO_EXCHANGE) diff --git a/freqtrade/commands/optimize_commands.py b/freqtrade/commands/optimize_commands.py index aa055469a..ea75f3bd1 100644 --- a/freqtrade/commands/optimize_commands.py +++ b/freqtrade/commands/optimize_commands.py @@ -1,23 +1,24 @@ import logging -from typing import Any, Dict +from typing import Any from freqtrade import constants -from freqtrade.configuration import setup_utils_configuration from freqtrade.enums import RunMode from freqtrade.exceptions import ConfigurationError, OperationalException -from freqtrade.util import fmt_coin logger = logging.getLogger(__name__) -def setup_optimize_configuration(args: Dict[str, Any], method: RunMode) -> Dict[str, Any]: +def setup_optimize_configuration(args: dict[str, Any], method: RunMode) -> dict[str, Any]: """ Prepare the configuration for the Hyperopt module :param args: Cli args from Arguments() :param method: Bot running mode :return: Configuration """ + from freqtrade.configuration import setup_utils_configuration + from freqtrade.util import fmt_coin + config = setup_utils_configuration(args, method) no_unlimited_runmodes = { @@ -41,7 +42,7 @@ def setup_optimize_configuration(args: Dict[str, Any], method: RunMode) -> Dict[ return config -def start_backtesting(args: Dict[str, Any]) -> None: +def start_backtesting(args: dict[str, Any]) -> None: """ Start Backtesting script :param args: Cli args from Arguments() @@ -60,10 +61,11 @@ def start_backtesting(args: Dict[str, Any]) -> None: backtesting.start() -def start_backtesting_show(args: Dict[str, Any]) -> None: +def start_backtesting_show(args: dict[str, Any]) -> None: """ Show previous backtest result """ + from freqtrade.configuration import setup_utils_configuration config = setup_utils_configuration(args, RunMode.UTIL_NO_EXCHANGE) @@ -76,7 +78,7 @@ def start_backtesting_show(args: Dict[str, Any]) -> None: show_sorted_pairlist(config, results) -def start_hyperopt(args: Dict[str, Any]) -> None: +def start_hyperopt(args: dict[str, Any]) -> None: """ Start hyperopt script :param args: Cli args from Arguments() @@ -121,7 +123,7 @@ def start_hyperopt(args: Dict[str, Any]) -> None: # Same in Edge and Backtesting start() functions. -def start_edge(args: Dict[str, Any]) -> None: +def start_edge(args: dict[str, Any]) -> None: """ Start Edge script :param args: Cli args from Arguments() @@ -138,24 +140,26 @@ def start_edge(args: Dict[str, Any]) -> None: edge_cli.start() -def start_lookahead_analysis(args: Dict[str, Any]) -> None: +def start_lookahead_analysis(args: dict[str, Any]) -> None: """ Start the backtest bias tester script :param args: Cli args from Arguments() :return: None """ + from freqtrade.configuration import setup_utils_configuration from freqtrade.optimize.analysis.lookahead_helpers import LookaheadAnalysisSubFunctions config = setup_utils_configuration(args, RunMode.UTIL_NO_EXCHANGE) LookaheadAnalysisSubFunctions.start(config) -def start_recursive_analysis(args: Dict[str, Any]) -> None: +def start_recursive_analysis(args: dict[str, Any]) -> None: """ Start the backtest recursive tester script :param args: Cli args from Arguments() :return: None """ + from freqtrade.configuration import setup_utils_configuration from freqtrade.optimize.analysis.recursive_helpers import RecursiveAnalysisSubFunctions config = setup_utils_configuration(args, RunMode.UTIL_NO_EXCHANGE) diff --git a/freqtrade/commands/pairlist_commands.py b/freqtrade/commands/pairlist_commands.py index 8b2963563..4330f501c 100644 --- a/freqtrade/commands/pairlist_commands.py +++ b/freqtrade/commands/pairlist_commands.py @@ -1,22 +1,22 @@ import logging -from typing import Any, Dict +from typing import Any import rapidjson -from freqtrade.configuration import setup_utils_configuration from freqtrade.enums import RunMode -from freqtrade.resolvers import ExchangeResolver logger = logging.getLogger(__name__) -def start_test_pairlist(args: Dict[str, Any]) -> None: +def start_test_pairlist(args: dict[str, Any]) -> None: """ Test Pairlist configuration """ + from freqtrade.configuration import setup_utils_configuration from freqtrade.persistence import FtNoDBContext from freqtrade.plugins.pairlistmanager import PairListManager + from freqtrade.resolvers import ExchangeResolver config = setup_utils_configuration(args, RunMode.UTIL_EXCHANGE) diff --git a/freqtrade/commands/plot_commands.py b/freqtrade/commands/plot_commands.py index 4b939cc80..065559ab9 100644 --- a/freqtrade/commands/plot_commands.py +++ b/freqtrade/commands/plot_commands.py @@ -1,11 +1,10 @@ -from typing import Any, Dict +from typing import Any -from freqtrade.configuration import setup_utils_configuration from freqtrade.enums import RunMode from freqtrade.exceptions import ConfigurationError -def validate_plot_args(args: Dict[str, Any]) -> None: +def validate_plot_args(args: dict[str, Any]) -> None: if not args.get("datadir") and not args.get("config"): raise ConfigurationError( "You need to specify either `--datadir` or `--config` " @@ -13,11 +12,12 @@ def validate_plot_args(args: Dict[str, Any]) -> None: ) -def start_plot_dataframe(args: Dict[str, Any]) -> None: +def start_plot_dataframe(args: dict[str, Any]) -> None: """ Entrypoint for dataframe plotting """ # Import here to avoid errors if plot-dependencies are not installed. + from freqtrade.configuration import setup_utils_configuration from freqtrade.plot.plotting import load_and_plot_trades validate_plot_args(args) @@ -26,11 +26,12 @@ def start_plot_dataframe(args: Dict[str, Any]) -> None: load_and_plot_trades(config) -def start_plot_profit(args: Dict[str, Any]) -> None: +def start_plot_profit(args: dict[str, Any]) -> None: """ Entrypoint for plot_profit """ # Import here to avoid errors if plot-dependencies are not installed. + from freqtrade.configuration import setup_utils_configuration from freqtrade.plot.plotting import plot_profit validate_plot_args(args) diff --git a/freqtrade/commands/strategy_utils_commands.py b/freqtrade/commands/strategy_utils_commands.py index 761a7262c..97d606f0c 100644 --- a/freqtrade/commands/strategy_utils_commands.py +++ b/freqtrade/commands/strategy_utils_commands.py @@ -1,27 +1,22 @@ import logging -import sys import time from pathlib import Path -from typing import Any, Dict +from typing import Any -from freqtrade.configuration import setup_utils_configuration from freqtrade.enums import RunMode -from freqtrade.resolvers import StrategyResolver -from freqtrade.strategy.strategyupdater import StrategyUpdater logger = logging.getLogger(__name__) -def start_strategy_update(args: Dict[str, Any]) -> None: +def start_strategy_update(args: dict[str, Any]) -> None: """ Start the strategy updating script :param args: Cli args from Arguments() :return: None """ - - if sys.version_info == (3, 8): # pragma: no cover - sys.exit("Freqtrade strategy updater requires Python version >= 3.9") + from freqtrade.configuration import setup_utils_configuration + from freqtrade.resolvers import StrategyResolver config = setup_utils_configuration(args, RunMode.UTIL_NO_EXCHANGE) @@ -49,6 +44,8 @@ def start_strategy_update(args: Dict[str, Any]) -> None: def start_conversion(strategy_obj, config): + from freqtrade.strategy.strategyupdater import StrategyUpdater + print(f"Conversion of {Path(strategy_obj['location']).name} started.") instance_strategy_updater = StrategyUpdater() start = time.perf_counter() diff --git a/freqtrade/commands/trade_commands.py b/freqtrade/commands/trade_commands.py index c7f7e524b..59e38c98b 100644 --- a/freqtrade/commands/trade_commands.py +++ b/freqtrade/commands/trade_commands.py @@ -1,12 +1,12 @@ import logging import signal -from typing import Any, Dict +from typing import Any logger = logging.getLogger(__name__) -def start_trading(args: Dict[str, Any]) -> int: +def start_trading(args: dict[str, Any]) -> int: """ Main entry point for trading mode """ diff --git a/freqtrade/commands/webserver_commands.py b/freqtrade/commands/webserver_commands.py index 2fd7fe75c..b60f01773 100644 --- a/freqtrade/commands/webserver_commands.py +++ b/freqtrade/commands/webserver_commands.py @@ -1,9 +1,9 @@ -from typing import Any, Dict +from typing import Any from freqtrade.enums import RunMode -def start_webserver(args: Dict[str, Any]) -> None: +def start_webserver(args: dict[str, Any]) -> None: """ Main entry point for webserver mode """ diff --git a/freqtrade/configuration/__init__.py b/freqtrade/configuration/__init__.py index 8fe65a9b0..aa06a70c9 100644 --- a/freqtrade/configuration/__init__.py +++ b/freqtrade/configuration/__init__.py @@ -1,6 +1,5 @@ # flake8: noqa: F401 -from freqtrade.configuration.asyncio_config import asyncio_setup from freqtrade.configuration.config_secrets import sanitize_config from freqtrade.configuration.config_setup import setup_utils_configuration from freqtrade.configuration.config_validation import validate_config_consistency diff --git a/freqtrade/configuration/config_schema.py b/freqtrade/configuration/config_schema.py index cd349daed..2106d928d 100644 --- a/freqtrade/configuration/config_schema.py +++ b/freqtrade/configuration/config_schema.py @@ -1,10 +1,8 @@ # Required json-schema for user specified config -from typing import Dict from freqtrade.constants import ( AVAILABLE_DATAHANDLERS, AVAILABLE_PAIRLISTS, - AVAILABLE_PROTECTIONS, BACKTEST_BREAKDOWNS, DRY_RUN_WALLET, EXPORT_OPTIONS, @@ -24,7 +22,7 @@ from freqtrade.constants import ( from freqtrade.enums import RPCMessageType -__MESSAGE_TYPE_DICT: Dict[str, Dict[str, str]] = {x: {"type": "object"} for x in RPCMessageType} +__MESSAGE_TYPE_DICT: dict[str, dict[str, str]] = {x: {"type": "object"} for x in RPCMessageType} __IN_STRATEGY = "\nUsually specified in the strategy and missing in the configuration." @@ -449,60 +447,6 @@ CONF_SCHEMA = { "required": ["method"], }, }, - "protections": { - "description": "Configuration for various protections.", - "type": "array", - "items": { - "type": "object", - "properties": { - "method": { - "description": "Method used for the protection.", - "type": "string", - "enum": AVAILABLE_PROTECTIONS, - }, - "stop_duration": { - "description": ( - "Duration to lock the pair after a protection is triggered, " - "in minutes." - ), - "type": "number", - "minimum": 0.0, - }, - "stop_duration_candles": { - "description": ( - "Duration to lock the pair after a protection is triggered, in " - "number of candles." - ), - "type": "number", - "minimum": 0, - }, - "unlock_at": { - "description": ( - "Time when trading will be unlocked regularly. Format: HH:MM" - ), - "type": "string", - }, - "trade_limit": { - "description": "Minimum number of trades required during lookback period.", - "type": "number", - "minimum": 1, - }, - "lookback_period": { - "description": "Period to look back for protection checks, in minutes.", - "type": "number", - "minimum": 1, - }, - "lookback_period_candles": { - "description": ( - "Period to look back for protection checks, in number " "of candles." - ), - "type": "number", - "minimum": 1, - }, - }, - "required": ["method"], - }, - }, # RPC section "telegram": { "description": "Telegram settings.", @@ -1051,6 +995,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/config_setup.py b/freqtrade/configuration/config_setup.py index 1246b6ea6..20b6b2557 100644 --- a/freqtrade/configuration/config_setup.py +++ b/freqtrade/configuration/config_setup.py @@ -1,5 +1,5 @@ import logging -from typing import Any, Dict +from typing import Any from freqtrade.enums import RunMode @@ -11,8 +11,8 @@ logger = logging.getLogger(__name__) def setup_utils_configuration( - args: Dict[str, Any], method: RunMode, *, set_dry: bool = True -) -> Dict[str, Any]: + args: dict[str, Any], method: RunMode, *, set_dry: bool = True +) -> dict[str, Any]: """ Prepare the configuration for utils subcommands :param args: Cli args from Arguments() diff --git a/freqtrade/configuration/config_validation.py b/freqtrade/configuration/config_validation.py index 6a14841ff..8640542a1 100644 --- a/freqtrade/configuration/config_validation.py +++ b/freqtrade/configuration/config_validation.py @@ -1,8 +1,7 @@ import logging from collections import Counter from copy import deepcopy -from datetime import datetime -from typing import Any, Dict +from typing import Any from jsonschema import Draft4Validator, validators from jsonschema.exceptions import ValidationError, best_match @@ -44,7 +43,7 @@ def _extend_validator(validator_class): FreqtradeValidator = _extend_validator(Draft4Validator) -def validate_config_schema(conf: Dict[str, Any], preliminary: bool = False) -> Dict[str, Any]: +def validate_config_schema(conf: dict[str, Any], preliminary: bool = False) -> dict[str, Any]: """ Validate the configuration follow the Config Schema :param conf: Config in JSON format @@ -70,7 +69,7 @@ def validate_config_schema(conf: Dict[str, Any], preliminary: bool = False) -> D raise ValidationError(best_match(Draft4Validator(conf_schema).iter_errors(conf)).message) -def validate_config_consistency(conf: Dict[str, Any], *, preliminary: bool = False) -> None: +def validate_config_consistency(conf: dict[str, Any], *, preliminary: bool = False) -> None: """ Validate the configuration consistency. Should be ran after loading both configuration and strategy, @@ -84,7 +83,6 @@ def validate_config_consistency(conf: Dict[str, Any], *, preliminary: bool = Fal _validate_price_config(conf) _validate_edge(conf) _validate_whitelist(conf) - _validate_protections(conf) _validate_unlimited_amount(conf) _validate_ask_orderbook(conf) _validate_freqai_hyperopt(conf) @@ -99,7 +97,7 @@ def validate_config_consistency(conf: Dict[str, Any], *, preliminary: bool = Fal validate_config_schema(conf, preliminary=preliminary) -def _validate_unlimited_amount(conf: Dict[str, Any]) -> None: +def _validate_unlimited_amount(conf: dict[str, Any]) -> None: """ If edge is disabled, either max_open_trades or stake_amount need to be set. :raise: ConfigurationError if config validation failed @@ -112,7 +110,7 @@ def _validate_unlimited_amount(conf: Dict[str, Any]) -> None: raise ConfigurationError("`max_open_trades` and `stake_amount` cannot both be unlimited.") -def _validate_price_config(conf: Dict[str, Any]) -> None: +def _validate_price_config(conf: dict[str, Any]) -> None: """ When using market orders, price sides must be using the "other" side of the price """ @@ -128,7 +126,7 @@ def _validate_price_config(conf: Dict[str, Any]) -> None: raise ConfigurationError('Market exit orders require exit_pricing.price_side = "other".') -def _validate_trailing_stoploss(conf: Dict[str, Any]) -> None: +def _validate_trailing_stoploss(conf: dict[str, Any]) -> None: if conf.get("stoploss") == 0.0: raise ConfigurationError( "The config stoploss needs to be different from 0 to avoid problems with sell orders." @@ -161,7 +159,7 @@ def _validate_trailing_stoploss(conf: Dict[str, Any]) -> None: ) -def _validate_edge(conf: Dict[str, Any]) -> None: +def _validate_edge(conf: dict[str, Any]) -> None: """ Edge and Dynamic whitelist should not both be enabled, since edge overrides dynamic whitelists. """ @@ -175,7 +173,7 @@ def _validate_edge(conf: Dict[str, Any]) -> None: ) -def _validate_whitelist(conf: Dict[str, Any]) -> None: +def _validate_whitelist(conf: dict[str, Any]) -> None: """ Dynamic whitelist does not require pair_whitelist to be set - however StaticWhitelist does. """ @@ -196,42 +194,7 @@ def _validate_whitelist(conf: Dict[str, Any]) -> None: raise ConfigurationError("StaticPairList requires pair_whitelist to be set.") -def _validate_protections(conf: Dict[str, Any]) -> None: - """ - Validate protection configuration validity - """ - - for prot in conf.get("protections", []): - parsed_unlock_at = None - if (config_unlock_at := prot.get("unlock_at")) is not None: - try: - parsed_unlock_at = datetime.strptime(config_unlock_at, "%H:%M") - except ValueError: - raise ConfigurationError(f"Invalid date format for unlock_at: {config_unlock_at}.") - - if "stop_duration" in prot and "stop_duration_candles" in prot: - raise ConfigurationError( - "Protections must specify either `stop_duration` or `stop_duration_candles`.\n" - f"Please fix the protection {prot.get('method')}." - ) - - if "lookback_period" in prot and "lookback_period_candles" in prot: - raise ConfigurationError( - "Protections must specify either `lookback_period` or `lookback_period_candles`.\n" - f"Please fix the protection {prot.get('method')}." - ) - - if parsed_unlock_at is not None and ( - "stop_duration" in prot or "stop_duration_candles" in prot - ): - raise ConfigurationError( - "Protections must specify either `unlock_at`, `stop_duration` or " - "`stop_duration_candles`.\n" - f"Please fix the protection {prot.get('method')}." - ) - - -def _validate_ask_orderbook(conf: Dict[str, Any]) -> None: +def _validate_ask_orderbook(conf: dict[str, Any]) -> None: ask_strategy = conf.get("exit_pricing", {}) ob_min = ask_strategy.get("order_book_min") ob_max = ask_strategy.get("order_book_max") @@ -251,7 +214,7 @@ def _validate_ask_orderbook(conf: Dict[str, Any]) -> None: ) -def validate_migrated_strategy_settings(conf: Dict[str, Any]) -> None: +def validate_migrated_strategy_settings(conf: dict[str, Any]) -> None: _validate_time_in_force(conf) _validate_order_types(conf) _validate_unfilledtimeout(conf) @@ -259,7 +222,7 @@ def validate_migrated_strategy_settings(conf: Dict[str, Any]) -> None: _strategy_settings(conf) -def _validate_time_in_force(conf: Dict[str, Any]) -> None: +def _validate_time_in_force(conf: dict[str, Any]) -> None: time_in_force = conf.get("order_time_in_force", {}) if "buy" in time_in_force or "sell" in time_in_force: if conf.get("trading_mode", TradingMode.SPOT) != TradingMode.SPOT: @@ -280,7 +243,7 @@ def _validate_time_in_force(conf: Dict[str, Any]) -> None: ) -def _validate_order_types(conf: Dict[str, Any]) -> None: +def _validate_order_types(conf: dict[str, Any]) -> None: order_types = conf.get("order_types", {}) old_order_types = [ "buy", @@ -315,7 +278,7 @@ def _validate_order_types(conf: Dict[str, Any]) -> None: process_deprecated_setting(conf, "order_types", o, "order_types", n) -def _validate_unfilledtimeout(conf: Dict[str, Any]) -> None: +def _validate_unfilledtimeout(conf: dict[str, Any]) -> None: unfilledtimeout = conf.get("unfilledtimeout", {}) if any(x in unfilledtimeout for x in ["buy", "sell"]): if conf.get("trading_mode", TradingMode.SPOT) != TradingMode.SPOT: @@ -334,7 +297,7 @@ def _validate_unfilledtimeout(conf: Dict[str, Any]) -> None: process_deprecated_setting(conf, "unfilledtimeout", o, "unfilledtimeout", n) -def _validate_pricing_rules(conf: Dict[str, Any]) -> None: +def _validate_pricing_rules(conf: dict[str, Any]) -> None: if conf.get("ask_strategy") or conf.get("bid_strategy"): if conf.get("trading_mode", TradingMode.SPOT) != TradingMode.SPOT: raise ConfigurationError("Please migrate your pricing settings to use the new wording.") @@ -364,7 +327,7 @@ def _validate_pricing_rules(conf: Dict[str, Any]) -> None: del conf["ask_strategy"] -def _validate_freqai_hyperopt(conf: Dict[str, Any]) -> None: +def _validate_freqai_hyperopt(conf: dict[str, Any]) -> None: freqai_enabled = conf.get("freqai", {}).get("enabled", False) analyze_per_epoch = conf.get("analyze_per_epoch", False) if analyze_per_epoch and freqai_enabled: @@ -373,7 +336,7 @@ def _validate_freqai_hyperopt(conf: Dict[str, Any]) -> None: ) -def _validate_freqai_include_timeframes(conf: Dict[str, Any], preliminary: bool) -> None: +def _validate_freqai_include_timeframes(conf: dict[str, Any], preliminary: bool) -> None: freqai_enabled = conf.get("freqai", {}).get("enabled", False) if freqai_enabled: main_tf = conf.get("timeframe", "5m") @@ -404,7 +367,7 @@ def _validate_freqai_include_timeframes(conf: Dict[str, Any], preliminary: bool) ) -def _validate_freqai_backtest(conf: Dict[str, Any]) -> None: +def _validate_freqai_backtest(conf: dict[str, Any]) -> None: if conf.get("runmode", RunMode.OTHER) == RunMode.BACKTEST: freqai_enabled = conf.get("freqai", {}).get("enabled", False) timerange = conf.get("timerange") @@ -427,7 +390,7 @@ def _validate_freqai_backtest(conf: Dict[str, Any]) -> None: ) -def _validate_consumers(conf: Dict[str, Any]) -> None: +def _validate_consumers(conf: dict[str, Any]) -> None: emc_conf = conf.get("external_message_consumer", {}) if emc_conf.get("enabled", False): if len(emc_conf.get("producers", [])) < 1: @@ -447,7 +410,7 @@ def _validate_consumers(conf: Dict[str, Any]) -> None: ) -def _validate_orderflow(conf: Dict[str, Any]) -> None: +def _validate_orderflow(conf: dict[str, Any]) -> None: if conf.get("exchange", {}).get("use_public_trades"): if "orderflow" not in conf: raise ConfigurationError( @@ -455,7 +418,7 @@ def _validate_orderflow(conf: Dict[str, Any]) -> None: ) -def _strategy_settings(conf: Dict[str, Any]) -> None: +def _strategy_settings(conf: dict[str, Any]) -> None: process_deprecated_setting(conf, None, "use_sell_signal", None, "use_exit_signal") process_deprecated_setting(conf, None, "sell_profit_only", None, "exit_profit_only") process_deprecated_setting(conf, None, "sell_profit_offset", None, "exit_profit_offset") diff --git a/freqtrade/configuration/configuration.py b/freqtrade/configuration/configuration.py index 0a99f9044..c9ab86737 100644 --- a/freqtrade/configuration/configuration.py +++ b/freqtrade/configuration/configuration.py @@ -7,7 +7,7 @@ import logging import warnings from copy import deepcopy from pathlib import Path -from typing import Any, Callable, Dict, List, Optional, Tuple +from typing import Any, Callable, Optional from freqtrade import constants from freqtrade.configuration.deprecated_settings import process_temporary_deprecated_settings @@ -37,7 +37,7 @@ 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: Optional[RunMode] = None) -> None: self.args = args self.config: Optional[Config] = None self.runmode = runmode @@ -53,7 +53,7 @@ class Configuration: return self.config @staticmethod - def from_files(files: List[str]) -> Dict[str, Any]: + def from_files(files: list[str]) -> dict[str, Any]: """ Iterate through the config files passed in, loading all of them and merging their contents. @@ -68,7 +68,7 @@ class Configuration: c = Configuration({"config": files}, RunMode.OTHER) return c.get_config() - def load_config(self) -> Dict[str, Any]: + def load_config(self) -> dict[str, Any]: """ Extract information for sys.argv and load the bot configuration :return: Configuration dictionary @@ -421,7 +421,7 @@ class Configuration: ] self._args_to_config_loop(config, configurations) - def _args_to_config_loop(self, config, configurations: List[Tuple[str, str]]) -> None: + def _args_to_config_loop(self, config, configurations: list[tuple[str, str]]) -> None: for argname, logstring in configurations: self._args_to_config(config, argname=argname, logstring=logstring) diff --git a/freqtrade/configuration/deploy_config.py b/freqtrade/configuration/deploy_config.py new file mode 100644 index 000000000..071ee0b11 --- /dev/null +++ b/freqtrade/configuration/deploy_config.py @@ -0,0 +1,250 @@ +import logging +import secrets +from pathlib import Path +from typing import Any + +from questionary import Separator, prompt + +from freqtrade.constants import UNLIMITED_STAKE_AMOUNT +from freqtrade.exceptions import OperationalException + + +logger = logging.getLogger(__name__) + + +def validate_is_int(val): + try: + _ = int(val) + return True + except Exception: + return False + + +def validate_is_float(val): + try: + _ = float(val) + return True + except Exception: + return False + + +def ask_user_overwrite(config_path: Path) -> bool: + questions = [ + { + "type": "confirm", + "name": "overwrite", + "message": f"File {config_path} already exists. Overwrite?", + "default": False, + }, + ] + answers = prompt(questions) + return answers["overwrite"] + + +def ask_user_config() -> dict[str, Any]: + """ + Ask user a few questions to build the configuration. + Interactive questions built using https://github.com/tmbo/questionary + :returns: Dict with keys to put into template + """ + + from freqtrade.configuration.detect_environment import running_in_docker + from freqtrade.exchange import available_exchanges + + questions: list[dict[str, Any]] = [ + { + "type": "confirm", + "name": "dry_run", + "message": "Do you want to enable Dry-run (simulated trades)?", + "default": True, + }, + { + "type": "text", + "name": "stake_currency", + "message": "Please insert your stake currency:", + "default": "USDT", + }, + { + "type": "text", + "name": "stake_amount", + "message": f"Please insert your stake amount (Number or '{UNLIMITED_STAKE_AMOUNT}'):", + "default": "unlimited", + "validate": lambda val: val == UNLIMITED_STAKE_AMOUNT or validate_is_float(val), + "filter": lambda val: ( + '"' + UNLIMITED_STAKE_AMOUNT + '"' if val == UNLIMITED_STAKE_AMOUNT else val + ), + }, + { + "type": "text", + "name": "max_open_trades", + "message": "Please insert max_open_trades (Integer or -1 for unlimited open trades):", + "default": "3", + "validate": lambda val: validate_is_int(val), + }, + { + "type": "select", + "name": "timeframe_in_config", + "message": "Time", + "choices": ["Have the strategy define timeframe.", "Override in configuration."], + }, + { + "type": "text", + "name": "timeframe", + "message": "Please insert your desired timeframe (e.g. 5m):", + "default": "5m", + "when": lambda x: x["timeframe_in_config"] == "Override in configuration.", + }, + { + "type": "text", + "name": "fiat_display_currency", + "message": ( + "Please insert your display Currency for reporting " + "(leave empty to disable FIAT conversion):" + ), + "default": "USD", + }, + { + "type": "select", + "name": "exchange_name", + "message": "Select exchange", + "choices": [ + "binance", + "binanceus", + "bingx", + "gate", + "htx", + "kraken", + "kucoin", + "okx", + Separator("------------------"), + "other", + ], + }, + { + "type": "confirm", + "name": "trading_mode", + "message": "Do you want to trade Perpetual Swaps (perpetual futures)?", + "default": False, + "filter": lambda val: "futures" if val else "spot", + "when": lambda x: x["exchange_name"] in ["binance", "gate", "okx", "bybit"], + }, + { + "type": "autocomplete", + "name": "exchange_name", + "message": "Type your exchange name (Must be supported by ccxt)", + "choices": available_exchanges(), + "when": lambda x: x["exchange_name"] == "other", + }, + { + "type": "password", + "name": "exchange_key", + "message": "Insert Exchange Key", + "when": lambda x: not x["dry_run"], + }, + { + "type": "password", + "name": "exchange_secret", + "message": "Insert Exchange Secret", + "when": lambda x: not x["dry_run"], + }, + { + "type": "password", + "name": "exchange_key_password", + "message": "Insert Exchange API Key password", + "when": lambda x: not x["dry_run"] and x["exchange_name"] in ("kucoin", "okx"), + }, + { + "type": "confirm", + "name": "telegram", + "message": "Do you want to enable Telegram?", + "default": False, + }, + { + "type": "password", + "name": "telegram_token", + "message": "Insert Telegram token", + "when": lambda x: x["telegram"], + }, + { + "type": "password", + "name": "telegram_chat_id", + "message": "Insert Telegram chat id", + "when": lambda x: x["telegram"], + }, + { + "type": "confirm", + "name": "api_server", + "message": "Do you want to enable the Rest API (includes FreqUI)?", + "default": False, + }, + { + "type": "text", + "name": "api_server_listen_addr", + "message": ( + "Insert Api server Listen Address (0.0.0.0 for docker, " + "otherwise best left untouched)" + ), + "default": "127.0.0.1" if not running_in_docker() else "0.0.0.0", # noqa: S104 + "when": lambda x: x["api_server"], + }, + { + "type": "text", + "name": "api_server_username", + "message": "Insert api-server username", + "default": "freqtrader", + "when": lambda x: x["api_server"], + }, + { + "type": "password", + "name": "api_server_password", + "message": "Insert api-server password", + "when": lambda x: x["api_server"], + }, + ] + answers = prompt(questions) + + if not answers: + # Interrupted questionary sessions return an empty dict. + raise OperationalException("User interrupted interactive questions.") + # Ensure default is set for non-futures exchanges + answers["trading_mode"] = answers.get("trading_mode", "spot") + answers["margin_mode"] = "isolated" if answers.get("trading_mode") == "futures" else "" + # Force JWT token to be a random string + answers["api_server_jwt_key"] = secrets.token_hex() + answers["api_server_ws_token"] = secrets.token_urlsafe(25) + + return answers + + +def deploy_new_config(config_path: Path, selections: dict[str, Any]) -> None: + """ + Applies selections to the template and writes the result to config_path + :param config_path: Path object for new config file. Should not exist yet + :param selections: Dict containing selections taken by the user. + """ + from jinja2.exceptions import TemplateNotFound + + from freqtrade.exchange import MAP_EXCHANGE_CHILDCLASS + from freqtrade.util import render_template + + try: + exchange_template = MAP_EXCHANGE_CHILDCLASS.get( + selections["exchange_name"], selections["exchange_name"] + ) + + selections["exchange"] = render_template( + templatefile=f"subtemplates/exchange_{exchange_template}.j2", arguments=selections + ) + except TemplateNotFound: + selections["exchange"] = render_template( + templatefile="subtemplates/exchange_generic.j2", arguments=selections + ) + + config_text = render_template(templatefile="base_config.json.j2", arguments=selections) + + logger.info(f"Writing config to `{config_path}`.") + logger.info( + "Please make sure to check the configuration contents and adjust settings to your needs." + ) + + config_path.write_text(config_text) diff --git a/freqtrade/configuration/deprecated_settings.py b/freqtrade/configuration/deprecated_settings.py index 6a0901ed7..c4d78e588 100644 --- a/freqtrade/configuration/deprecated_settings.py +++ b/freqtrade/configuration/deprecated_settings.py @@ -177,4 +177,6 @@ def process_temporary_deprecated_settings(config: Config) -> None: ) if "protections" in config: - logger.warning("DEPRECATED: Setting 'protections' in the configuration is deprecated.") + raise ConfigurationError( + "DEPRECATED: Setting 'protections' in the configuration is deprecated." + ) diff --git a/freqtrade/configuration/environment_vars.py b/freqtrade/configuration/environment_vars.py index 0830f3df7..37445538d 100644 --- a/freqtrade/configuration/environment_vars.py +++ b/freqtrade/configuration/environment_vars.py @@ -1,6 +1,6 @@ import logging import os -from typing import Any, Dict +from typing import Any from freqtrade.constants import ENV_VAR_PREFIX from freqtrade.misc import deep_merge_dicts @@ -24,7 +24,7 @@ def _get_var_typed(val): return val -def _flat_vars_to_nested_dict(env_dict: Dict[str, Any], prefix: str) -> Dict[str, Any]: +def _flat_vars_to_nested_dict(env_dict: dict[str, Any], prefix: str) -> dict[str, Any]: """ Environment variables must be prefixed with FREQTRADE. FREQTRADE__{section}__{key} @@ -33,7 +33,7 @@ def _flat_vars_to_nested_dict(env_dict: Dict[str, Any], prefix: str) -> Dict[str :return: Nested dict based on available and relevant variables. """ no_convert = ["CHAT_ID", "PASSWORD"] - relevant_vars: Dict[str, Any] = {} + relevant_vars: dict[str, Any] = {} for env_var, val in sorted(env_dict.items()): if env_var.startswith(prefix): @@ -51,7 +51,7 @@ def _flat_vars_to_nested_dict(env_dict: Dict[str, Any], prefix: str) -> Dict[str return relevant_vars -def enironment_vars_to_dict() -> Dict[str, Any]: +def enironment_vars_to_dict() -> dict[str, Any]: """ Read environment variables and return a nested dict for relevant variables Relevant variables must follow the FREQTRADE__{section}__{key} pattern diff --git a/freqtrade/configuration/load_config.py b/freqtrade/configuration/load_config.py index c11f6b37e..6ff451246 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, Dict, List, Optional +from typing import Any, Optional import rapidjson @@ -42,7 +42,7 @@ def log_config_error_range(path: str, errmsg: str) -> str: return "" -def load_file(path: Path) -> Dict[str, Any]: +def load_file(path: Path) -> dict[str, Any]: try: with path.open("r") as file: config = rapidjson.load(file, parse_mode=CONFIG_PARSE_MODE) @@ -51,7 +51,7 @@ def load_file(path: Path) -> Dict[str, Any]: return config -def load_config_file(path: str) -> Dict[str, Any]: +def load_config_file(path: str) -> dict[str, Any]: """ Loads a config file from the given path :param path: path as str @@ -78,8 +78,8 @@ def load_config_file(path: str) -> Dict[str, Any]: def load_from_files( - files: List[str], base_path: Optional[Path] = None, level: int = 0 -) -> Dict[str, Any]: + files: list[str], base_path: Optional[Path] = None, level: int = 0 +) -> dict[str, Any]: """ Recursively load configuration files if specified. Sub-files are assumed to be relative to the initial config. diff --git a/freqtrade/constants.py b/freqtrade/constants.py index 120f463f3..9acb3bdc0 100644 --- a/freqtrade/constants.py +++ b/freqtrade/constants.py @@ -4,7 +4,7 @@ bot constants """ -from typing import Any, Dict, List, Literal, Optional, Tuple +from typing import Any, Literal, Optional from freqtrade.enums import CandleType, PriceType @@ -57,7 +57,6 @@ AVAILABLE_PAIRLISTS = [ "SpreadFilter", "VolatilityFilter", ] -AVAILABLE_PROTECTIONS = ["CooldownPeriod", "LowProfitPairs", "MaxDrawdown", "StoplossGuard"] AVAILABLE_DATAHANDLERS = ["json", "jsongz", "hdf5", "feather", "parquet"] BACKTEST_BREAKDOWNS = ["day", "week", "month"] BACKTEST_CACHE_AGE = ["none", "day", "week", "month"] @@ -188,14 +187,14 @@ CANCEL_REASON = { } # List of pairs with their timeframes -PairWithTimeframe = Tuple[str, str, CandleType] -ListPairsWithTimeframes = List[PairWithTimeframe] +PairWithTimeframe = tuple[str, str, CandleType] +ListPairsWithTimeframes = list[PairWithTimeframe] # Type for trades list -TradeList = List[List] +TradeList = list[list] # ticks, pair, timeframe, CandleType -TickWithTimeframe = Tuple[str, str, CandleType, Optional[int], Optional[int]] -ListTicksWithTimeframes = List[TickWithTimeframe] +TickWithTimeframe = tuple[str, str, CandleType, Optional[int], Optional[int]] +ListTicksWithTimeframes = list[TickWithTimeframe] LongShort = Literal["long", "short"] EntryExit = Literal["entry", "exit"] @@ -204,9 +203,9 @@ MakerTaker = Literal["maker", "taker"] BidAsk = Literal["bid", "ask"] OBLiteral = Literal["asks", "bids"] -Config = Dict[str, Any] +Config = dict[str, Any] # Exchange part of the configuration. -ExchangeConfig = Dict[str, Any] +ExchangeConfig = dict[str, Any] IntOrInf = float diff --git a/freqtrade/data/btanalysis.py b/freqtrade/data/btanalysis.py index 580807a76..7e4d02f75 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, Dict, List, Literal, Optional, Union +from typing import Any, Literal, Optional, Union import numpy as np import pandas as pd @@ -137,7 +137,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: Union[Path, str]) -> dict[str, Any]: """ Read metadata dictionary from backtest results file without reading and deserializing entire file. @@ -176,7 +176,7 @@ def load_backtest_stats(filename: Union[Path, str]) -> BacktestResultType: return data -def load_and_merge_backtest_result(strategy_name: str, filename: Path, results: Dict[str, Any]): +def load_and_merge_backtest_result(strategy_name: str, filename: Path, results: dict[str, Any]): """ Load one strategy from multi-strategy result and merge it with results :param strategy_name: Name of the strategy contained in the result @@ -195,12 +195,12 @@ def load_and_merge_backtest_result(strategy_name: str, filename: Path, results: break -def _get_backtest_files(dirname: Path) -> List[Path]: +def _get_backtest_files(dirname: Path) -> list[Path]: # Weird glob expression here avoids including .meta.json files. return list(reversed(sorted(dirname.glob("backtest-result-*-[0-9][0-9].json")))) -def _extract_backtest_result(filename: Path) -> List[BacktestHistoryEntryType]: +def _extract_backtest_result(filename: Path) -> list[BacktestHistoryEntryType]: metadata = load_backtest_metadata(filename) return [ { @@ -220,14 +220,14 @@ def _extract_backtest_result(filename: Path) -> List[BacktestHistoryEntryType]: ] -def get_backtest_result(filename: Path) -> List[BacktestHistoryEntryType]: +def get_backtest_result(filename: Path) -> list[BacktestHistoryEntryType]: """ Get backtest result read from metadata file """ return _extract_backtest_result(filename) -def get_backtest_resultlist(dirname: Path) -> List[BacktestHistoryEntryType]: +def get_backtest_resultlist(dirname: Path) -> list[BacktestHistoryEntryType]: """ Get list of backtest results read from metadata files """ @@ -244,12 +244,13 @@ def delete_backtest_result(file_abs: Path): """ # *.meta.json logger.info(f"Deleting backtest result file: {file_abs.name}") - file_abs_meta = file_abs.with_suffix(".meta.json") - file_abs.unlink() - file_abs_meta.unlink() + + for file in file_abs.parent.glob(f"{file_abs.stem}*"): + logger.info(f"Deleting file: {file}") + file.unlink() -def update_backtest_metadata(filename: Path, strategy: str, content: Dict[str, Any]): +def update_backtest_metadata(filename: Path, strategy: str, content: dict[str, Any]): """ Updates backtest metadata file with new content. :raises: ValueError if metadata file does not exist, or strategy is not in this file. @@ -275,8 +276,8 @@ 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 -) -> Dict[str, Any]: + dirname: Union[Path, str], run_ids: dict[str, str], min_backtest_date: Optional[datetime] = None +) -> dict[str, Any]: """ Find existing backtest stats that match specified run IDs and load them. :param dirname: pathlib.Path object, or string pointing to the file. @@ -287,7 +288,7 @@ def find_existing_backtest_stats( # Copy so we can modify this dict without affecting parent scope. run_ids = copy(run_ids) dirname = Path(dirname) - results: Dict[str, Any] = { + results: dict[str, Any] = { "metadata": {}, "strategy": {}, "strategy_comparison": [], @@ -438,7 +439,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: Union[list[Trade], list[LocalTrade]]) -> pd.DataFrame: """ Convert list of Trade objects to pandas Dataframe :param trades: List of trade objects diff --git a/freqtrade/data/converter/converter.py b/freqtrade/data/converter/converter.py index 0475ddee2..48a07082e 100644 --- a/freqtrade/data/converter/converter.py +++ b/freqtrade/data/converter/converter.py @@ -3,7 +3,6 @@ Functions to convert data from one format to another """ import logging -from typing import Dict import numpy as np import pandas as pd @@ -158,8 +157,8 @@ def trim_dataframe( def trim_dataframes( - preprocessed: Dict[str, DataFrame], timerange, startup_candles: int -) -> Dict[str, DataFrame]: + preprocessed: dict[str, DataFrame], timerange, startup_candles: int +) -> dict[str, DataFrame]: """ Trim startup period from analyzed dataframes :param preprocessed: Dict of pair: dataframe @@ -167,7 +166,7 @@ def trim_dataframes( :param startup_candles: Startup-candles that should be removed :return: Dict of trimmed dataframes """ - processed: Dict[str, DataFrame] = {} + processed: dict[str, DataFrame] = {} for pair, df in preprocessed.items(): trimed_df = trim_dataframe(df, timerange, startup_candles=startup_candles) diff --git a/freqtrade/data/converter/orderflow.py b/freqtrade/data/converter/orderflow.py index f0cc726d2..e64caa88b 100644 --- a/freqtrade/data/converter/orderflow.py +++ b/freqtrade/data/converter/orderflow.py @@ -7,7 +7,6 @@ import time import typing from collections import OrderedDict from datetime import datetime -from typing import Tuple import numpy as np import pandas as pd @@ -62,11 +61,11 @@ def _calculate_ohlcv_candle_start_and_end(df: pd.DataFrame, timeframe: str): def populate_dataframe_with_trades( - cached_grouped_trades: OrderedDict[Tuple[datetime, datetime], pd.DataFrame], + cached_grouped_trades: OrderedDict[tuple[datetime, datetime], pd.DataFrame], config: Config, dataframe: pd.DataFrame, trades: pd.DataFrame, -) -> Tuple[pd.DataFrame, OrderedDict[Tuple[datetime, datetime], pd.DataFrame]]: +) -> tuple[pd.DataFrame, OrderedDict[tuple[datetime, datetime], pd.DataFrame]]: """ Populates a dataframe with trades :param dataframe: Dataframe to populate diff --git a/freqtrade/data/converter/trade_converter.py b/freqtrade/data/converter/trade_converter.py index 9b8fe718e..4d34fa83f 100644 --- a/freqtrade/data/converter/trade_converter.py +++ b/freqtrade/data/converter/trade_converter.py @@ -4,7 +4,6 @@ Functions to convert data from one format to another import logging from pathlib import Path -from typing import Dict, List import pandas as pd from pandas import DataFrame, to_datetime @@ -34,7 +33,7 @@ def trades_df_remove_duplicates(trades: pd.DataFrame) -> pd.DataFrame: return trades.drop_duplicates(subset=["timestamp", "id"]) -def trades_dict_to_list(trades: List[Dict]) -> TradeList: +def trades_dict_to_list(trades: list[dict]) -> TradeList: """ Convert fetch_trades result into a List (to be more memory efficient). :param trades: List of trades, as returned by ccxt.fetch_trades. @@ -91,8 +90,8 @@ def trades_to_ohlcv(trades: DataFrame, timeframe: str) -> DataFrame: def convert_trades_to_ohlcv( - pairs: List[str], - timeframes: List[str], + pairs: list[str], + timeframes: list[str], datadir: Path, timerange: TimeRange, erase: bool, diff --git a/freqtrade/data/dataprovider.py b/freqtrade/data/dataprovider.py index e40228511..491728695 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, Dict, List, Optional, Tuple +from typing import Any, Optional from pandas import DataFrame, Timedelta, Timestamp, to_timedelta @@ -48,15 +48,15 @@ class DataProvider: self._exchange = exchange self._pairlists = pairlists self.__rpc = rpc - self.__cached_pairs: Dict[PairWithTimeframe, Tuple[DataFrame, datetime]] = {} + self.__cached_pairs: dict[PairWithTimeframe, tuple[DataFrame, datetime]] = {} self.__slice_index: Optional[int] = None self.__slice_date: Optional[datetime] = None - self.__cached_pairs_backtesting: Dict[PairWithTimeframe, DataFrame] = {} - self.__producer_pairs_df: Dict[ - str, Dict[PairWithTimeframe, Tuple[DataFrame, datetime]] + self.__cached_pairs_backtesting: dict[PairWithTimeframe, DataFrame] = {} + self.__producer_pairs_df: dict[ + str, dict[PairWithTimeframe, tuple[DataFrame, datetime]] ] = {} - self.__producer_pairs: Dict[str, List[str]] = {} + self.__producer_pairs: dict[str, list[str]] = {} self._msg_queue: deque = deque() self._default_candle_type = self._config.get("candle_type_def", CandleType.SPOT) @@ -101,7 +101,7 @@ class DataProvider: self.__cached_pairs[pair_key] = (dataframe, datetime.now(timezone.utc)) # For multiple producers we will want to merge the pairlists instead of overwriting - def _set_producer_pairs(self, pairlist: List[str], producer_name: str = "default"): + def _set_producer_pairs(self, pairlist: list[str], producer_name: str = "default"): """ Set the pairs received to later be used. @@ -109,7 +109,7 @@ class DataProvider: """ self.__producer_pairs[producer_name] = pairlist - def get_producer_pairs(self, producer_name: str = "default") -> List[str]: + def get_producer_pairs(self, producer_name: str = "default") -> list[str]: """ Get the pairs cached from the producer @@ -177,7 +177,7 @@ class DataProvider: timeframe: str, candle_type: CandleType, producer_name: str = "default", - ) -> Tuple[bool, int]: + ) -> tuple[bool, int]: """ Append a candle to the existing external dataframe. The incoming dataframe must have at least 1 candle. @@ -258,7 +258,7 @@ class DataProvider: timeframe: Optional[str] = None, candle_type: Optional[CandleType] = None, producer_name: str = "default", - ) -> Tuple[DataFrame, datetime]: + ) -> tuple[DataFrame, datetime]: """ Get the pair data from producers. @@ -377,7 +377,7 @@ class DataProvider: logger.warning(f"No data found for ({pair}, {timeframe}, {candle_type}).") return data - def get_analyzed_dataframe(self, pair: str, timeframe: str) -> Tuple[DataFrame, datetime]: + def get_analyzed_dataframe(self, pair: str, timeframe: str) -> tuple[DataFrame, datetime]: """ Retrieve the analyzed dataframe. Returns the full dataframe in trade mode (live / dry), and the last 1000 candles (up to the time evaluated at this moment) in all other modes. @@ -408,7 +408,7 @@ class DataProvider: """ return RunMode(self._config.get("runmode", RunMode.OTHER)) - def current_whitelist(self) -> List[str]: + def current_whitelist(self) -> list[str]: """ fetch latest available whitelist. @@ -529,7 +529,7 @@ class DataProvider: ) return trades_df - def market(self, pair: str) -> Optional[Dict[str, Any]]: + def market(self, pair: str) -> Optional[dict[str, Any]]: """ Return market data for the pair :param pair: Pair to get the data for diff --git a/freqtrade/data/entryexitanalysis.py b/freqtrade/data/entryexitanalysis.py index f7ab7836f..b4b5b966a 100644 --- a/freqtrade/data/entryexitanalysis.py +++ b/freqtrade/data/entryexitanalysis.py @@ -1,6 +1,5 @@ import logging from pathlib import Path -from typing import Dict, List import joblib import pandas as pd @@ -48,14 +47,14 @@ def _load_signal_candles(backtest_dir: Path): return _load_backtest_analysis_data(backtest_dir, "signals") -def _load_exit_signal_candles(backtest_dir: Path) -> Dict[str, Dict[str, pd.DataFrame]]: +def _load_exit_signal_candles(backtest_dir: Path) -> dict[str, dict[str, pd.DataFrame]]: return _load_backtest_analysis_data(backtest_dir, "exited") def _process_candles_and_indicators( pairlist, strategy_name, trades, signal_candles, date_col: str = "open_date" ): - analysed_trades_dict: Dict[str, Dict] = {strategy_name: {}} + analysed_trades_dict: dict[str, dict] = {strategy_name: {}} try: logger.info(f"Processing {strategy_name} : {len(pairlist)} pairs") @@ -261,8 +260,8 @@ def prepare_results( def print_results( res_df: pd.DataFrame, exit_df: pd.DataFrame, - analysis_groups: List[str], - indicator_list: List[str], + analysis_groups: list[str], + indicator_list: list[str], entry_only: bool, exit_only: bool, csv_path: Path, @@ -307,7 +306,7 @@ def print_results( def _merge_dfs( entry_df: pd.DataFrame, exit_df: pd.DataFrame, - available_inds: List[str], + available_inds: list[str], entry_only: bool, exit_only: bool, ): @@ -438,7 +437,7 @@ def _generate_dfs( pairlist: list, enter_reason_list: list, exit_reason_list: list, - signal_candles: Dict, + signal_candles: dict, strategy_name: str, timerange: TimeRange, trades: pd.DataFrame, diff --git a/freqtrade/data/history/datahandlers/idatahandler.py b/freqtrade/data/history/datahandlers/idatahandler.py index db1660dc8..940c4d71e 100644 --- a/freqtrade/data/history/datahandlers/idatahandler.py +++ b/freqtrade/data/history/datahandlers/idatahandler.py @@ -10,7 +10,7 @@ from abc import ABC, abstractmethod from copy import deepcopy from datetime import datetime, timezone from pathlib import Path -from typing import List, Optional, Tuple, Type +from typing import Optional from pandas import DataFrame, to_datetime @@ -71,7 +71,7 @@ class IDataHandler(ABC): ] @classmethod - def ohlcv_get_pairs(cls, datadir: Path, timeframe: str, candle_type: CandleType) -> List[str]: + def ohlcv_get_pairs(cls, datadir: Path, timeframe: str, candle_type: CandleType) -> list[str]: """ Returns a list of all pairs with ohlcv data available in this datadir for the specified timeframe @@ -107,7 +107,7 @@ class IDataHandler(ABC): def ohlcv_data_min_max( self, pair: str, timeframe: str, candle_type: CandleType - ) -> Tuple[datetime, datetime, int]: + ) -> tuple[datetime, datetime, int]: """ Returns the min and max timestamp for the given pair and timeframe. :param pair: Pair to get min/max for @@ -168,7 +168,7 @@ class IDataHandler(ABC): """ @classmethod - def trades_get_available_data(cls, datadir: Path, trading_mode: TradingMode) -> List[str]: + def trades_get_available_data(cls, datadir: Path, trading_mode: TradingMode) -> list[str]: """ Returns a list of all pairs with ohlcv data available in this datadir :param datadir: Directory to search for ohlcv files @@ -191,7 +191,7 @@ class IDataHandler(ABC): self, pair: str, trading_mode: TradingMode, - ) -> Tuple[datetime, datetime, int]: + ) -> tuple[datetime, datetime, int]: """ Returns the min and max timestamp for the given pair's trades data. :param pair: Pair to get min/max for @@ -212,7 +212,7 @@ class IDataHandler(ABC): ) @classmethod - def trades_get_pairs(cls, datadir: Path) -> List[str]: + def trades_get_pairs(cls, datadir: Path) -> list[str]: """ Returns a list of all pairs for which trade data is available in this :param datadir: Directory to search for ohlcv files @@ -532,7 +532,7 @@ class IDataHandler(ABC): Path(old_name).rename(new_name) -def get_datahandlerclass(datatype: str) -> Type[IDataHandler]: +def get_datahandlerclass(datatype: str) -> type[IDataHandler]: """ Get datahandler class. Could be done using Resolvers, but since this may be called often and resolvers diff --git a/freqtrade/data/history/history_utils.py b/freqtrade/data/history/history_utils.py index 8a65db26a..092faa19a 100644 --- a/freqtrade/data/history/history_utils.py +++ b/freqtrade/data/history/history_utils.py @@ -2,7 +2,7 @@ import logging import operator from datetime import datetime, timedelta from pathlib import Path -from typing import Dict, List, Optional, Tuple +from typing import Optional from pandas import DataFrame, concat @@ -77,7 +77,7 @@ def load_pair_history( def load_data( datadir: Path, timeframe: str, - pairs: List[str], + pairs: list[str], *, timerange: Optional[TimeRange] = None, fill_up_missing: bool = True, @@ -86,7 +86,7 @@ def load_data( data_format: str = "feather", candle_type: CandleType = CandleType.SPOT, user_futures_funding_rate: Optional[int] = None, -) -> Dict[str, DataFrame]: +) -> dict[str, DataFrame]: """ Load ohlcv history data for a list of pairs. @@ -101,7 +101,7 @@ def load_data( :param candle_type: Any of the enum CandleType (must match trading mode!) :return: dict(:) """ - result: Dict[str, DataFrame] = {} + result: dict[str, DataFrame] = {} if startup_candles > 0 and timerange: logger.info(f"Using indicator startup period: {startup_candles} ...") @@ -135,7 +135,7 @@ def refresh_data( *, datadir: Path, timeframe: str, - pairs: List[str], + pairs: list[str], exchange: Exchange, data_format: Optional[str] = None, timerange: Optional[TimeRange] = None, @@ -172,7 +172,7 @@ def _load_cached_data_for_updating( data_handler: IDataHandler, candle_type: CandleType, prepend: bool = False, -) -> Tuple[DataFrame, Optional[int], Optional[int]]: +) -> tuple[DataFrame, Optional[int], Optional[int]]: """ Load cached data to download more data. If timerange is passed in, checks whether data from an before the stored data will be @@ -318,8 +318,8 @@ def _download_pair_history( def refresh_backtest_ohlcv_data( exchange: Exchange, - pairs: List[str], - timeframes: List[str], + pairs: list[str], + timeframes: list[str], datadir: Path, trading_mode: str, timerange: Optional[TimeRange] = None, @@ -327,7 +327,7 @@ def refresh_backtest_ohlcv_data( erase: bool = False, data_format: Optional[str] = None, prepend: bool = False, -) -> List[str]: +) -> list[str]: """ Refresh stored ohlcv data for backtesting and hyperopt operations. Used by freqtrade download-data subcommand. @@ -489,14 +489,14 @@ def _download_trades_history( def refresh_backtest_trades_data( exchange: Exchange, - pairs: List[str], + pairs: list[str], datadir: Path, timerange: TimeRange, trading_mode: TradingMode, new_pairs_days: int = 30, erase: bool = False, data_format: str = "feather", -) -> List[str]: +) -> list[str]: """ Refresh stored trades data for backtesting and hyperopt operations. Used by freqtrade download-data subcommand. @@ -531,7 +531,7 @@ def refresh_backtest_trades_data( return pairs_not_available -def get_timerange(data: Dict[str, DataFrame]) -> Tuple[datetime, datetime]: +def get_timerange(data: dict[str, DataFrame]) -> tuple[datetime, datetime]: """ Get the maximum common timerange for the given backtest data. @@ -583,12 +583,12 @@ def download_data_main(config: Config) -> None: timerange = TimeRange.parse_timerange(f"{time_since}-") if "timerange" in config: - timerange = timerange.parse_timerange(config["timerange"]) + timerange = TimeRange.parse_timerange(config["timerange"]) # Remove stake-currency to skip checks which are not relevant for datadownload config["stake_currency"] = "" - pairs_not_available: List[str] = [] + pairs_not_available: list[str] = [] # Init exchange from freqtrade.resolvers.exchange_resolver import ExchangeResolver diff --git a/freqtrade/data/metrics.py b/freqtrade/data/metrics.py index 2e4673fb5..de8ebac4a 100644 --- a/freqtrade/data/metrics.py +++ b/freqtrade/data/metrics.py @@ -2,7 +2,6 @@ import logging import math from dataclasses import dataclass from datetime import datetime -from typing import Dict, Tuple import numpy as np import pandas as pd @@ -11,7 +10,7 @@ import pandas as pd logger = logging.getLogger(__name__) -def calculate_market_change(data: Dict[str, pd.DataFrame], column: str = "close") -> float: +def calculate_market_change(data: dict[str, pd.DataFrame], column: str = "close") -> float: """ Calculate market change based on "column". Calculation is done by taking the first non-null and the last non-null element of each column @@ -32,7 +31,7 @@ def calculate_market_change(data: Dict[str, pd.DataFrame], column: str = "close" def combine_dataframes_by_column( - data: Dict[str, pd.DataFrame], column: str = "close" + data: dict[str, pd.DataFrame], column: str = "close" ) -> pd.DataFrame: """ Combine multiple dataframes "column" @@ -50,7 +49,7 @@ def combine_dataframes_by_column( def combined_dataframes_with_rel_mean( - data: Dict[str, pd.DataFrame], fromdt: datetime, todt: datetime, column: str = "close" + data: dict[str, pd.DataFrame], fromdt: datetime, todt: datetime, column: str = "close" ) -> pd.DataFrame: """ Combine multiple dataframes "column" @@ -70,7 +69,7 @@ def combined_dataframes_with_rel_mean( def combine_dataframes_with_mean( - data: Dict[str, pd.DataFrame], column: str = "close" + data: dict[str, pd.DataFrame], column: str = "close" ) -> pd.DataFrame: """ Combine multiple dataframes "column" @@ -222,7 +221,7 @@ def calculate_max_drawdown( ) -def calculate_csum(trades: pd.DataFrame, starting_balance: float = 0) -> Tuple[float, float]: +def calculate_csum(trades: pd.DataFrame, starting_balance: float = 0) -> tuple[float, float]: """ Calculate min/max cumsum of trades, to show if the wallet/stake amount ratio is sane :param trades: DataFrame containing trades (requires columns close_date and profit_percent) @@ -255,15 +254,15 @@ def calculate_cagr(days_passed: int, starting_balance: float, final_balance: flo return (final_balance / starting_balance) ** (1 / (days_passed / 365)) - 1 -def calculate_expectancy(trades: pd.DataFrame) -> Tuple[float, float]: +def calculate_expectancy(trades: pd.DataFrame) -> tuple[float, float]: """ Calculate expectancy :param trades: DataFrame containing trades (requires columns close_date and profit_abs) :return: expectancy, expectancy_ratio """ - expectancy = 0 - expectancy_ratio = 100 + expectancy = 0.0 + expectancy_ratio = 100.0 if len(trades) > 0: winning_trades = trades.loc[trades["profit_abs"] > 0] diff --git a/freqtrade/edge/edge_positioning.py b/freqtrade/edge/edge_positioning.py index b6cc1a7df..546126513 100644 --- a/freqtrade/edge/edge_positioning.py +++ b/freqtrade/edge/edge_positioning.py @@ -5,7 +5,7 @@ import logging from collections import defaultdict from copy import deepcopy from datetime import timedelta -from typing import Any, Dict, List, NamedTuple +from typing import Any, NamedTuple import numpy as np import utils_find_1st as utf1st @@ -44,7 +44,7 @@ class Edge: Author: https://github.com/mishaker """ - _cached_pairs: Dict[str, Any] = {} # Keeps a list of pairs + _cached_pairs: dict[str, Any] = {} # Keeps a list of pairs def __init__(self, config: Config, exchange, strategy) -> None: self.config = config @@ -52,7 +52,7 @@ class Edge: self.strategy: IStrategy = strategy self.edge_config = self.config.get("edge", {}) - self._cached_pairs: Dict[str, Any] = {} # Keeps a list of pairs + self._cached_pairs: dict[str, Any] = {} # Keeps a list of pairs self._final_pairs: list = [] # checking max_open_trades. it should be -1 as with Edge @@ -93,7 +93,7 @@ class Edge: except IndexError: self.fee = None - def calculate(self, pairs: List[str]) -> bool: + def calculate(self, pairs: list[str]) -> bool: if self.fee is None and pairs: self.fee = self.exchange.get_fee(pairs[0]) @@ -104,7 +104,7 @@ class Edge: ): return False - data: Dict[str, Any] = {} + data: dict[str, Any] = {} logger.info("Using stake_currency: %s ...", self.config["stake_currency"]) logger.info("Using local backtesting data (using whitelist in given config) ...") @@ -231,7 +231,7 @@ class Edge: ) return self.strategy.stoploss - def adjust(self, pairs: List[str]) -> list: + def adjust(self, pairs: list[str]) -> list: """ Filters out and sorts "pairs" according to Edge calculated pairs """ @@ -260,7 +260,7 @@ class Edge: return self._final_pairs - def accepted_pairs(self) -> List[Dict[str, Any]]: + def accepted_pairs(self) -> list[dict[str, Any]]: """ return a list of accepted pairs along with their winrate, expectancy and stoploss """ @@ -322,7 +322,7 @@ class Edge: return result - def _process_expectancy(self, results: DataFrame) -> Dict[str, Any]: + def _process_expectancy(self, results: DataFrame) -> dict[str, Any]: """ This calculates WinRate, Required Risk Reward, Risk Reward and Expectancy of all pairs The calculation will be done per pair and per strategy. diff --git a/freqtrade/exchange/__init__.py b/freqtrade/exchange/__init__.py index d52f94293..0d1176b35 100644 --- a/freqtrade/exchange/__init__.py +++ b/freqtrade/exchange/__init__.py @@ -43,4 +43,5 @@ from freqtrade.exchange.hyperliquid import Hyperliquid from freqtrade.exchange.idex import Idex from freqtrade.exchange.kraken import Kraken from freqtrade.exchange.kucoin import Kucoin +from freqtrade.exchange.lbank import Lbank from freqtrade.exchange.okx import Okx diff --git a/freqtrade/exchange/binance.py b/freqtrade/exchange/binance.py index d7fb0a353..c0e46c32a 100644 --- a/freqtrade/exchange/binance.py +++ b/freqtrade/exchange/binance.py @@ -3,7 +3,7 @@ import logging from datetime import datetime, timezone from pathlib import Path -from typing import Dict, List, Optional, Tuple +from typing import Optional import ccxt @@ -46,14 +46,14 @@ class Binance(Exchange): "ws_enabled": False, } - _supported_trading_mode_margin_pairs: List[Tuple[TradingMode, MarginMode]] = [ + _supported_trading_mode_margin_pairs: list[tuple[TradingMode, MarginMode]] = [ # TradingMode.SPOT always supported and not required in this list # (TradingMode.MARGIN, MarginMode.CROSS), # (TradingMode.FUTURES, MarginMode.CROSS), (TradingMode.FUTURES, MarginMode.ISOLATED) ] - def get_tickers(self, symbols: Optional[List[str]] = None, cached: bool = False) -> Tickers: + def get_tickers(self, symbols: Optional[list[str]] = 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. @@ -144,6 +144,29 @@ 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]]: + """ + Fetch funding rates for the given symbols. + :param symbols: List of symbols to fetch funding rates for + :return: Dict of funding rates for the given symbols + """ + try: + if self.trading_mode == TradingMode.FUTURES: + rates = self._api.fetch_funding_rates(symbols) + return rates + return {} + except ccxt.DDoSProtection as e: + raise DDosProtection(e) from e + except (ccxt.OperationFailed, ccxt.ExchangeError) as e: + raise TemporaryError( + f"Error in additional_exchange_init due to {e.__class__.__name__}. Message: {e}" + ) from e + + except ccxt.BaseError as e: + raise OperationalException(e) from e + def dry_run_liquidation_price( self, pair: str, @@ -153,8 +176,7 @@ class Binance(Exchange): stake_amount: float, leverage: float, wallet_balance: float, # Or margin balance - mm_ex_1: float = 0.0, # (Binance) Cross only - upnl_ex_1: float = 0.0, # (Binance) Cross only + open_trades: list, ) -> Optional[float]: """ Important: Must be fetching data from cached values as this is used by backtesting! @@ -172,6 +194,7 @@ class Binance(Exchange): :param wallet_balance: Amount of margin_mode in the wallet being used to trade Cross-Margin Mode: crossWalletBalance Isolated-Margin Mode: isolatedWalletBalance + :param open_trades: List of open trades in the same wallet # * Only required for Cross :param mm_ex_1: (TMM) @@ -180,15 +203,41 @@ class Binance(Exchange): :param upnl_ex_1: (UPNL) Cross-Margin Mode: Unrealized PNL of all other contracts, excluding Contract 1. Isolated-Margin Mode: 0 + :param other """ - - side_1 = -1 if is_short else 1 - cross_vars = upnl_ex_1 - mm_ex_1 if self.margin_mode == MarginMode.CROSS else 0.0 + cross_vars: float = 0.0 # mm_ratio: Binance's formula specifies maintenance margin rate which is mm_ratio * 100% # maintenance_amt: (CUM) Maintenance Amount of position mm_ratio, maintenance_amt = self.get_maintenance_ratio_and_amt(pair, stake_amount) + if self.margin_mode == MarginMode.CROSS: + mm_ex_1: float = 0.0 + upnl_ex_1: float = 0.0 + pairs = [trade.pair for trade in open_trades] + if self._config["runmode"] in ("live", "dry_run"): + funding_rates = self.fetch_funding_rates(pairs) + for trade in open_trades: + if trade.pair == pair: + # Only "other" trades are considered + continue + if self._config["runmode"] in ("live", "dry_run"): + mark_price = funding_rates[trade.pair]["markPrice"] + else: + # Fall back to open rate for backtesting + mark_price = trade.open_rate + mm_ratio1, maint_amnt1 = self.get_maintenance_ratio_and_amt( + trade.pair, trade.stake_amount + ) + maint_margin = trade.amount * mark_price * mm_ratio1 - maint_amnt1 + mm_ex_1 += maint_margin + + upnl_ex_1 += trade.amount * mark_price - trade.amount * trade.open_rate + + cross_vars = upnl_ex_1 - mm_ex_1 + + side_1 = -1 if is_short else 1 + if maintenance_amt is None: raise OperationalException( "Parameter maintenance_amt is required by Binance.liquidation_price" @@ -204,7 +253,7 @@ class Binance(Exchange): "Freqtrade only supports isolated futures for leverage trading" ) - def load_leverage_tiers(self) -> Dict[str, List[Dict]]: + def load_leverage_tiers(self) -> dict[str, list[dict]]: if self.trading_mode == TradingMode.FUTURES: if self._config["dry_run"]: leverage_tiers_path = Path(__file__).parent / "binance_leverage_tiers.json" diff --git a/freqtrade/exchange/binance_leverage_tiers.json b/freqtrade/exchange/binance_leverage_tiers.json index c0eac5b0e..98512868d 100644 --- a/freqtrade/exchange/binance_leverage_tiers.json +++ b/freqtrade/exchange/binance_leverage_tiers.json @@ -275,6 +275,152 @@ } } ], + "1000CAT/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": 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": "9", + "initialLeverage": "1", + "notionalCap": "10000000", + "notionalFloor": "5000000", + "maintMarginRatio": "0.5", + "cum": "1668150.0" + } + } + ], "1000FLOKI/USDT:USDT": [ { "tier": 1.0, @@ -702,112 +848,128 @@ "tier": 3.0, "currency": "USDT", "minNotional": 200000.0, - "maxNotional": 1000000.0, - "maintenanceMarginRate": 0.02, - "maxLeverage": 25.0, + "maxNotional": 300000.0, + "maintenanceMarginRate": 0.015, + "maxLeverage": 40.0, "info": { "bracket": "3", - "initialLeverage": "25", - "notionalCap": "1000000", + "initialLeverage": "40", + "notionalCap": "300000", "notionalFloor": "200000", - "maintMarginRatio": "0.02", - "cum": "2070.0" + "maintMarginRatio": "0.015", + "cum": "1070.0" } }, { "tier": 4.0, "currency": "USDT", - "minNotional": 1000000.0, - "maxNotional": 2000000.0, - "maintenanceMarginRate": 0.025, - "maxLeverage": 20.0, + "minNotional": 300000.0, + "maxNotional": 1500000.0, + "maintenanceMarginRate": 0.02, + "maxLeverage": 25.0, "info": { "bracket": "4", - "initialLeverage": "20", - "notionalCap": "2000000", - "notionalFloor": "1000000", - "maintMarginRatio": "0.025", - "cum": "7070.0" + "initialLeverage": "25", + "notionalCap": "1500000", + "notionalFloor": "300000", + "maintMarginRatio": "0.02", + "cum": "2570.0" } }, { "tier": 5.0, "currency": "USDT", - "minNotional": 2000000.0, - "maxNotional": 10000000.0, - "maintenanceMarginRate": 0.05, - "maxLeverage": 10.0, + "minNotional": 1500000.0, + "maxNotional": 3000000.0, + "maintenanceMarginRate": 0.025, + "maxLeverage": 20.0, "info": { "bracket": "5", - "initialLeverage": "10", - "notionalCap": "10000000", - "notionalFloor": "2000000", - "maintMarginRatio": "0.05", - "cum": "57070.0" + "initialLeverage": "20", + "notionalCap": "3000000", + "notionalFloor": "1500000", + "maintMarginRatio": "0.025", + "cum": "10070.0" } }, { "tier": 6.0, "currency": "USDT", - "minNotional": 10000000.0, - "maxNotional": 20000000.0, - "maintenanceMarginRate": 0.1, - "maxLeverage": 5.0, + "minNotional": 3000000.0, + "maxNotional": 15000000.0, + "maintenanceMarginRate": 0.05, + "maxLeverage": 10.0, "info": { "bracket": "6", - "initialLeverage": "5", - "notionalCap": "20000000", - "notionalFloor": "10000000", - "maintMarginRatio": "0.1", - "cum": "557070.0" + "initialLeverage": "10", + "notionalCap": "15000000", + "notionalFloor": "3000000", + "maintMarginRatio": "0.05", + "cum": "85070.0" } }, { "tier": 7.0, "currency": "USDT", - "minNotional": 20000000.0, - "maxNotional": 25000000.0, - "maintenanceMarginRate": 0.125, - "maxLeverage": 4.0, + "minNotional": 15000000.0, + "maxNotional": 30000000.0, + "maintenanceMarginRate": 0.1, + "maxLeverage": 5.0, "info": { "bracket": "7", - "initialLeverage": "4", - "notionalCap": "25000000", - "notionalFloor": "20000000", - "maintMarginRatio": "0.125", - "cum": "1057070.0" + "initialLeverage": "5", + "notionalCap": "30000000", + "notionalFloor": "15000000", + "maintMarginRatio": "0.1", + "cum": "835070.0" } }, { "tier": 8.0, "currency": "USDT", - "minNotional": 25000000.0, - "maxNotional": 50000000.0, - "maintenanceMarginRate": 0.25, - "maxLeverage": 2.0, + "minNotional": 30000000.0, + "maxNotional": 37500000.0, + "maintenanceMarginRate": 0.125, + "maxLeverage": 4.0, "info": { "bracket": "8", - "initialLeverage": "2", - "notionalCap": "50000000", - "notionalFloor": "25000000", - "maintMarginRatio": "0.25", - "cum": "4182070.0" + "initialLeverage": "4", + "notionalCap": "37500000", + "notionalFloor": "30000000", + "maintMarginRatio": "0.125", + "cum": "1585070.0" } }, { "tier": 9.0, "currency": "USDT", - "minNotional": 50000000.0, - "maxNotional": 100000000.0, + "minNotional": 37500000.0, + "maxNotional": 75000000.0, + "maintenanceMarginRate": 0.25, + "maxLeverage": 2.0, + "info": { + "bracket": "9", + "initialLeverage": "2", + "notionalCap": "75000000", + "notionalFloor": "37500000", + "maintMarginRatio": "0.25", + "cum": "6272570.0" + } + }, + { + "tier": 10.0, + "currency": "USDT", + "minNotional": 75000000.0, + "maxNotional": 150000000.0, "maintenanceMarginRate": 0.5, "maxLeverage": 1.0, "info": { - "bracket": "9", + "bracket": "10", "initialLeverage": "1", - "notionalCap": "100000000", - "notionalFloor": "50000000", + "notionalCap": "150000000", + "notionalFloor": "75000000", "maintMarginRatio": "0.5", - "cum": "16682070.0" + "cum": "25022570.0" } } ], @@ -816,112 +978,112 @@ "tier": 1.0, "currency": "USDT", "minNotional": 0.0, - "maxNotional": 5000.0, - "maintenanceMarginRate": 0.015, - "maxLeverage": 50.0, + "maxNotional": 10000.0, + "maintenanceMarginRate": 0.01, + "maxLeverage": 75.0, "info": { "bracket": "1", - "initialLeverage": "50", - "notionalCap": "5000", + "initialLeverage": "75", + "notionalCap": "10000", "notionalFloor": "0", - "maintMarginRatio": "0.015", + "maintMarginRatio": "0.01", "cum": "0.0" } }, { "tier": 2.0, "currency": "USDT", - "minNotional": 5000.0, - "maxNotional": 25000.0, - "maintenanceMarginRate": 0.02, - "maxLeverage": 25.0, + "minNotional": 10000.0, + "maxNotional": 20000.0, + "maintenanceMarginRate": 0.015, + "maxLeverage": 50.0, "info": { "bracket": "2", - "initialLeverage": "25", - "notionalCap": "25000", - "notionalFloor": "5000", - "maintMarginRatio": "0.02", - "cum": "25.0" + "initialLeverage": "50", + "notionalCap": "20000", + "notionalFloor": "10000", + "maintMarginRatio": "0.015", + "cum": "50.0" } }, { "tier": 3.0, "currency": "USDT", - "minNotional": 25000.0, - "maxNotional": 50000.0, - "maintenanceMarginRate": 0.025, - "maxLeverage": 20.0, + "minNotional": 20000.0, + "maxNotional": 100000.0, + "maintenanceMarginRate": 0.02, + "maxLeverage": 25.0, "info": { "bracket": "3", - "initialLeverage": "20", - "notionalCap": "50000", - "notionalFloor": "25000", - "maintMarginRatio": "0.025", + "initialLeverage": "25", + "notionalCap": "100000", + "notionalFloor": "20000", + "maintMarginRatio": "0.02", "cum": "150.0" } }, { "tier": 4.0, "currency": "USDT", - "minNotional": 50000.0, - "maxNotional": 500000.0, - "maintenanceMarginRate": 0.05, - "maxLeverage": 10.0, + "minNotional": 100000.0, + "maxNotional": 200000.0, + "maintenanceMarginRate": 0.025, + "maxLeverage": 20.0, "info": { "bracket": "4", - "initialLeverage": "10", - "notionalCap": "500000", - "notionalFloor": "50000", - "maintMarginRatio": "0.05", - "cum": "1400.0" + "initialLeverage": "20", + "notionalCap": "200000", + "notionalFloor": "100000", + "maintMarginRatio": "0.025", + "cum": "650.0" } }, { "tier": 5.0, "currency": "USDT", - "minNotional": 500000.0, + "minNotional": 200000.0, "maxNotional": 1000000.0, - "maintenanceMarginRate": 0.1, - "maxLeverage": 5.0, + "maintenanceMarginRate": 0.05, + "maxLeverage": 10.0, "info": { "bracket": "5", - "initialLeverage": "5", + "initialLeverage": "10", "notionalCap": "1000000", - "notionalFloor": "500000", - "maintMarginRatio": "0.1", - "cum": "26400.0" + "notionalFloor": "200000", + "maintMarginRatio": "0.05", + "cum": "5650.0" } }, { "tier": 6.0, "currency": "USDT", "minNotional": 1000000.0, - "maxNotional": 1250000.0, - "maintenanceMarginRate": 0.125, - "maxLeverage": 4.0, + "maxNotional": 2000000.0, + "maintenanceMarginRate": 0.1, + "maxLeverage": 5.0, "info": { "bracket": "6", - "initialLeverage": "4", - "notionalCap": "1250000", + "initialLeverage": "5", + "notionalCap": "2000000", "notionalFloor": "1000000", - "maintMarginRatio": "0.125", - "cum": "51400.0" + "maintMarginRatio": "0.1", + "cum": "55650.0" } }, { "tier": 7.0, "currency": "USDT", - "minNotional": 1250000.0, + "minNotional": 2000000.0, "maxNotional": 2500000.0, - "maintenanceMarginRate": 0.25, - "maxLeverage": 2.0, + "maintenanceMarginRate": 0.125, + "maxLeverage": 4.0, "info": { "bracket": "7", - "initialLeverage": "2", + "initialLeverage": "4", "notionalCap": "2500000", - "notionalFloor": "1250000", - "maintMarginRatio": "0.25", - "cum": "207650.0" + "notionalFloor": "2000000", + "maintMarginRatio": "0.125", + "cum": "105650.0" } }, { @@ -929,15 +1091,31 @@ "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": "8", + "bracket": "9", "initialLeverage": "1", - "notionalCap": "5000000", - "notionalFloor": "2500000", + "notionalCap": "10000000", + "notionalFloor": "5000000", "maintMarginRatio": "0.5", - "cum": "832650.0" + "cum": "1668150.0" } } ], @@ -1498,111 +1676,13 @@ "tier": 1.0, "currency": "USDT", "minNotional": 0.0, - "maxNotional": 5000.0, - "maintenanceMarginRate": 0.012, - "maxLeverage": 25.0, - "info": { - "bracket": "1", - "initialLeverage": "25", - "notionalCap": "5000", - "notionalFloor": "0", - "maintMarginRatio": "0.012", - "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": "65.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": "690.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": "5690.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": "11940.0" - } - }, - { - "tier": 6.0, - "currency": "USDT", - "minNotional": 1000000.0, - "maxNotional": 10000000.0, - "maintenanceMarginRate": 0.5, - "maxLeverage": 1.0, - "info": { - "bracket": "6", - "initialLeverage": "1", - "notionalCap": "10000000", - "notionalFloor": "1000000", - "maintMarginRatio": "0.5", - "cum": "386940.0" - } - } - ], - "1MBABYDOGE/USDT:USDT": [ - { - "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" @@ -1611,129 +1691,275 @@ { "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": 50000.0, + "minNotional": 20000.0, + "maxNotional": 100000.0, "maintenanceMarginRate": 0.02, "maxLeverage": 25.0, "info": { "bracket": "3", "initialLeverage": "25", - "notionalCap": "50000", - "notionalFloor": "10000", + "notionalCap": "100000", + "notionalFloor": "20000", "maintMarginRatio": "0.02", - "cum": "75.0" + "cum": "150.0" } }, { "tier": 4.0, "currency": "USDT", - "minNotional": 50000.0, - "maxNotional": 100000.0, + "minNotional": 100000.0, + "maxNotional": 200000.0, "maintenanceMarginRate": 0.025, "maxLeverage": 20.0, "info": { "bracket": "4", "initialLeverage": "20", - "notionalCap": "100000", - "notionalFloor": "50000", + "notionalCap": "200000", + "notionalFloor": "100000", "maintMarginRatio": "0.025", - "cum": "325.0" + "cum": "650.0" } }, { "tier": 5.0, "currency": "USDT", - "minNotional": 100000.0, - "maxNotional": 500000.0, + "minNotional": 200000.0, + "maxNotional": 1000000.0, "maintenanceMarginRate": 0.05, "maxLeverage": 10.0, "info": { "bracket": "5", "initialLeverage": "10", - "notionalCap": "500000", - "notionalFloor": "100000", + "notionalCap": "1000000", + "notionalFloor": "200000", "maintMarginRatio": "0.05", - "cum": "2825.0" + "cum": "5650.0" } }, { "tier": 6.0, "currency": "USDT", - "minNotional": 500000.0, - "maxNotional": 1000000.0, + "minNotional": 1000000.0, + "maxNotional": 2000000.0, "maintenanceMarginRate": 0.1, "maxLeverage": 5.0, "info": { "bracket": "6", "initialLeverage": "5", - "notionalCap": "1000000", - "notionalFloor": "500000", + "notionalCap": "2000000", + "notionalFloor": "1000000", "maintMarginRatio": "0.1", - "cum": "27825.0" + "cum": "55650.0" } }, { "tier": 7.0, "currency": "USDT", - "minNotional": 1000000.0, - "maxNotional": 1250000.0, + "minNotional": 2000000.0, + "maxNotional": 2500000.0, "maintenanceMarginRate": 0.125, "maxLeverage": 4.0, "info": { "bracket": "7", "initialLeverage": "4", - "notionalCap": "1250000", - "notionalFloor": "1000000", + "notionalCap": "2500000", + "notionalFloor": "2000000", "maintMarginRatio": "0.125", - "cum": "52825.0" + "cum": "105650.0" } }, { "tier": 8.0, "currency": "USDT", - "minNotional": 1250000.0, - "maxNotional": 2500000.0, + "minNotional": 2500000.0, + "maxNotional": 5000000.0, "maintenanceMarginRate": 0.25, "maxLeverage": 2.0, "info": { "bracket": "8", "initialLeverage": "2", - "notionalCap": "2500000", - "notionalFloor": "1250000", + "notionalCap": "5000000", + "notionalFloor": "2500000", "maintMarginRatio": "0.25", - "cum": "209075.0" + "cum": "418150.0" } }, { "tier": 9.0, "currency": "USDT", - "minNotional": 2500000.0, - "maxNotional": 5000000.0, + "minNotional": 5000000.0, + "maxNotional": 10000000.0, "maintenanceMarginRate": 0.5, "maxLeverage": 1.0, "info": { "bracket": "9", "initialLeverage": "1", - "notionalCap": "5000000", - "notionalFloor": "2500000", + "notionalCap": "10000000", + "notionalFloor": "5000000", "maintMarginRatio": "0.5", - "cum": "834075.0" + "cum": "1668150.0" + } + } + ], + "1MBABYDOGE/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": 30000.0, + "maintenanceMarginRate": 0.015, + "maxLeverage": 50.0, + "info": { + "bracket": "2", + "initialLeverage": "50", + "notionalCap": "30000", + "notionalFloor": "10000", + "maintMarginRatio": "0.015", + "cum": "50.0" + } + }, + { + "tier": 3.0, + "currency": "USDT", + "minNotional": 30000.0, + "maxNotional": 150000.0, + "maintenanceMarginRate": 0.02, + "maxLeverage": 25.0, + "info": { + "bracket": "3", + "initialLeverage": "25", + "notionalCap": "150000", + "notionalFloor": "30000", + "maintMarginRatio": "0.02", + "cum": "200.0" + } + }, + { + "tier": 4.0, + "currency": "USDT", + "minNotional": 150000.0, + "maxNotional": 300000.0, + "maintenanceMarginRate": 0.025, + "maxLeverage": 20.0, + "info": { + "bracket": "4", + "initialLeverage": "20", + "notionalCap": "300000", + "notionalFloor": "150000", + "maintMarginRatio": "0.025", + "cum": "950.0" + } + }, + { + "tier": 5.0, + "currency": "USDT", + "minNotional": 300000.0, + "maxNotional": 1500000.0, + "maintenanceMarginRate": 0.05, + "maxLeverage": 10.0, + "info": { + "bracket": "5", + "initialLeverage": "10", + "notionalCap": "1500000", + "notionalFloor": "300000", + "maintMarginRatio": "0.05", + "cum": "8450.0" + } + }, + { + "tier": 6.0, + "currency": "USDT", + "minNotional": 1500000.0, + "maxNotional": 3000000.0, + "maintenanceMarginRate": 0.1, + "maxLeverage": 5.0, + "info": { + "bracket": "6", + "initialLeverage": "5", + "notionalCap": "3000000", + "notionalFloor": "1500000", + "maintMarginRatio": "0.1", + "cum": "83450.0" + } + }, + { + "tier": 7.0, + "currency": "USDT", + "minNotional": 3000000.0, + "maxNotional": 3750000.0, + "maintenanceMarginRate": 0.125, + "maxLeverage": 4.0, + "info": { + "bracket": "7", + "initialLeverage": "4", + "notionalCap": "3750000", + "notionalFloor": "3000000", + "maintMarginRatio": "0.125", + "cum": "158450.0" + } + }, + { + "tier": 8.0, + "currency": "USDT", + "minNotional": 3750000.0, + "maxNotional": 7500000.0, + "maintenanceMarginRate": 0.25, + "maxLeverage": 2.0, + "info": { + "bracket": "8", + "initialLeverage": "2", + "notionalCap": "7500000", + "notionalFloor": "3750000", + "maintMarginRatio": "0.25", + "cum": "627200.0" + } + }, + { + "tier": 9.0, + "currency": "USDT", + "minNotional": 7500000.0, + "maxNotional": 15000000.0, + "maintenanceMarginRate": 0.5, + "maxLeverage": 1.0, + "info": { + "bracket": "9", + "initialLeverage": "1", + "notionalCap": "15000000", + "notionalFloor": "7500000", + "maintMarginRatio": "0.5", + "cum": "2502200.0" } } ], @@ -3465,14 +3691,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" } }, @@ -3480,112 +3706,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" } } ], @@ -3854,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" @@ -3869,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" } } ], @@ -4130,13 +4372,13 @@ "tier": 2.0, "currency": "USDT", "minNotional": 10000.0, - "maxNotional": 60000.0, + "maxNotional": 100000.0, "maintenanceMarginRate": 0.015, "maxLeverage": 50.0, "info": { "bracket": "2", "initialLeverage": "50", - "notionalCap": "60000", + "notionalCap": "100000", "notionalFloor": "10000", "maintMarginRatio": "0.015", "cum": "50.0" @@ -4145,7 +4387,7 @@ { "tier": 3.0, "currency": "USDT", - "minNotional": 60000.0, + "minNotional": 100000.0, "maxNotional": 900000.0, "maintenanceMarginRate": 0.02, "maxLeverage": 25.0, @@ -4153,9 +4395,9 @@ "bracket": "3", "initialLeverage": "25", "notionalCap": "900000", - "notionalFloor": "60000", + "notionalFloor": "100000", "maintMarginRatio": "0.02", - "cum": "350.0" + "cum": "550.0" } }, { @@ -4171,87 +4413,87 @@ "notionalCap": "1100000", "notionalFloor": "900000", "maintMarginRatio": "0.025", - "cum": "4850.0" + "cum": "5050.0" } }, { "tier": 5.0, "currency": "USDT", "minNotional": 1100000.0, - "maxNotional": 3000000.0, + "maxNotional": 5000000.0, "maintenanceMarginRate": 0.05, "maxLeverage": 10.0, "info": { "bracket": "5", "initialLeverage": "10", - "notionalCap": "3000000", + "notionalCap": "5000000", "notionalFloor": "1100000", "maintMarginRatio": "0.05", - "cum": "32350.0" + "cum": "32550.0" } }, { "tier": 6.0, "currency": "USDT", - "minNotional": 3000000.0, - "maxNotional": 6000000.0, + "minNotional": 5000000.0, + "maxNotional": 10000000.0, "maintenanceMarginRate": 0.1, "maxLeverage": 5.0, "info": { "bracket": "6", "initialLeverage": "5", - "notionalCap": "6000000", - "notionalFloor": "3000000", + "notionalCap": "10000000", + "notionalFloor": "5000000", "maintMarginRatio": "0.1", - "cum": "182350.0" + "cum": "282550.0" } }, { "tier": 7.0, "currency": "USDT", - "minNotional": 6000000.0, - "maxNotional": 7500000.0, + "minNotional": 10000000.0, + "maxNotional": 12500000.0, "maintenanceMarginRate": 0.125, "maxLeverage": 4.0, "info": { "bracket": "7", "initialLeverage": "4", - "notionalCap": "7500000", - "notionalFloor": "6000000", + "notionalCap": "12500000", + "notionalFloor": "10000000", "maintMarginRatio": "0.125", - "cum": "332350.0" + "cum": "532550.0" } }, { "tier": 8.0, "currency": "USDT", - "minNotional": 7500000.0, - "maxNotional": 18000000.0, + "minNotional": 12500000.0, + "maxNotional": 25000000.0, "maintenanceMarginRate": 0.25, "maxLeverage": 2.0, "info": { "bracket": "8", "initialLeverage": "2", - "notionalCap": "18000000", - "notionalFloor": "7500000", + "notionalCap": "25000000", + "notionalFloor": "12500000", "maintMarginRatio": "0.25", - "cum": "1269850.0" + "cum": "2095050.0" } }, { "tier": 9.0, "currency": "USDT", - "minNotional": 18000000.0, - "maxNotional": 30000000.0, + "minNotional": 25000000.0, + "maxNotional": 50000000.0, "maintenanceMarginRate": 0.5, "maxLeverage": 1.0, "info": { "bracket": "9", "initialLeverage": "1", - "notionalCap": "30000000", - "notionalFloor": "18000000", + "notionalCap": "50000000", + "notionalFloor": "25000000", "maintMarginRatio": "0.5", - "cum": "5769850.0" + "cum": "8345050.0" } } ], @@ -4260,128 +4502,144 @@ "tier": 1.0, "currency": "USDT", "minNotional": 0.0, - "maxNotional": 5000.0, - "maintenanceMarginRate": 0.015, - "maxLeverage": 50.0, + "maxNotional": 10000.0, + "maintenanceMarginRate": 0.01, + "maxLeverage": 75.0, "info": { "bracket": "1", - "initialLeverage": "50", - "notionalCap": "5000", + "initialLeverage": "75", + "notionalCap": "10000", "notionalFloor": "0", - "maintMarginRatio": "0.015", + "maintMarginRatio": "0.01", "cum": "0.0" } }, { "tier": 2.0, "currency": "USDT", - "minNotional": 5000.0, - "maxNotional": 25000.0, - "maintenanceMarginRate": 0.02, - "maxLeverage": 25.0, + "minNotional": 10000.0, + "maxNotional": 20000.0, + "maintenanceMarginRate": 0.015, + "maxLeverage": 50.0, "info": { "bracket": "2", - "initialLeverage": "25", - "notionalCap": "25000", - "notionalFloor": "5000", - "maintMarginRatio": "0.02", - "cum": "25.0" + "initialLeverage": "50", + "notionalCap": "20000", + "notionalFloor": "10000", + "maintMarginRatio": "0.015", + "cum": "50.0" } }, { "tier": 3.0, "currency": "USDT", - "minNotional": 25000.0, - "maxNotional": 80000.0, - "maintenanceMarginRate": 0.025, - "maxLeverage": 20.0, + "minNotional": 20000.0, + "maxNotional": 100000.0, + "maintenanceMarginRate": 0.02, + "maxLeverage": 25.0, "info": { "bracket": "3", - "initialLeverage": "20", - "notionalCap": "80000", - "notionalFloor": "25000", - "maintMarginRatio": "0.025", + "initialLeverage": "25", + "notionalCap": "100000", + "notionalFloor": "20000", + "maintMarginRatio": "0.02", "cum": "150.0" } }, { "tier": 4.0, "currency": "USDT", - "minNotional": 80000.0, - "maxNotional": 800000.0, - "maintenanceMarginRate": 0.05, - "maxLeverage": 10.0, + "minNotional": 100000.0, + "maxNotional": 200000.0, + "maintenanceMarginRate": 0.025, + "maxLeverage": 20.0, "info": { "bracket": "4", - "initialLeverage": "10", - "notionalCap": "800000", - "notionalFloor": "80000", - "maintMarginRatio": "0.05", - "cum": "2150.0" + "initialLeverage": "20", + "notionalCap": "200000", + "notionalFloor": "100000", + "maintMarginRatio": "0.025", + "cum": "650.0" } }, { "tier": 5.0, "currency": "USDT", - "minNotional": 800000.0, - "maxNotional": 1600000.0, - "maintenanceMarginRate": 0.1, - "maxLeverage": 5.0, + "minNotional": 200000.0, + "maxNotional": 1000000.0, + "maintenanceMarginRate": 0.05, + "maxLeverage": 10.0, "info": { "bracket": "5", - "initialLeverage": "5", - "notionalCap": "1600000", - "notionalFloor": "800000", - "maintMarginRatio": "0.1", - "cum": "42150.0" + "initialLeverage": "10", + "notionalCap": "1000000", + "notionalFloor": "200000", + "maintMarginRatio": "0.05", + "cum": "5650.0" } }, { "tier": 6.0, "currency": "USDT", - "minNotional": 1600000.0, + "minNotional": 1000000.0, "maxNotional": 2000000.0, - "maintenanceMarginRate": 0.125, - "maxLeverage": 4.0, + "maintenanceMarginRate": 0.1, + "maxLeverage": 5.0, "info": { "bracket": "6", - "initialLeverage": "4", + "initialLeverage": "5", "notionalCap": "2000000", - "notionalFloor": "1600000", - "maintMarginRatio": "0.125", - "cum": "82150.0" + "notionalFloor": "1000000", + "maintMarginRatio": "0.1", + "cum": "55650.0" } }, { "tier": 7.0, "currency": "USDT", "minNotional": 2000000.0, - "maxNotional": 4000000.0, - "maintenanceMarginRate": 0.25, - "maxLeverage": 2.0, + "maxNotional": 2500000.0, + "maintenanceMarginRate": 0.125, + "maxLeverage": 4.0, "info": { "bracket": "7", - "initialLeverage": "2", - "notionalCap": "4000000", + "initialLeverage": "4", + "notionalCap": "2500000", "notionalFloor": "2000000", - "maintMarginRatio": "0.25", - "cum": "332150.0" + "maintMarginRatio": "0.125", + "cum": "105650.0" } }, { "tier": 8.0, "currency": "USDT", - "minNotional": 4000000.0, - "maxNotional": 8000000.0, + "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": "8", + "bracket": "9", "initialLeverage": "1", - "notionalCap": "8000000", - "notionalFloor": "4000000", + "notionalCap": "10000000", + "notionalFloor": "5000000", "maintMarginRatio": "0.5", - "cum": "1332150.0" + "cum": "1668150.0" } } ], @@ -4522,10 +4780,10 @@ "minNotional": 0.0, "maxNotional": 5000.0, "maintenanceMarginRate": 0.006, - "maxLeverage": 50.0, + "maxLeverage": 75.0, "info": { "bracket": "1", - "initialLeverage": "50", + "initialLeverage": "75", "notionalCap": "5000", "notionalFloor": "0", "maintMarginRatio": "0.006", @@ -4538,10 +4796,10 @@ "minNotional": 5000.0, "maxNotional": 50000.0, "maintenanceMarginRate": 0.01, - "maxLeverage": 25.0, + "maxLeverage": 50.0, "info": { "bracket": "2", - "initialLeverage": "25", + "initialLeverage": "50", "notionalCap": "50000", "notionalFloor": "5000", "maintMarginRatio": "0.01", @@ -4552,96 +4810,128 @@ "tier": 3.0, "currency": "USDT", "minNotional": 50000.0, - "maxNotional": 600000.0, - "maintenanceMarginRate": 0.025, - "maxLeverage": 20.0, + "maxNotional": 80000.0, + "maintenanceMarginRate": 0.015, + "maxLeverage": 40.0, "info": { "bracket": "3", - "initialLeverage": "20", - "notionalCap": "600000", + "initialLeverage": "40", + "notionalCap": "80000", "notionalFloor": "50000", - "maintMarginRatio": "0.025", - "cum": "770.0" + "maintMarginRatio": "0.015", + "cum": "270.0" } }, { "tier": 4.0, "currency": "USDT", - "minNotional": 600000.0, - "maxNotional": 1200000.0, - "maintenanceMarginRate": 0.05, - "maxLeverage": 10.0, + "minNotional": 80000.0, + "maxNotional": 300000.0, + "maintenanceMarginRate": 0.02, + "maxLeverage": 25.0, "info": { "bracket": "4", - "initialLeverage": "10", - "notionalCap": "1200000", - "notionalFloor": "600000", - "maintMarginRatio": "0.05", - "cum": "15770.0" + "initialLeverage": "25", + "notionalCap": "300000", + "notionalFloor": "80000", + "maintMarginRatio": "0.02", + "cum": "670.0" } }, { "tier": 5.0, "currency": "USDT", - "minNotional": 1200000.0, - "maxNotional": 3000000.0, - "maintenanceMarginRate": 0.1, - "maxLeverage": 5.0, + "minNotional": 300000.0, + "maxNotional": 600000.0, + "maintenanceMarginRate": 0.025, + "maxLeverage": 20.0, "info": { "bracket": "5", - "initialLeverage": "5", - "notionalCap": "3000000", - "notionalFloor": "1200000", - "maintMarginRatio": "0.1", - "cum": "75770.0" + "initialLeverage": "20", + "notionalCap": "600000", + "notionalFloor": "300000", + "maintMarginRatio": "0.025", + "cum": "2170.0" } }, { "tier": 6.0, "currency": "USDT", - "minNotional": 3000000.0, - "maxNotional": 5000000.0, - "maintenanceMarginRate": 0.125, - "maxLeverage": 4.0, + "minNotional": 600000.0, + "maxNotional": 3000000.0, + "maintenanceMarginRate": 0.05, + "maxLeverage": 10.0, "info": { "bracket": "6", - "initialLeverage": "4", - "notionalCap": "5000000", - "notionalFloor": "3000000", - "maintMarginRatio": "0.125", - "cum": "150770.0" + "initialLeverage": "10", + "notionalCap": "3000000", + "notionalFloor": "600000", + "maintMarginRatio": "0.05", + "cum": "17170.0" } }, { "tier": 7.0, "currency": "USDT", - "minNotional": 5000000.0, - "maxNotional": 12000000.0, - "maintenanceMarginRate": 0.25, - "maxLeverage": 2.0, + "minNotional": 3000000.0, + "maxNotional": 6000000.0, + "maintenanceMarginRate": 0.1, + "maxLeverage": 5.0, "info": { "bracket": "7", - "initialLeverage": "2", - "notionalCap": "12000000", - "notionalFloor": "5000000", - "maintMarginRatio": "0.25", - "cum": "775770.0" + "initialLeverage": "5", + "notionalCap": "6000000", + "notionalFloor": "3000000", + "maintMarginRatio": "0.1", + "cum": "167170.0" } }, { "tier": 8.0, "currency": "USDT", - "minNotional": 12000000.0, - "maxNotional": 20000000.0, + "minNotional": 6000000.0, + "maxNotional": 7500000.0, + "maintenanceMarginRate": 0.125, + "maxLeverage": 4.0, + "info": { + "bracket": "8", + "initialLeverage": "4", + "notionalCap": "7500000", + "notionalFloor": "6000000", + "maintMarginRatio": "0.125", + "cum": "317170.0" + } + }, + { + "tier": 9.0, + "currency": "USDT", + "minNotional": 7500000.0, + "maxNotional": 15000000.0, + "maintenanceMarginRate": 0.25, + "maxLeverage": 2.0, + "info": { + "bracket": "9", + "initialLeverage": "2", + "notionalCap": "15000000", + "notionalFloor": "7500000", + "maintMarginRatio": "0.25", + "cum": "1254670.0" + } + }, + { + "tier": 10.0, + "currency": "USDT", + "minNotional": 15000000.0, + "maxNotional": 30000000.0, "maintenanceMarginRate": 0.5, "maxLeverage": 1.0, "info": { - "bracket": "8", + "bracket": "10", "initialLeverage": "1", - "notionalCap": "20000000", - "notionalFloor": "12000000", + "notionalCap": "30000000", + "notionalFloor": "15000000", "maintMarginRatio": "0.5", - "cum": "3775770.0" + "cum": "5004670.0" } } ], @@ -4650,96 +4940,144 @@ "tier": 1.0, "currency": "USDT", "minNotional": 0.0, - "maxNotional": 10000.0, - "maintenanceMarginRate": 0.03, - "maxLeverage": 20.0, + "maxNotional": 5000.0, + "maintenanceMarginRate": 0.01, + "maxLeverage": 75.0, "info": { "bracket": "1", - "initialLeverage": "20", - "notionalCap": "10000", + "initialLeverage": "75", + "notionalCap": "5000", "notionalFloor": "0", - "maintMarginRatio": "0.03", + "maintMarginRatio": "0.01", "cum": "0.0" } }, { "tier": 2.0, "currency": "USDT", - "minNotional": 10000.0, - "maxNotional": 200000.0, - "maintenanceMarginRate": 0.05, - "maxLeverage": 10.0, + "minNotional": 5000.0, + "maxNotional": 10000.0, + "maintenanceMarginRate": 0.015, + "maxLeverage": 50.0, "info": { "bracket": "2", - "initialLeverage": "10", - "notionalCap": "200000", - "notionalFloor": "10000", - "maintMarginRatio": "0.05", - "cum": "200.0" + "initialLeverage": "50", + "notionalCap": "10000", + "notionalFloor": "5000", + "maintMarginRatio": "0.015", + "cum": "25.0" } }, { "tier": 3.0, "currency": "USDT", + "minNotional": 10000.0, + "maxNotional": 20000.0, + "maintenanceMarginRate": 0.02, + "maxLeverage": 25.0, + "info": { + "bracket": "3", + "initialLeverage": "25", + "notionalCap": "20000", + "notionalFloor": "10000", + "maintMarginRatio": "0.02", + "cum": "75.0" + } + }, + { + "tier": 4.0, + "currency": "USDT", + "minNotional": 20000.0, + "maxNotional": 40000.0, + "maintenanceMarginRate": 0.025, + "maxLeverage": 20.0, + "info": { + "bracket": "4", + "initialLeverage": "20", + "notionalCap": "40000", + "notionalFloor": "20000", + "maintMarginRatio": "0.025", + "cum": "175.0" + } + }, + { + "tier": 5.0, + "currency": "USDT", + "minNotional": 40000.0, + "maxNotional": 200000.0, + "maintenanceMarginRate": 0.05, + "maxLeverage": 10.0, + "info": { + "bracket": "5", + "initialLeverage": "10", + "notionalCap": "200000", + "notionalFloor": "40000", + "maintMarginRatio": "0.05", + "cum": "1175.0" + } + }, + { + "tier": 6.0, + "currency": "USDT", "minNotional": 200000.0, "maxNotional": 500000.0, "maintenanceMarginRate": 0.1, "maxLeverage": 5.0, "info": { - "bracket": "3", + "bracket": "6", "initialLeverage": "5", "notionalCap": "500000", "notionalFloor": "200000", "maintMarginRatio": "0.1", - "cum": "10200.0" + "cum": "11175.0" } }, { - "tier": 4.0, + "tier": 7.0, "currency": "USDT", "minNotional": 500000.0, "maxNotional": 1000000.0, "maintenanceMarginRate": 0.125, "maxLeverage": 4.0, "info": { - "bracket": "4", + "bracket": "7", "initialLeverage": "4", "notionalCap": "1000000", "notionalFloor": "500000", "maintMarginRatio": "0.125", - "cum": "22700.0" + "cum": "23675.0" } }, { - "tier": 5.0, + "tier": 8.0, "currency": "USDT", "minNotional": 1000000.0, "maxNotional": 3000000.0, "maintenanceMarginRate": 0.25, "maxLeverage": 2.0, "info": { - "bracket": "5", + "bracket": "8", "initialLeverage": "2", "notionalCap": "3000000", "notionalFloor": "1000000", "maintMarginRatio": "0.25", - "cum": "147700.0" + "cum": "148675.0" } }, { - "tier": 6.0, + "tier": 9.0, "currency": "USDT", "minNotional": 3000000.0, - "maxNotional": 3500000.0, + "maxNotional": 5000000.0, "maintenanceMarginRate": 0.5, "maxLeverage": 1.0, "info": { - "bracket": "6", + "bracket": "9", "initialLeverage": "1", - "notionalCap": "3500000", + "notionalCap": "5000000", "notionalFloor": "3000000", "maintMarginRatio": "0.5", - "cum": "897700.0" + "cum": "898675.0" } } ], @@ -4748,112 +5086,144 @@ "tier": 1.0, "currency": "USDT", "minNotional": 0.0, - "maxNotional": 5000.0, - "maintenanceMarginRate": 0.02, - "maxLeverage": 20.0, + "maxNotional": 10000.0, + "maintenanceMarginRate": 0.01, + "maxLeverage": 75.0, "info": { "bracket": "1", - "initialLeverage": "20", - "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": 15.0, + "minNotional": 10000.0, + "maxNotional": 20000.0, + "maintenanceMarginRate": 0.015, + "maxLeverage": 50.0, "info": { "bracket": "2", - "initialLeverage": "15", - "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": 200000.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": "200000", - "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": 200000.0, - "maxNotional": 500000.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": "500000", - "notionalFloor": "200000", - "maintMarginRatio": "0.1", - "cum": "10650.0" + "initialLeverage": "20", + "notionalCap": "200000", + "notionalFloor": "100000", + "maintMarginRatio": "0.025", + "cum": "650.0" } }, { "tier": 5.0, "currency": "USDT", - "minNotional": 500000.0, + "minNotional": 200000.0, "maxNotional": 1000000.0, - "maintenanceMarginRate": 0.125, - "maxLeverage": 4.0, + "maintenanceMarginRate": 0.05, + "maxLeverage": 10.0, "info": { "bracket": "5", - "initialLeverage": "4", + "initialLeverage": "10", "notionalCap": "1000000", - "notionalFloor": "500000", - "maintMarginRatio": "0.125", - "cum": "23150.0" + "notionalFloor": "200000", + "maintMarginRatio": "0.05", + "cum": "5650.0" } }, { "tier": 6.0, "currency": "USDT", "minNotional": 1000000.0, - "maxNotional": 3000000.0, - "maintenanceMarginRate": 0.25, - "maxLeverage": 2.0, + "maxNotional": 2000000.0, + "maintenanceMarginRate": 0.1, + "maxLeverage": 5.0, "info": { "bracket": "6", - "initialLeverage": "2", - "notionalCap": "3000000", + "initialLeverage": "5", + "notionalCap": "2000000", "notionalFloor": "1000000", - "maintMarginRatio": "0.25", - "cum": "148150.0" + "maintMarginRatio": "0.1", + "cum": "55650.0" } }, { "tier": 7.0, "currency": "USDT", - "minNotional": 3000000.0, + "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": "7", + "bracket": "9", "initialLeverage": "1", - "notionalCap": "5000000", - "notionalFloor": "3000000", + "notionalCap": "10000000", + "notionalFloor": "5000000", "maintMarginRatio": "0.5", - "cum": "898150.0" + "cum": "1668150.0" } } ], @@ -5236,112 +5606,128 @@ "tier": 3.0, "currency": "USDT", "minNotional": 50000.0, - "maxNotional": 200000.0, - "maintenanceMarginRate": 0.02, - "maxLeverage": 25.0, + "maxNotional": 80000.0, + "maintenanceMarginRate": 0.015, + "maxLeverage": 40.0, "info": { "bracket": "3", - "initialLeverage": "25", - "notionalCap": "200000", + "initialLeverage": "40", + "notionalCap": "80000", "notionalFloor": "50000", - "maintMarginRatio": "0.02", - "cum": "540.0" + "maintMarginRatio": "0.015", + "cum": "290.0" } }, { "tier": 4.0, "currency": "USDT", + "minNotional": 80000.0, + "maxNotional": 200000.0, + "maintenanceMarginRate": 0.02, + "maxLeverage": 25.0, + "info": { + "bracket": "4", + "initialLeverage": "25", + "notionalCap": "200000", + "notionalFloor": "80000", + "maintMarginRatio": "0.02", + "cum": "690.0" + } + }, + { + "tier": 5.0, + "currency": "USDT", "minNotional": 200000.0, "maxNotional": 600000.0, "maintenanceMarginRate": 0.025, "maxLeverage": 20.0, "info": { - "bracket": "4", + "bracket": "5", "initialLeverage": "20", "notionalCap": "600000", "notionalFloor": "200000", "maintMarginRatio": "0.025", - "cum": "1540.0" - } - }, - { - "tier": 5.0, - "currency": "USDT", - "minNotional": 600000.0, - "maxNotional": 1200000.0, - "maintenanceMarginRate": 0.05, - "maxLeverage": 10.0, - "info": { - "bracket": "5", - "initialLeverage": "10", - "notionalCap": "1200000", - "notionalFloor": "600000", - "maintMarginRatio": "0.05", - "cum": "16540.0" + "cum": "1690.0" } }, { "tier": 6.0, "currency": "USDT", - "minNotional": 1200000.0, - "maxNotional": 3200000.0, - "maintenanceMarginRate": 0.1, - "maxLeverage": 5.0, + "minNotional": 600000.0, + "maxNotional": 2000000.0, + "maintenanceMarginRate": 0.05, + "maxLeverage": 10.0, "info": { "bracket": "6", - "initialLeverage": "5", - "notionalCap": "3200000", - "notionalFloor": "1200000", - "maintMarginRatio": "0.1", - "cum": "76540.0" + "initialLeverage": "10", + "notionalCap": "2000000", + "notionalFloor": "600000", + "maintMarginRatio": "0.05", + "cum": "16690.0" } }, { "tier": 7.0, "currency": "USDT", - "minNotional": 3200000.0, - "maxNotional": 5000000.0, - "maintenanceMarginRate": 0.125, - "maxLeverage": 4.0, + "minNotional": 2000000.0, + "maxNotional": 4000000.0, + "maintenanceMarginRate": 0.1, + "maxLeverage": 5.0, "info": { "bracket": "7", - "initialLeverage": "4", - "notionalCap": "5000000", - "notionalFloor": "3200000", - "maintMarginRatio": "0.125", - "cum": "156540.0" + "initialLeverage": "5", + "notionalCap": "4000000", + "notionalFloor": "2000000", + "maintMarginRatio": "0.1", + "cum": "116690.0" } }, { "tier": 8.0, "currency": "USDT", + "minNotional": 4000000.0, + "maxNotional": 5000000.0, + "maintenanceMarginRate": 0.125, + "maxLeverage": 4.0, + "info": { + "bracket": "8", + "initialLeverage": "4", + "notionalCap": "5000000", + "notionalFloor": "4000000", + "maintMarginRatio": "0.125", + "cum": "216690.0" + } + }, + { + "tier": 9.0, + "currency": "USDT", "minNotional": 5000000.0, "maxNotional": 12000000.0, "maintenanceMarginRate": 0.25, "maxLeverage": 2.0, "info": { - "bracket": "8", + "bracket": "9", "initialLeverage": "2", "notionalCap": "12000000", "notionalFloor": "5000000", "maintMarginRatio": "0.25", - "cum": "781540.0" + "cum": "841690.0" } }, { - "tier": 9.0, + "tier": 10.0, "currency": "USDT", "minNotional": 12000000.0, "maxNotional": 20000000.0, "maintenanceMarginRate": 0.5, "maxLeverage": 1.0, "info": { - "bracket": "9", + "bracket": "10", "initialLeverage": "1", "notionalCap": "20000000", "notionalFloor": "12000000", "maintMarginRatio": "0.5", - "cum": "3781540.0" + "cum": "3841690.0" } } ], @@ -5805,14 +6191,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" } }, @@ -5820,96 +6206,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": 100000.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": "100000", - "notionalFloor": "25000", - "maintMarginRatio": "0.05", - "cum": "675.0" + "initialLeverage": "25", + "notionalCap": "30000", + "notionalFloor": "10000", + "maintMarginRatio": "0.02", + "cum": "75.0" } }, { "tier": 4.0, "currency": "USDT", - "minNotional": 100000.0, - "maxNotional": 200000.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": "200000", - "notionalFloor": "100000", - "maintMarginRatio": "0.1", - "cum": "5675.0" + "initialLeverage": "20", + "notionalCap": "60000", + "notionalFloor": "30000", + "maintMarginRatio": "0.025", + "cum": "225.0" } }, { "tier": 5.0, "currency": "USDT", - "minNotional": 200000.0, - "maxNotional": 500000.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": "500000", - "notionalFloor": "200000", - "maintMarginRatio": "0.125", - "cum": "10675.0" + "initialLeverage": "10", + "notionalCap": "300000", + "notionalFloor": "60000", + "maintMarginRatio": "0.05", + "cum": "1725.0" } }, { "tier": 6.0, "currency": "USDT", - "minNotional": 500000.0, - "maxNotional": 1000000.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": "1000000", - "notionalFloor": "500000", - "maintMarginRatio": "0.25", - "cum": "73175.0" + "initialLeverage": "5", + "notionalCap": "600000", + "notionalFloor": "300000", + "maintMarginRatio": "0.1", + "cum": "16725.0" } }, { "tier": 7.0, "currency": "USDT", - "minNotional": 1000000.0, - "maxNotional": 2000000.0, + "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": "7", + "bracket": "9", "initialLeverage": "1", - "notionalCap": "2000000", - "notionalFloor": "1000000", + "notionalCap": "3000000", + "notionalFloor": "1500000", "maintMarginRatio": "0.5", - "cum": "323175.0" + "cum": "500475.0" } } ], @@ -6407,14 +6825,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" } }, @@ -6422,112 +6840,128 @@ "tier": 2.0, "currency": "USDT", "minNotional": 5000.0, - "maxNotional": 20000.0, - "maintenanceMarginRate": 0.02, - "maxLeverage": 25.0, + "maxNotional": 10000.0, + "maintenanceMarginRate": 0.015, + "maxLeverage": 50.0, "info": { "bracket": "2", - "initialLeverage": "25", - "notionalCap": "20000", + "initialLeverage": "50", + "notionalCap": "10000", "notionalFloor": "5000", - "maintMarginRatio": "0.02", + "maintMarginRatio": "0.015", "cum": "25.0" } }, { "tier": 3.0, "currency": "USDT", - "minNotional": 20000.0, - "maxNotional": 25000.0, - "maintenanceMarginRate": 0.025, - "maxLeverage": 20.0, + "minNotional": 10000.0, + "maxNotional": 50000.0, + "maintenanceMarginRate": 0.02, + "maxLeverage": 25.0, "info": { "bracket": "3", - "initialLeverage": "20", - "notionalCap": "25000", - "notionalFloor": "20000", - "maintMarginRatio": "0.025", - "cum": "125.0" + "initialLeverage": "25", + "notionalCap": "50000", + "notionalFloor": "10000", + "maintMarginRatio": "0.02", + "cum": "75.0" } }, { "tier": 4.0, "currency": "USDT", - "minNotional": 25000.0, - "maxNotional": 200000.0, - "maintenanceMarginRate": 0.05, - "maxLeverage": 10.0, + "minNotional": 50000.0, + "maxNotional": 100000.0, + "maintenanceMarginRate": 0.025, + "maxLeverage": 20.0, "info": { "bracket": "4", - "initialLeverage": "10", - "notionalCap": "200000", - "notionalFloor": "25000", - "maintMarginRatio": "0.05", - "cum": "750.0" + "initialLeverage": "20", + "notionalCap": "100000", + "notionalFloor": "50000", + "maintMarginRatio": "0.025", + "cum": "325.0" } }, { "tier": 5.0, "currency": "USDT", - "minNotional": 200000.0, - "maxNotional": 400000.0, - "maintenanceMarginRate": 0.1, - "maxLeverage": 5.0, + "minNotional": 100000.0, + "maxNotional": 500000.0, + "maintenanceMarginRate": 0.05, + "maxLeverage": 10.0, "info": { "bracket": "5", - "initialLeverage": "5", - "notionalCap": "400000", - "notionalFloor": "200000", - "maintMarginRatio": "0.1", - "cum": "10750.0" + "initialLeverage": "10", + "notionalCap": "500000", + "notionalFloor": "100000", + "maintMarginRatio": "0.05", + "cum": "2825.0" } }, { "tier": 6.0, "currency": "USDT", - "minNotional": 400000.0, - "maxNotional": 500000.0, - "maintenanceMarginRate": 0.125, - "maxLeverage": 4.0, + "minNotional": 500000.0, + "maxNotional": 1000000.0, + "maintenanceMarginRate": 0.1, + "maxLeverage": 5.0, "info": { "bracket": "6", - "initialLeverage": "4", - "notionalCap": "500000", - "notionalFloor": "400000", - "maintMarginRatio": "0.125", - "cum": "20750.0" + "initialLeverage": "5", + "notionalCap": "1000000", + "notionalFloor": "500000", + "maintMarginRatio": "0.1", + "cum": "27825.0" } }, { "tier": 7.0, "currency": "USDT", - "minNotional": 500000.0, - "maxNotional": 1000000.0, - "maintenanceMarginRate": 0.25, - "maxLeverage": 2.0, + "minNotional": 1000000.0, + "maxNotional": 1250000.0, + "maintenanceMarginRate": 0.125, + "maxLeverage": 4.0, "info": { "bracket": "7", - "initialLeverage": "2", - "notionalCap": "1000000", - "notionalFloor": "500000", - "maintMarginRatio": "0.25", - "cum": "83250.0" + "initialLeverage": "4", + "notionalCap": "1250000", + "notionalFloor": "1000000", + "maintMarginRatio": "0.125", + "cum": "52825.0" } }, { "tier": 8.0, "currency": "USDT", - "minNotional": 1000000.0, - "maxNotional": 2000000.0, + "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": "8", + "bracket": "9", "initialLeverage": "1", - "notionalCap": "2000000", - "notionalFloor": "1000000", + "notionalCap": "5000000", + "notionalFloor": "2500000", "maintMarginRatio": "0.5", - "cum": "333250.0" + "cum": "834075.0" } } ], @@ -7251,14 +7685,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" } }, @@ -7266,96 +7700,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": 100000.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": "100000", - "notionalFloor": "25000", - "maintMarginRatio": "0.05", - "cum": "675.0" + "initialLeverage": "25", + "notionalCap": "30000", + "notionalFloor": "10000", + "maintMarginRatio": "0.02", + "cum": "75.0" } }, { "tier": 4.0, "currency": "USDT", - "minNotional": 100000.0, - "maxNotional": 200000.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": "200000", - "notionalFloor": "100000", - "maintMarginRatio": "0.1", - "cum": "5675.0" + "initialLeverage": "20", + "notionalCap": "60000", + "notionalFloor": "30000", + "maintMarginRatio": "0.025", + "cum": "225.0" } }, { "tier": 5.0, "currency": "USDT", - "minNotional": 200000.0, - "maxNotional": 500000.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": "500000", - "notionalFloor": "200000", - "maintMarginRatio": "0.125", - "cum": "10675.0" + "initialLeverage": "10", + "notionalCap": "300000", + "notionalFloor": "60000", + "maintMarginRatio": "0.05", + "cum": "1725.0" } }, { "tier": 6.0, "currency": "USDT", - "minNotional": 500000.0, - "maxNotional": 1000000.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": "1000000", - "notionalFloor": "500000", - "maintMarginRatio": "0.25", - "cum": "73175.0" + "initialLeverage": "5", + "notionalCap": "600000", + "notionalFloor": "300000", + "maintMarginRatio": "0.1", + "cum": "16725.0" } }, { "tier": 7.0, "currency": "USDT", - "minNotional": 1000000.0, - "maxNotional": 2000000.0, + "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": "7", + "bracket": "9", "initialLeverage": "1", - "notionalCap": "2000000", - "notionalFloor": "1000000", + "notionalCap": "3000000", + "notionalFloor": "1500000", "maintMarginRatio": "0.5", - "cum": "323175.0" + "cum": "500475.0" } } ], @@ -7592,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" @@ -7607,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" } } ], @@ -8160,13 +8626,13 @@ "tier": 4.0, "currency": "USDT", "minNotional": 200000.0, - "maxNotional": 1000000.0, + "maxNotional": 1500000.0, "maintenanceMarginRate": 0.02, "maxLeverage": 25.0, "info": { "bracket": "4", "initialLeverage": "25", - "notionalCap": "1000000", + "notionalCap": "1500000", "notionalFloor": "200000", "maintMarginRatio": "0.02", "cum": "2210.0" @@ -8175,97 +8641,97 @@ { "tier": 5.0, "currency": "USDT", - "minNotional": 1000000.0, - "maxNotional": 2000000.0, + "minNotional": 1500000.0, + "maxNotional": 3000000.0, "maintenanceMarginRate": 0.025, "maxLeverage": 20.0, "info": { "bracket": "5", "initialLeverage": "20", - "notionalCap": "2000000", - "notionalFloor": "1000000", + "notionalCap": "3000000", + "notionalFloor": "1500000", "maintMarginRatio": "0.025", - "cum": "7210.0" + "cum": "9710.0" } }, { "tier": 6.0, "currency": "USDT", - "minNotional": 2000000.0, - "maxNotional": 10000000.0, + "minNotional": 3000000.0, + "maxNotional": 15000000.0, "maintenanceMarginRate": 0.05, "maxLeverage": 10.0, "info": { "bracket": "6", "initialLeverage": "10", - "notionalCap": "10000000", - "notionalFloor": "2000000", + "notionalCap": "15000000", + "notionalFloor": "3000000", "maintMarginRatio": "0.05", - "cum": "57210.0" + "cum": "84710.0" } }, { "tier": 7.0, "currency": "USDT", - "minNotional": 10000000.0, - "maxNotional": 20000000.0, + "minNotional": 15000000.0, + "maxNotional": 30000000.0, "maintenanceMarginRate": 0.1, "maxLeverage": 5.0, "info": { "bracket": "7", "initialLeverage": "5", - "notionalCap": "20000000", - "notionalFloor": "10000000", + "notionalCap": "30000000", + "notionalFloor": "15000000", "maintMarginRatio": "0.1", - "cum": "557210.0" + "cum": "834710.0" } }, { "tier": 8.0, "currency": "USDT", - "minNotional": 20000000.0, - "maxNotional": 25000000.0, + "minNotional": 30000000.0, + "maxNotional": 37500000.0, "maintenanceMarginRate": 0.125, "maxLeverage": 4.0, "info": { "bracket": "8", "initialLeverage": "4", - "notionalCap": "25000000", - "notionalFloor": "20000000", + "notionalCap": "37500000", + "notionalFloor": "30000000", "maintMarginRatio": "0.125", - "cum": "1057210.0" + "cum": "1584710.0" } }, { "tier": 9.0, "currency": "USDT", - "minNotional": 25000000.0, - "maxNotional": 50000000.0, + "minNotional": 37500000.0, + "maxNotional": 75000000.0, "maintenanceMarginRate": 0.25, "maxLeverage": 2.0, "info": { "bracket": "9", "initialLeverage": "2", - "notionalCap": "50000000", - "notionalFloor": "25000000", + "notionalCap": "75000000", + "notionalFloor": "37500000", "maintMarginRatio": "0.25", - "cum": "4182210.0" + "cum": "6272210.0" } }, { "tier": 10.0, "currency": "USDT", - "minNotional": 50000000.0, - "maxNotional": 100000000.0, + "minNotional": 75000000.0, + "maxNotional": 150000000.0, "maintenanceMarginRate": 0.5, "maxLeverage": 1.0, "info": { "bracket": "10", "initialLeverage": "1", - "notionalCap": "100000000", - "notionalFloor": "50000000", + "notionalCap": "150000000", + "notionalFloor": "75000000", "maintMarginRatio": "0.5", - "cum": "16682210.0" + "cum": "25022210.0" } } ], @@ -8388,96 +8854,144 @@ "tier": 1.0, "currency": "USDT", "minNotional": 0.0, - "maxNotional": 5000.0, - "maintenanceMarginRate": 0.02, - "maxLeverage": 20.0, + "maxNotional": 10000.0, + "maintenanceMarginRate": 0.01, + "maxLeverage": 26.0, "info": { "bracket": "1", - "initialLeverage": "20", - "notionalCap": "5000", + "initialLeverage": "26", + "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": 15.0, + "minNotional": 10000.0, + "maxNotional": 20000.0, + "maintenanceMarginRate": 0.015, + "maxLeverage": 25.0, "info": { "bracket": "2", - "initialLeverage": "15", - "notionalCap": "25000", - "notionalFloor": "5000", - "maintMarginRatio": "0.025", - "cum": "25.0" + "initialLeverage": "25", + "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": 20.0, "info": { "bracket": "3", - "initialLeverage": "10", + "initialLeverage": "20", "notionalCap": "100000", - "notionalFloor": "25000", - "maintMarginRatio": "0.05", - "cum": "650.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": 15.0, "info": { "bracket": "4", - "initialLeverage": "5", - "notionalCap": "250000", + "initialLeverage": "15", + "notionalCap": "200000", "notionalFloor": "100000", - "maintMarginRatio": "0.1", - "cum": "5650.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": "11900.0" + "notionalFloor": "200000", + "maintMarginRatio": "0.05", + "cum": "5650.0" } }, { "tier": 6.0, "currency": "USDT", "minNotional": 1000000.0, - "maxNotional": 5000000.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": 3000000.0, + "maintenanceMarginRate": 0.25, + "maxLeverage": 2.0, + "info": { + "bracket": "8", + "initialLeverage": "2", + "notionalCap": "3000000", + "notionalFloor": "2500000", + "maintMarginRatio": "0.25", + "cum": "418150.0" + } + }, + { + "tier": 9.0, + "currency": "USDT", + "minNotional": 3000000.0, + "maxNotional": 3500000.0, "maintenanceMarginRate": 0.5, "maxLeverage": 1.0, "info": { - "bracket": "6", + "bracket": "9", "initialLeverage": "1", - "notionalCap": "5000000", - "notionalFloor": "1000000", + "notionalCap": "3500000", + "notionalFloor": "3000000", "maintMarginRatio": "0.5", - "cum": "386900.0" + "cum": "1168150.0" } } ], @@ -8632,13 +9146,13 @@ "tier": 2.0, "currency": "USDT", "minNotional": 10000.0, - "maxNotional": 40000.0, + "maxNotional": 60000.0, "maintenanceMarginRate": 0.015, "maxLeverage": 50.0, "info": { "bracket": "2", "initialLeverage": "50", - "notionalCap": "40000", + "notionalCap": "60000", "notionalFloor": "10000", "maintMarginRatio": "0.015", "cum": "50.0" @@ -8647,113 +9161,113 @@ { "tier": 3.0, "currency": "USDT", - "minNotional": 40000.0, - "maxNotional": 200000.0, + "minNotional": 60000.0, + "maxNotional": 300000.0, "maintenanceMarginRate": 0.02, "maxLeverage": 25.0, "info": { "bracket": "3", "initialLeverage": "25", - "notionalCap": "200000", - "notionalFloor": "40000", + "notionalCap": "300000", + "notionalFloor": "60000", "maintMarginRatio": "0.02", - "cum": "250.0" + "cum": "350.0" } }, { "tier": 4.0, "currency": "USDT", - "minNotional": 200000.0, - "maxNotional": 400000.0, + "minNotional": 300000.0, + "maxNotional": 600000.0, "maintenanceMarginRate": 0.025, "maxLeverage": 20.0, "info": { "bracket": "4", "initialLeverage": "20", - "notionalCap": "400000", - "notionalFloor": "200000", + "notionalCap": "600000", + "notionalFloor": "300000", "maintMarginRatio": "0.025", - "cum": "1250.0" + "cum": "1850.0" } }, { "tier": 5.0, "currency": "USDT", - "minNotional": 400000.0, - "maxNotional": 2000000.0, + "minNotional": 600000.0, + "maxNotional": 3000000.0, "maintenanceMarginRate": 0.05, "maxLeverage": 10.0, "info": { "bracket": "5", "initialLeverage": "10", - "notionalCap": "2000000", - "notionalFloor": "400000", + "notionalCap": "3000000", + "notionalFloor": "600000", "maintMarginRatio": "0.05", - "cum": "11250.0" + "cum": "16850.0" } }, { "tier": 6.0, "currency": "USDT", - "minNotional": 2000000.0, - "maxNotional": 4000000.0, + "minNotional": 3000000.0, + "maxNotional": 6000000.0, "maintenanceMarginRate": 0.1, "maxLeverage": 5.0, "info": { "bracket": "6", "initialLeverage": "5", - "notionalCap": "4000000", - "notionalFloor": "2000000", + "notionalCap": "6000000", + "notionalFloor": "3000000", "maintMarginRatio": "0.1", - "cum": "111250.0" + "cum": "166850.0" } }, { "tier": 7.0, "currency": "USDT", - "minNotional": 4000000.0, - "maxNotional": 5000000.0, + "minNotional": 6000000.0, + "maxNotional": 7500000.0, "maintenanceMarginRate": 0.125, "maxLeverage": 4.0, "info": { "bracket": "7", "initialLeverage": "4", - "notionalCap": "5000000", - "notionalFloor": "4000000", + "notionalCap": "7500000", + "notionalFloor": "6000000", "maintMarginRatio": "0.125", - "cum": "211250.0" + "cum": "316850.0" } }, { "tier": 8.0, "currency": "USDT", - "minNotional": 5000000.0, - "maxNotional": 10000000.0, + "minNotional": 7500000.0, + "maxNotional": 15000000.0, "maintenanceMarginRate": 0.25, "maxLeverage": 2.0, "info": { "bracket": "8", "initialLeverage": "2", - "notionalCap": "10000000", - "notionalFloor": "5000000", + "notionalCap": "15000000", + "notionalFloor": "7500000", "maintMarginRatio": "0.25", - "cum": "836250.0" + "cum": "1254350.0" } }, { "tier": 9.0, "currency": "USDT", - "minNotional": 10000000.0, - "maxNotional": 20000000.0, + "minNotional": 15000000.0, + "maxNotional": 30000000.0, "maintenanceMarginRate": 0.5, "maxLeverage": 1.0, "info": { "bracket": "9", "initialLeverage": "1", - "notionalCap": "20000000", - "notionalFloor": "10000000", + "notionalCap": "30000000", + "notionalFloor": "15000000", "maintMarginRatio": "0.5", - "cum": "3336250.0" + "cum": "5004350.0" } } ], @@ -8877,14 +9391,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" } }, @@ -8892,112 +9406,128 @@ "tier": 2.0, "currency": "USDT", "minNotional": 5000.0, - "maxNotional": 20000.0, - "maintenanceMarginRate": 0.02, - "maxLeverage": 25.0, + "maxNotional": 10000.0, + "maintenanceMarginRate": 0.015, + "maxLeverage": 50.0, "info": { "bracket": "2", - "initialLeverage": "25", - "notionalCap": "20000", + "initialLeverage": "50", + "notionalCap": "10000", "notionalFloor": "5000", - "maintMarginRatio": "0.02", + "maintMarginRatio": "0.015", "cum": "25.0" } }, { "tier": 3.0, "currency": "USDT", - "minNotional": 20000.0, - "maxNotional": 25000.0, - "maintenanceMarginRate": 0.025, - "maxLeverage": 20.0, + "minNotional": 10000.0, + "maxNotional": 30000.0, + "maintenanceMarginRate": 0.02, + "maxLeverage": 25.0, "info": { "bracket": "3", - "initialLeverage": "20", - "notionalCap": "25000", - "notionalFloor": "20000", - "maintMarginRatio": "0.025", - "cum": "125.0" + "initialLeverage": "25", + "notionalCap": "30000", + "notionalFloor": "10000", + "maintMarginRatio": "0.02", + "cum": "75.0" } }, { "tier": 4.0, "currency": "USDT", - "minNotional": 25000.0, - "maxNotional": 200000.0, - "maintenanceMarginRate": 0.05, - "maxLeverage": 10.0, + "minNotional": 30000.0, + "maxNotional": 60000.0, + "maintenanceMarginRate": 0.025, + "maxLeverage": 20.0, "info": { "bracket": "4", - "initialLeverage": "10", - "notionalCap": "200000", - "notionalFloor": "25000", - "maintMarginRatio": "0.05", - "cum": "750.0" + "initialLeverage": "20", + "notionalCap": "60000", + "notionalFloor": "30000", + "maintMarginRatio": "0.025", + "cum": "225.0" } }, { "tier": 5.0, "currency": "USDT", - "minNotional": 200000.0, - "maxNotional": 400000.0, - "maintenanceMarginRate": 0.1, - "maxLeverage": 5.0, + "minNotional": 60000.0, + "maxNotional": 300000.0, + "maintenanceMarginRate": 0.05, + "maxLeverage": 10.0, "info": { "bracket": "5", - "initialLeverage": "5", - "notionalCap": "400000", - "notionalFloor": "200000", - "maintMarginRatio": "0.1", - "cum": "10750.0" + "initialLeverage": "10", + "notionalCap": "300000", + "notionalFloor": "60000", + "maintMarginRatio": "0.05", + "cum": "1725.0" } }, { "tier": 6.0, "currency": "USDT", - "minNotional": 400000.0, - "maxNotional": 500000.0, - "maintenanceMarginRate": 0.125, - "maxLeverage": 4.0, + "minNotional": 300000.0, + "maxNotional": 600000.0, + "maintenanceMarginRate": 0.1, + "maxLeverage": 5.0, "info": { "bracket": "6", - "initialLeverage": "4", - "notionalCap": "500000", - "notionalFloor": "400000", - "maintMarginRatio": "0.125", - "cum": "20750.0" + "initialLeverage": "5", + "notionalCap": "600000", + "notionalFloor": "300000", + "maintMarginRatio": "0.1", + "cum": "16725.0" } }, { "tier": 7.0, "currency": "USDT", - "minNotional": 500000.0, - "maxNotional": 1000000.0, - "maintenanceMarginRate": 0.25, - "maxLeverage": 2.0, + "minNotional": 600000.0, + "maxNotional": 750000.0, + "maintenanceMarginRate": 0.125, + "maxLeverage": 4.0, "info": { "bracket": "7", - "initialLeverage": "2", - "notionalCap": "1000000", - "notionalFloor": "500000", - "maintMarginRatio": "0.25", - "cum": "83250.0" + "initialLeverage": "4", + "notionalCap": "750000", + "notionalFloor": "600000", + "maintMarginRatio": "0.125", + "cum": "31725.0" } }, { "tier": 8.0, "currency": "USDT", - "minNotional": 1000000.0, - "maxNotional": 2000000.0, + "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": "8", + "bracket": "9", "initialLeverage": "1", - "notionalCap": "2000000", - "notionalFloor": "1000000", + "notionalCap": "3000000", + "notionalFloor": "1500000", "maintMarginRatio": "0.5", - "cum": "333250.0" + "cum": "500475.0" } } ], @@ -9617,7 +10147,7 @@ } } ], - "BTC/USDT:USDT-240927": [ + "BTC/USDT:USDT-241227": [ { "tier": 1.0, "currency": "USDT", @@ -9747,7 +10277,7 @@ } } ], - "BTC/USDT:USDT-241227": [ + "BTC/USDT:USDT-250328": [ { "tier": 1.0, "currency": "USDT", @@ -10193,14 +10723,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" } }, @@ -10208,64 +10738,64 @@ "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": 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": "675.0" + "initialLeverage": "25", + "notionalCap": "50000", + "notionalFloor": "10000", + "maintMarginRatio": "0.02", + "cum": "75.0" } }, { "tier": 4.0, "currency": "USDT", - "minNotional": 100000.0, - "maxNotional": 200000.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": "200000", - "notionalFloor": "100000", - "maintMarginRatio": "0.1", - "cum": "5675.0" + "initialLeverage": "20", + "notionalCap": "100000", + "notionalFloor": "50000", + "maintMarginRatio": "0.025", + "cum": "325.0" } }, { "tier": 5.0, "currency": "USDT", - "minNotional": 200000.0, + "minNotional": 100000.0, "maxNotional": 500000.0, - "maintenanceMarginRate": 0.125, - "maxLeverage": 4.0, + "maintenanceMarginRate": 0.05, + "maxLeverage": 10.0, "info": { "bracket": "5", - "initialLeverage": "4", + "initialLeverage": "10", "notionalCap": "500000", - "notionalFloor": "200000", - "maintMarginRatio": "0.125", - "cum": "10675.0" + "notionalFloor": "100000", + "maintMarginRatio": "0.05", + "cum": "2825.0" } }, { @@ -10273,31 +10803,63 @@ "currency": "USDT", "minNotional": 500000.0, "maxNotional": 1000000.0, - "maintenanceMarginRate": 0.25, - "maxLeverage": 2.0, + "maintenanceMarginRate": 0.1, + "maxLeverage": 5.0, "info": { "bracket": "6", - "initialLeverage": "2", + "initialLeverage": "5", "notionalCap": "1000000", "notionalFloor": "500000", - "maintMarginRatio": "0.25", - "cum": "73175.0" + "maintMarginRatio": "0.1", + "cum": "27825.0" } }, { "tier": 7.0, "currency": "USDT", "minNotional": 1000000.0, - "maxNotional": 2000000.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": "7", + "bracket": "9", "initialLeverage": "1", - "notionalCap": "2000000", - "notionalFloor": "1000000", + "notionalCap": "5000000", + "notionalFloor": "2500000", "maintMarginRatio": "0.5", - "cum": "323175.0" + "cum": "834075.0" } } ], @@ -10322,13 +10884,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" @@ -10337,113 +10899,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" } } ], @@ -10453,14 +11015,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" } }, @@ -10468,96 +11030,128 @@ "tier": 2.0, "currency": "USDT", "minNotional": 5000.0, - "maxNotional": 25000.0, - "maintenanceMarginRate": 0.025, - "maxLeverage": 20.0, + "maxNotional": 16000.0, + "maintenanceMarginRate": 0.015, + "maxLeverage": 50.0, "info": { "bracket": "2", - "initialLeverage": "20", - "notionalCap": "25000", + "initialLeverage": "50", + "notionalCap": "16000", "notionalFloor": "5000", - "maintMarginRatio": "0.025", - "cum": "50.0" + "maintMarginRatio": "0.015", + "cum": "25.0" } }, { "tier": 3.0, "currency": "USDT", - "minNotional": 25000.0, - "maxNotional": 300000.0, - "maintenanceMarginRate": 0.05, - "maxLeverage": 10.0, + "minNotional": 16000.0, + "maxNotional": 80000.0, + "maintenanceMarginRate": 0.02, + "maxLeverage": 25.0, "info": { "bracket": "3", - "initialLeverage": "10", - "notionalCap": "300000", - "notionalFloor": "25000", - "maintMarginRatio": "0.05", - "cum": "675.0" + "initialLeverage": "25", + "notionalCap": "80000", + "notionalFloor": "16000", + "maintMarginRatio": "0.02", + "cum": "105.0" } }, { "tier": 4.0, "currency": "USDT", - "minNotional": 300000.0, - "maxNotional": 800000.0, - "maintenanceMarginRate": 0.1, - "maxLeverage": 5.0, + "minNotional": 80000.0, + "maxNotional": 160000.0, + "maintenanceMarginRate": 0.025, + "maxLeverage": 20.0, "info": { "bracket": "4", - "initialLeverage": "5", - "notionalCap": "800000", - "notionalFloor": "300000", - "maintMarginRatio": "0.1", - "cum": "15675.0" + "initialLeverage": "20", + "notionalCap": "160000", + "notionalFloor": "80000", + "maintMarginRatio": "0.025", + "cum": "505.0" } }, { "tier": 5.0, "currency": "USDT", - "minNotional": 800000.0, - "maxNotional": 1000000.0, - "maintenanceMarginRate": 0.125, - "maxLeverage": 4.0, + "minNotional": 160000.0, + "maxNotional": 800000.0, + "maintenanceMarginRate": 0.05, + "maxLeverage": 10.0, "info": { "bracket": "5", - "initialLeverage": "4", - "notionalCap": "1000000", - "notionalFloor": "800000", - "maintMarginRatio": "0.125", - "cum": "35675.0" + "initialLeverage": "10", + "notionalCap": "800000", + "notionalFloor": "160000", + "maintMarginRatio": "0.05", + "cum": "4505.0" } }, { "tier": 6.0, "currency": "USDT", - "minNotional": 1000000.0, - "maxNotional": 3000000.0, - "maintenanceMarginRate": 0.25, - "maxLeverage": 2.0, + "minNotional": 800000.0, + "maxNotional": 1600000.0, + "maintenanceMarginRate": 0.1, + "maxLeverage": 5.0, "info": { "bracket": "6", - "initialLeverage": "2", - "notionalCap": "3000000", - "notionalFloor": "1000000", - "maintMarginRatio": "0.25", - "cum": "160675.0" + "initialLeverage": "5", + "notionalCap": "1600000", + "notionalFloor": "800000", + "maintMarginRatio": "0.1", + "cum": "44505.0" } }, { "tier": 7.0, "currency": "USDT", - "minNotional": 3000000.0, - "maxNotional": 5000000.0, + "minNotional": 1600000.0, + "maxNotional": 2000000.0, + "maintenanceMarginRate": 0.125, + "maxLeverage": 4.0, + "info": { + "bracket": "7", + "initialLeverage": "4", + "notionalCap": "2000000", + "notionalFloor": "1600000", + "maintMarginRatio": "0.125", + "cum": "84505.0" + } + }, + { + "tier": 8.0, + "currency": "USDT", + "minNotional": 2000000.0, + "maxNotional": 4000000.0, + "maintenanceMarginRate": 0.25, + "maxLeverage": 2.0, + "info": { + "bracket": "8", + "initialLeverage": "2", + "notionalCap": "4000000", + "notionalFloor": "2000000", + "maintMarginRatio": "0.25", + "cum": "334505.0" + } + }, + { + "tier": 9.0, + "currency": "USDT", + "minNotional": 4000000.0, + "maxNotional": 8000000.0, "maintenanceMarginRate": 0.5, "maxLeverage": 1.0, "info": { - "bracket": "7", + "bracket": "9", "initialLeverage": "1", - "notionalCap": "5000000", - "notionalFloor": "3000000", + "notionalCap": "8000000", + "notionalFloor": "4000000", "maintMarginRatio": "0.5", - "cum": "910675.0" + "cum": "1334505.0" } } ], @@ -10680,13 +11274,13 @@ "tier": 1.0, "currency": "USDT", "minNotional": 0.0, - "maxNotional": 5000.0, + "maxNotional": 10000.0, "maintenanceMarginRate": 0.01, - "maxLeverage": 50.0, + "maxLeverage": 75.0, "info": { "bracket": "1", - "initialLeverage": "50", - "notionalCap": "5000", + "initialLeverage": "75", + "notionalCap": "10000", "notionalFloor": "0", "maintMarginRatio": "0.01", "cum": "0.0" @@ -10695,65 +11289,65 @@ { "tier": 2.0, "currency": "USDT", - "minNotional": 5000.0, - "maxNotional": 50000.0, + "minNotional": 10000.0, + "maxNotional": 60000.0, "maintenanceMarginRate": 0.015, - "maxLeverage": 25.0, + "maxLeverage": 50.0, "info": { "bracket": "2", - "initialLeverage": "25", - "notionalCap": "50000", - "notionalFloor": "5000", + "initialLeverage": "50", + "notionalCap": "60000", + "notionalFloor": "10000", "maintMarginRatio": "0.015", - "cum": "25.0" + "cum": "50.0" } }, { "tier": 3.0, "currency": "USDT", - "minNotional": 50000.0, + "minNotional": 60000.0, "maxNotional": 400000.0, "maintenanceMarginRate": 0.02, - "maxLeverage": 20.0, + "maxLeverage": 25.0, "info": { "bracket": "3", - "initialLeverage": "20", + "initialLeverage": "25", "notionalCap": "400000", - "notionalFloor": "50000", + "notionalFloor": "60000", "maintMarginRatio": "0.02", - "cum": "275.0" + "cum": "350.0" } }, { "tier": 4.0, "currency": "USDT", "minNotional": 400000.0, - "maxNotional": 1200000.0, - "maintenanceMarginRate": 0.05, - "maxLeverage": 10.0, + "maxNotional": 600000.0, + "maintenanceMarginRate": 0.025, + "maxLeverage": 20.0, "info": { "bracket": "4", - "initialLeverage": "10", - "notionalCap": "1200000", + "initialLeverage": "20", + "notionalCap": "600000", "notionalFloor": "400000", - "maintMarginRatio": "0.05", - "cum": "12275.0" + "maintMarginRatio": "0.025", + "cum": "2350.0" } }, { "tier": 5.0, "currency": "USDT", - "minNotional": 1200000.0, + "minNotional": 600000.0, "maxNotional": 3000000.0, - "maintenanceMarginRate": 0.1, - "maxLeverage": 5.0, + "maintenanceMarginRate": 0.05, + "maxLeverage": 10.0, "info": { "bracket": "5", - "initialLeverage": "5", + "initialLeverage": "10", "notionalCap": "3000000", - "notionalFloor": "1200000", - "maintMarginRatio": "0.1", - "cum": "72275.0" + "notionalFloor": "600000", + "maintMarginRatio": "0.05", + "cum": "17350.0" } }, { @@ -10761,47 +11355,63 @@ "currency": "USDT", "minNotional": 3000000.0, "maxNotional": 6000000.0, - "maintenanceMarginRate": 0.125, - "maxLeverage": 4.0, + "maintenanceMarginRate": 0.1, + "maxLeverage": 5.0, "info": { "bracket": "6", - "initialLeverage": "4", + "initialLeverage": "5", "notionalCap": "6000000", "notionalFloor": "3000000", - "maintMarginRatio": "0.125", - "cum": "147275.0" + "maintMarginRatio": "0.1", + "cum": "167350.0" } }, { "tier": 7.0, "currency": "USDT", "minNotional": 6000000.0, - "maxNotional": 18000000.0, - "maintenanceMarginRate": 0.25, - "maxLeverage": 2.0, + "maxNotional": 7500000.0, + "maintenanceMarginRate": 0.125, + "maxLeverage": 4.0, "info": { "bracket": "7", - "initialLeverage": "2", - "notionalCap": "18000000", + "initialLeverage": "4", + "notionalCap": "7500000", "notionalFloor": "6000000", - "maintMarginRatio": "0.25", - "cum": "897275.0" + "maintMarginRatio": "0.125", + "cum": "317350.0" } }, { "tier": 8.0, "currency": "USDT", + "minNotional": 7500000.0, + "maxNotional": 18000000.0, + "maintenanceMarginRate": 0.25, + "maxLeverage": 2.0, + "info": { + "bracket": "8", + "initialLeverage": "2", + "notionalCap": "18000000", + "notionalFloor": "7500000", + "maintMarginRatio": "0.25", + "cum": "1254850.0" + } + }, + { + "tier": 9.0, + "currency": "USDT", "minNotional": 18000000.0, "maxNotional": 30000000.0, "maintenanceMarginRate": 0.5, "maxLeverage": 1.0, "info": { - "bracket": "8", + "bracket": "9", "initialLeverage": "1", "notionalCap": "30000000", "notionalFloor": "18000000", "maintMarginRatio": "0.5", - "cum": "5397275.0" + "cum": "5754850.0" } } ], @@ -11055,14 +11665,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" } }, @@ -11070,80 +11680,128 @@ "tier": 2.0, "currency": "USDT", "minNotional": 5000.0, - "maxNotional": 25000.0, - "maintenanceMarginRate": 0.025, - "maxLeverage": 20.0, + "maxNotional": 16000.0, + "maintenanceMarginRate": 0.015, + "maxLeverage": 50.0, "info": { "bracket": "2", - "initialLeverage": "20", - "notionalCap": "25000", + "initialLeverage": "50", + "notionalCap": "16000", "notionalFloor": "5000", - "maintMarginRatio": "0.025", - "cum": "50.0" + "maintMarginRatio": "0.015", + "cum": "25.0" } }, { "tier": 3.0, "currency": "USDT", - "minNotional": 25000.0, - "maxNotional": 100000.0, - "maintenanceMarginRate": 0.05, - "maxLeverage": 10.0, + "minNotional": 16000.0, + "maxNotional": 80000.0, + "maintenanceMarginRate": 0.02, + "maxLeverage": 25.0, "info": { "bracket": "3", - "initialLeverage": "10", - "notionalCap": "100000", - "notionalFloor": "25000", - "maintMarginRatio": "0.05", - "cum": "675.0" + "initialLeverage": "25", + "notionalCap": "80000", + "notionalFloor": "16000", + "maintMarginRatio": "0.02", + "cum": "105.0" } }, { "tier": 4.0, "currency": "USDT", - "minNotional": 100000.0, - "maxNotional": 250000.0, - "maintenanceMarginRate": 0.1, - "maxLeverage": 5.0, + "minNotional": 80000.0, + "maxNotional": 160000.0, + "maintenanceMarginRate": 0.025, + "maxLeverage": 20.0, "info": { "bracket": "4", - "initialLeverage": "5", - "notionalCap": "250000", - "notionalFloor": "100000", - "maintMarginRatio": "0.1", - "cum": "5675.0" + "initialLeverage": "20", + "notionalCap": "160000", + "notionalFloor": "80000", + "maintMarginRatio": "0.025", + "cum": "505.0" } }, { "tier": 5.0, "currency": "USDT", - "minNotional": 250000.0, - "maxNotional": 1000000.0, - "maintenanceMarginRate": 0.125, - "maxLeverage": 2.0, + "minNotional": 160000.0, + "maxNotional": 800000.0, + "maintenanceMarginRate": 0.05, + "maxLeverage": 10.0, "info": { "bracket": "5", - "initialLeverage": "2", - "notionalCap": "1000000", - "notionalFloor": "250000", - "maintMarginRatio": "0.125", - "cum": "11925.0" + "initialLeverage": "10", + "notionalCap": "800000", + "notionalFloor": "160000", + "maintMarginRatio": "0.05", + "cum": "4505.0" } }, { "tier": 6.0, "currency": "USDT", - "minNotional": 1000000.0, - "maxNotional": 5000000.0, + "minNotional": 800000.0, + "maxNotional": 1600000.0, + "maintenanceMarginRate": 0.1, + "maxLeverage": 5.0, + "info": { + "bracket": "6", + "initialLeverage": "5", + "notionalCap": "1600000", + "notionalFloor": "800000", + "maintMarginRatio": "0.1", + "cum": "44505.0" + } + }, + { + "tier": 7.0, + "currency": "USDT", + "minNotional": 1600000.0, + "maxNotional": 2000000.0, + "maintenanceMarginRate": 0.125, + "maxLeverage": 4.0, + "info": { + "bracket": "7", + "initialLeverage": "4", + "notionalCap": "2000000", + "notionalFloor": "1600000", + "maintMarginRatio": "0.125", + "cum": "84505.0" + } + }, + { + "tier": 8.0, + "currency": "USDT", + "minNotional": 2000000.0, + "maxNotional": 4000000.0, + "maintenanceMarginRate": 0.25, + "maxLeverage": 2.0, + "info": { + "bracket": "8", + "initialLeverage": "2", + "notionalCap": "4000000", + "notionalFloor": "2000000", + "maintMarginRatio": "0.25", + "cum": "334505.0" + } + }, + { + "tier": 9.0, + "currency": "USDT", + "minNotional": 4000000.0, + "maxNotional": 8000000.0, "maintenanceMarginRate": 0.5, "maxLeverage": 1.0, "info": { - "bracket": "6", + "bracket": "9", "initialLeverage": "1", - "notionalCap": "5000000", - "notionalFloor": "1000000", + "notionalCap": "8000000", + "notionalFloor": "4000000", "maintMarginRatio": "0.5", - "cum": "386925.0" + "cum": "1334505.0" } } ], @@ -11521,6 +12179,152 @@ } } ], + "COS/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": 20000.0, + "maintenanceMarginRate": 0.02, + "maxLeverage": 25.0, + "info": { + "bracket": "3", + "initialLeverage": "25", + "notionalCap": "20000", + "notionalFloor": "10000", + "maintMarginRatio": "0.02", + "cum": "75.0" + } + }, + { + "tier": 4.0, + "currency": "USDT", + "minNotional": 20000.0, + "maxNotional": 40000.0, + "maintenanceMarginRate": 0.025, + "maxLeverage": 20.0, + "info": { + "bracket": "4", + "initialLeverage": "20", + "notionalCap": "40000", + "notionalFloor": "20000", + "maintMarginRatio": "0.025", + "cum": "175.0" + } + }, + { + "tier": 5.0, + "currency": "USDT", + "minNotional": 40000.0, + "maxNotional": 200000.0, + "maintenanceMarginRate": 0.05, + "maxLeverage": 10.0, + "info": { + "bracket": "5", + "initialLeverage": "10", + "notionalCap": "200000", + "notionalFloor": "40000", + "maintMarginRatio": "0.05", + "cum": "1175.0" + } + }, + { + "tier": 6.0, + "currency": "USDT", + "minNotional": 200000.0, + "maxNotional": 400000.0, + "maintenanceMarginRate": 0.1, + "maxLeverage": 5.0, + "info": { + "bracket": "6", + "initialLeverage": "5", + "notionalCap": "400000", + "notionalFloor": "200000", + "maintMarginRatio": "0.1", + "cum": "11175.0" + } + }, + { + "tier": 7.0, + "currency": "USDT", + "minNotional": 400000.0, + "maxNotional": 500000.0, + "maintenanceMarginRate": 0.125, + "maxLeverage": 4.0, + "info": { + "bracket": "7", + "initialLeverage": "4", + "notionalCap": "500000", + "notionalFloor": "400000", + "maintMarginRatio": "0.125", + "cum": "21175.0" + } + }, + { + "tier": 8.0, + "currency": "USDT", + "minNotional": 500000.0, + "maxNotional": 1000000.0, + "maintenanceMarginRate": 0.25, + "maxLeverage": 2.0, + "info": { + "bracket": "8", + "initialLeverage": "2", + "notionalCap": "1000000", + "notionalFloor": "500000", + "maintMarginRatio": "0.25", + "cum": "83675.0" + } + }, + { + "tier": 9.0, + "currency": "USDT", + "minNotional": 1000000.0, + "maxNotional": 2000000.0, + "maintenanceMarginRate": 0.5, + "maxLeverage": 1.0, + "info": { + "bracket": "9", + "initialLeverage": "1", + "notionalCap": "2000000", + "notionalFloor": "1000000", + "maintMarginRatio": "0.5", + "cum": "333675.0" + } + } + ], "COTI/USDT:USDT": [ { "tier": 1.0, @@ -12406,112 +13210,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" } } ], @@ -12955,6 +13791,152 @@ } } ], + "DIA/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": 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": "9", + "initialLeverage": "1", + "notionalCap": "10000000", + "notionalFloor": "5000000", + "maintMarginRatio": "0.5", + "cum": "1668150.0" + } + } + ], "DODOX/USDT:USDT": [ { "tier": 1.0, @@ -13284,13 +14266,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" @@ -13299,97 +14281,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" } } ], @@ -13414,13 +14396,13 @@ "tier": 2.0, "currency": "USDT", "minNotional": 10000.0, - "maxNotional": 40000.0, + "maxNotional": 60000.0, "maintenanceMarginRate": 0.015, "maxLeverage": 50.0, "info": { "bracket": "2", "initialLeverage": "50", - "notionalCap": "40000", + "notionalCap": "60000", "notionalFloor": "10000", "maintMarginRatio": "0.015", "cum": "50.0" @@ -13429,113 +14411,113 @@ { "tier": 3.0, "currency": "USDT", - "minNotional": 40000.0, - "maxNotional": 200000.0, + "minNotional": 60000.0, + "maxNotional": 300000.0, "maintenanceMarginRate": 0.02, "maxLeverage": 25.0, "info": { "bracket": "3", "initialLeverage": "25", - "notionalCap": "200000", - "notionalFloor": "40000", + "notionalCap": "300000", + "notionalFloor": "60000", "maintMarginRatio": "0.02", - "cum": "250.0" + "cum": "350.0" } }, { "tier": 4.0, "currency": "USDT", - "minNotional": 200000.0, - "maxNotional": 400000.0, + "minNotional": 300000.0, + "maxNotional": 600000.0, "maintenanceMarginRate": 0.025, "maxLeverage": 20.0, "info": { "bracket": "4", "initialLeverage": "20", - "notionalCap": "400000", - "notionalFloor": "200000", + "notionalCap": "600000", + "notionalFloor": "300000", "maintMarginRatio": "0.025", - "cum": "1250.0" + "cum": "1850.0" } }, { "tier": 5.0, "currency": "USDT", - "minNotional": 400000.0, - "maxNotional": 2000000.0, + "minNotional": 600000.0, + "maxNotional": 3000000.0, "maintenanceMarginRate": 0.05, "maxLeverage": 10.0, "info": { "bracket": "5", "initialLeverage": "10", - "notionalCap": "2000000", - "notionalFloor": "400000", + "notionalCap": "3000000", + "notionalFloor": "600000", "maintMarginRatio": "0.05", - "cum": "11250.0" + "cum": "16850.0" } }, { "tier": 6.0, "currency": "USDT", - "minNotional": 2000000.0, - "maxNotional": 4000000.0, + "minNotional": 3000000.0, + "maxNotional": 6000000.0, "maintenanceMarginRate": 0.1, "maxLeverage": 5.0, "info": { "bracket": "6", "initialLeverage": "5", - "notionalCap": "4000000", - "notionalFloor": "2000000", + "notionalCap": "6000000", + "notionalFloor": "3000000", "maintMarginRatio": "0.1", - "cum": "111250.0" + "cum": "166850.0" } }, { "tier": 7.0, "currency": "USDT", - "minNotional": 4000000.0, - "maxNotional": 5000000.0, + "minNotional": 6000000.0, + "maxNotional": 7500000.0, "maintenanceMarginRate": 0.125, "maxLeverage": 4.0, "info": { "bracket": "7", "initialLeverage": "4", - "notionalCap": "5000000", - "notionalFloor": "4000000", + "notionalCap": "7500000", + "notionalFloor": "6000000", "maintMarginRatio": "0.125", - "cum": "211250.0" + "cum": "316850.0" } }, { "tier": 8.0, "currency": "USDT", - "minNotional": 5000000.0, - "maxNotional": 10000000.0, + "minNotional": 7500000.0, + "maxNotional": 15000000.0, "maintenanceMarginRate": 0.25, "maxLeverage": 2.0, "info": { "bracket": "8", "initialLeverage": "2", - "notionalCap": "10000000", - "notionalFloor": "5000000", + "notionalCap": "15000000", + "notionalFloor": "7500000", "maintMarginRatio": "0.25", - "cum": "836250.0" + "cum": "1254350.0" } }, { "tier": 9.0, "currency": "USDT", - "minNotional": 10000000.0, - "maxNotional": 20000000.0, + "minNotional": 15000000.0, + "maxNotional": 30000000.0, "maintenanceMarginRate": 0.5, "maxLeverage": 1.0, "info": { "bracket": "9", "initialLeverage": "1", - "notionalCap": "20000000", - "notionalFloor": "10000000", + "notionalCap": "30000000", + "notionalFloor": "15000000", "maintMarginRatio": "0.5", - "cum": "3336250.0" + "cum": "5004350.0" } } ], @@ -13820,13 +14802,13 @@ "tier": 1.0, "currency": "USDT", "minNotional": 0.0, - "maxNotional": 5000.0, + "maxNotional": 10000.0, "maintenanceMarginRate": 0.01, - "maxLeverage": 50.0, + "maxLeverage": 75.0, "info": { "bracket": "1", - "initialLeverage": "50", - "notionalCap": "5000", + "initialLeverage": "75", + "notionalCap": "10000", "notionalFloor": "0", "maintMarginRatio": "0.01", "cum": "0.0" @@ -13835,65 +14817,65 @@ { "tier": 2.0, "currency": "USDT", - "minNotional": 5000.0, - "maxNotional": 50000.0, - "maintenanceMarginRate": 0.02, - "maxLeverage": 25.0, + "minNotional": 10000.0, + "maxNotional": 40000.0, + "maintenanceMarginRate": 0.015, + "maxLeverage": 50.0, "info": { "bracket": "2", - "initialLeverage": "25", - "notionalCap": "50000", - "notionalFloor": "5000", - "maintMarginRatio": "0.02", + "initialLeverage": "50", + "notionalCap": "40000", + "notionalFloor": "10000", + "maintMarginRatio": "0.015", "cum": "50.0" } }, { "tier": 3.0, "currency": "USDT", - "minNotional": 50000.0, - "maxNotional": 400000.0, - "maintenanceMarginRate": 0.025, - "maxLeverage": 20.0, + "minNotional": 40000.0, + "maxNotional": 200000.0, + "maintenanceMarginRate": 0.02, + "maxLeverage": 25.0, "info": { "bracket": "3", - "initialLeverage": "20", - "notionalCap": "400000", - "notionalFloor": "50000", - "maintMarginRatio": "0.025", - "cum": "300.0" + "initialLeverage": "25", + "notionalCap": "200000", + "notionalFloor": "40000", + "maintMarginRatio": "0.02", + "cum": "250.0" } }, { "tier": 4.0, "currency": "USDT", - "minNotional": 400000.0, - "maxNotional": 800000.0, - "maintenanceMarginRate": 0.05, - "maxLeverage": 10.0, + "minNotional": 200000.0, + "maxNotional": 400000.0, + "maintenanceMarginRate": 0.025, + "maxLeverage": 20.0, "info": { "bracket": "4", - "initialLeverage": "10", - "notionalCap": "800000", - "notionalFloor": "400000", - "maintMarginRatio": "0.05", - "cum": "10300.0" + "initialLeverage": "20", + "notionalCap": "400000", + "notionalFloor": "200000", + "maintMarginRatio": "0.025", + "cum": "1250.0" } }, { "tier": 5.0, "currency": "USDT", - "minNotional": 800000.0, + "minNotional": 400000.0, "maxNotional": 2000000.0, - "maintenanceMarginRate": 0.1, - "maxLeverage": 5.0, + "maintenanceMarginRate": 0.05, + "maxLeverage": 10.0, "info": { "bracket": "5", - "initialLeverage": "5", + "initialLeverage": "10", "notionalCap": "2000000", - "notionalFloor": "800000", - "maintMarginRatio": "0.1", - "cum": "50300.0" + "notionalFloor": "400000", + "maintMarginRatio": "0.05", + "cum": "11250.0" } }, { @@ -13901,47 +14883,63 @@ "currency": "USDT", "minNotional": 2000000.0, "maxNotional": 4000000.0, - "maintenanceMarginRate": 0.125, - "maxLeverage": 4.0, + "maintenanceMarginRate": 0.1, + "maxLeverage": 5.0, "info": { "bracket": "6", - "initialLeverage": "4", + "initialLeverage": "5", "notionalCap": "4000000", "notionalFloor": "2000000", - "maintMarginRatio": "0.125", - "cum": "100300.0" + "maintMarginRatio": "0.1", + "cum": "111250.0" } }, { "tier": 7.0, "currency": "USDT", "minNotional": 4000000.0, - "maxNotional": 12000000.0, - "maintenanceMarginRate": 0.25, - "maxLeverage": 2.0, + "maxNotional": 5000000.0, + "maintenanceMarginRate": 0.125, + "maxLeverage": 4.0, "info": { "bracket": "7", - "initialLeverage": "2", - "notionalCap": "12000000", + "initialLeverage": "4", + "notionalCap": "5000000", "notionalFloor": "4000000", - "maintMarginRatio": "0.25", - "cum": "600300.0" + "maintMarginRatio": "0.125", + "cum": "211250.0" } }, { "tier": 8.0, "currency": "USDT", + "minNotional": 5000000.0, + "maxNotional": 12000000.0, + "maintenanceMarginRate": 0.25, + "maxLeverage": 2.0, + "info": { + "bracket": "8", + "initialLeverage": "2", + "notionalCap": "12000000", + "notionalFloor": "5000000", + "maintMarginRatio": "0.25", + "cum": "836250.0" + } + }, + { + "tier": 9.0, + "currency": "USDT", "minNotional": 12000000.0, "maxNotional": 20000000.0, "maintenanceMarginRate": 0.5, "maxLeverage": 1.0, "info": { - "bracket": "8", + "bracket": "9", "initialLeverage": "1", "notionalCap": "20000000", "notionalFloor": "12000000", "maintMarginRatio": "0.5", - "cum": "3600300.0" + "cum": "3836250.0" } } ], @@ -14194,13 +15192,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" @@ -14209,97 +15207,275 @@ { "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" + } + } + ], + "EIGEN/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": 60000.0, + "maintenanceMarginRate": 0.015, + "maxLeverage": 50.0, + "info": { + "bracket": "2", + "initialLeverage": "50", + "notionalCap": "60000", + "notionalFloor": "10000", + "maintMarginRatio": "0.015", + "cum": "50.0" + } + }, + { + "tier": 3.0, + "currency": "USDT", + "minNotional": 60000.0, + "maxNotional": 300000.0, + "maintenanceMarginRate": 0.02, + "maxLeverage": 25.0, + "info": { + "bracket": "3", + "initialLeverage": "25", + "notionalCap": "300000", + "notionalFloor": "60000", + "maintMarginRatio": "0.02", + "cum": "350.0" + } + }, + { + "tier": 4.0, + "currency": "USDT", + "minNotional": 300000.0, + "maxNotional": 600000.0, + "maintenanceMarginRate": 0.025, + "maxLeverage": 20.0, + "info": { + "bracket": "4", + "initialLeverage": "20", + "notionalCap": "600000", + "notionalFloor": "300000", + "maintMarginRatio": "0.025", + "cum": "1850.0" + } + }, + { + "tier": 5.0, + "currency": "USDT", + "minNotional": 600000.0, + "maxNotional": 3000000.0, + "maintenanceMarginRate": 0.05, + "maxLeverage": 10.0, + "info": { + "bracket": "5", + "initialLeverage": "10", + "notionalCap": "3000000", + "notionalFloor": "600000", + "maintMarginRatio": "0.05", + "cum": "16850.0" + } + }, + { + "tier": 6.0, + "currency": "USDT", + "minNotional": 3000000.0, + "maxNotional": 6000000.0, + "maintenanceMarginRate": 0.1, + "maxLeverage": 5.0, + "info": { + "bracket": "6", + "initialLeverage": "5", + "notionalCap": "6000000", + "notionalFloor": "3000000", + "maintMarginRatio": "0.1", + "cum": "166850.0" + } + }, + { + "tier": 7.0, + "currency": "USDT", + "minNotional": 6000000.0, + "maxNotional": 7500000.0, + "maintenanceMarginRate": 0.125, + "maxLeverage": 4.0, + "info": { + "bracket": "7", + "initialLeverage": "4", + "notionalCap": "7500000", + "notionalFloor": "6000000", + "maintMarginRatio": "0.125", + "cum": "316850.0" + } + }, + { + "tier": 8.0, + "currency": "USDT", + "minNotional": 7500000.0, + "maxNotional": 15000000.0, + "maintenanceMarginRate": 0.25, + "maxLeverage": 2.0, + "info": { + "bracket": "8", + "initialLeverage": "2", + "notionalCap": "15000000", + "notionalFloor": "7500000", + "maintMarginRatio": "0.25", + "cum": "1254350.0" + } + }, + { + "tier": 9.0, + "currency": "USDT", + "minNotional": 15000000.0, + "maxNotional": 30000000.0, + "maintenanceMarginRate": 0.5, + "maxLeverage": 1.0, + "info": { + "bracket": "9", + "initialLeverage": "1", + "notionalCap": "30000000", + "notionalFloor": "15000000", + "maintMarginRatio": "0.5", + "cum": "5004350.0" } } ], @@ -14454,13 +15630,13 @@ "tier": 2.0, "currency": "USDT", "minNotional": 10000.0, - "maxNotional": 30000.0, + "maxNotional": 80000.0, "maintenanceMarginRate": 0.015, "maxLeverage": 50.0, "info": { "bracket": "2", "initialLeverage": "50", - "notionalCap": "30000", + "notionalCap": "80000", "notionalFloor": "10000", "maintMarginRatio": "0.015", "cum": "50.0" @@ -14469,113 +15645,113 @@ { "tier": 3.0, "currency": "USDT", - "minNotional": 30000.0, - "maxNotional": 150000.0, + "minNotional": 80000.0, + "maxNotional": 400000.0, "maintenanceMarginRate": 0.02, "maxLeverage": 25.0, "info": { "bracket": "3", "initialLeverage": "25", - "notionalCap": "150000", - "notionalFloor": "30000", + "notionalCap": "400000", + "notionalFloor": "80000", "maintMarginRatio": "0.02", - "cum": "200.0" + "cum": "450.0" } }, { "tier": 4.0, "currency": "USDT", - "minNotional": 150000.0, - "maxNotional": 300000.0, + "minNotional": 400000.0, + "maxNotional": 800000.0, "maintenanceMarginRate": 0.025, "maxLeverage": 20.0, "info": { "bracket": "4", "initialLeverage": "20", - "notionalCap": "300000", - "notionalFloor": "150000", + "notionalCap": "800000", + "notionalFloor": "400000", "maintMarginRatio": "0.025", - "cum": "950.0" + "cum": "2450.0" } }, { "tier": 5.0, "currency": "USDT", - "minNotional": 300000.0, - "maxNotional": 1500000.0, + "minNotional": 800000.0, + "maxNotional": 4000000.0, "maintenanceMarginRate": 0.05, "maxLeverage": 10.0, "info": { "bracket": "5", "initialLeverage": "10", - "notionalCap": "1500000", - "notionalFloor": "300000", + "notionalCap": "4000000", + "notionalFloor": "800000", "maintMarginRatio": "0.05", - "cum": "8450.0" + "cum": "22450.0" } }, { "tier": 6.0, "currency": "USDT", - "minNotional": 1500000.0, - "maxNotional": 3000000.0, + "minNotional": 4000000.0, + "maxNotional": 8000000.0, "maintenanceMarginRate": 0.1, "maxLeverage": 5.0, "info": { "bracket": "6", "initialLeverage": "5", - "notionalCap": "3000000", - "notionalFloor": "1500000", + "notionalCap": "8000000", + "notionalFloor": "4000000", "maintMarginRatio": "0.1", - "cum": "83450.0" + "cum": "222450.0" } }, { "tier": 7.0, "currency": "USDT", - "minNotional": 3000000.0, - "maxNotional": 3750000.0, + "minNotional": 8000000.0, + "maxNotional": 10000000.0, "maintenanceMarginRate": 0.125, "maxLeverage": 4.0, "info": { "bracket": "7", "initialLeverage": "4", - "notionalCap": "3750000", - "notionalFloor": "3000000", + "notionalCap": "10000000", + "notionalFloor": "8000000", "maintMarginRatio": "0.125", - "cum": "158450.0" + "cum": "422450.0" } }, { "tier": 8.0, "currency": "USDT", - "minNotional": 3750000.0, - "maxNotional": 7500000.0, + "minNotional": 10000000.0, + "maxNotional": 20000000.0, "maintenanceMarginRate": 0.25, "maxLeverage": 2.0, "info": { "bracket": "8", "initialLeverage": "2", - "notionalCap": "7500000", - "notionalFloor": "3750000", + "notionalCap": "20000000", + "notionalFloor": "10000000", "maintMarginRatio": "0.25", - "cum": "627200.0" + "cum": "1672450.0" } }, { "tier": 9.0, "currency": "USDT", - "minNotional": 7500000.0, - "maxNotional": 15000000.0, + "minNotional": 20000000.0, + "maxNotional": 40000000.0, "maintenanceMarginRate": 0.5, "maxLeverage": 1.0, "info": { "bracket": "9", "initialLeverage": "1", - "notionalCap": "15000000", - "notionalFloor": "7500000", + "notionalCap": "40000000", + "notionalFloor": "20000000", "maintMarginRatio": "0.5", - "cum": "2502200.0" + "cum": "6672450.0" } } ], @@ -15665,7 +16841,7 @@ } } ], - "ETH/USDT:USDT-240927": [ + "ETH/USDT:USDT-241227": [ { "tier": 1.0, "currency": "USDT", @@ -15795,7 +16971,7 @@ } } ], - "ETH/USDT:USDT-241227": [ + "ETH/USDT:USDT-250328": [ { "tier": 1.0, "currency": "USDT", @@ -16060,128 +17236,144 @@ "tier": 1.0, "currency": "USDT", "minNotional": 0.0, - "maxNotional": 5000.0, - "maintenanceMarginRate": 0.015, - "maxLeverage": 50.0, + "maxNotional": 10000.0, + "maintenanceMarginRate": 0.01, + "maxLeverage": 75.0, "info": { "bracket": "1", - "initialLeverage": "50", - "notionalCap": "5000", + "initialLeverage": "75", + "notionalCap": "10000", "notionalFloor": "0", - "maintMarginRatio": "0.015", + "maintMarginRatio": "0.01", "cum": "0.0" } }, { "tier": 2.0, "currency": "USDT", - "minNotional": 5000.0, - "maxNotional": 50000.0, - "maintenanceMarginRate": 0.02, - "maxLeverage": 25.0, + "minNotional": 10000.0, + "maxNotional": 20000.0, + "maintenanceMarginRate": 0.015, + "maxLeverage": 50.0, "info": { "bracket": "2", - "initialLeverage": "25", - "notionalCap": "50000", - "notionalFloor": "5000", - "maintMarginRatio": "0.02", - "cum": "25.0" + "initialLeverage": "50", + "notionalCap": "20000", + "notionalFloor": "10000", + "maintMarginRatio": "0.015", + "cum": "50.0" } }, { "tier": 3.0, "currency": "USDT", - "minNotional": 50000.0, + "minNotional": 20000.0, "maxNotional": 100000.0, - "maintenanceMarginRate": 0.025, - "maxLeverage": 20.0, + "maintenanceMarginRate": 0.02, + "maxLeverage": 25.0, "info": { "bracket": "3", - "initialLeverage": "20", + "initialLeverage": "25", "notionalCap": "100000", - "notionalFloor": "50000", - "maintMarginRatio": "0.025", - "cum": "275.0" + "notionalFloor": "20000", + "maintMarginRatio": "0.02", + "cum": "150.0" } }, { "tier": 4.0, "currency": "USDT", "minNotional": 100000.0, - "maxNotional": 1000000.0, - "maintenanceMarginRate": 0.05, - "maxLeverage": 10.0, + "maxNotional": 200000.0, + "maintenanceMarginRate": 0.025, + "maxLeverage": 20.0, "info": { "bracket": "4", - "initialLeverage": "10", - "notionalCap": "1000000", + "initialLeverage": "20", + "notionalCap": "200000", "notionalFloor": "100000", - "maintMarginRatio": "0.05", - "cum": "2775.0" + "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": "5", + "bracket": "6", "initialLeverage": "5", "notionalCap": "2000000", "notionalFloor": "1000000", "maintMarginRatio": "0.1", - "cum": "52775.0" + "cum": "55650.0" } }, { - "tier": 6.0, + "tier": 7.0, "currency": "USDT", "minNotional": 2000000.0, "maxNotional": 2500000.0, "maintenanceMarginRate": 0.125, "maxLeverage": 4.0, "info": { - "bracket": "6", + "bracket": "7", "initialLeverage": "4", "notionalCap": "2500000", "notionalFloor": "2000000", "maintMarginRatio": "0.125", - "cum": "102775.0" + "cum": "105650.0" } }, { - "tier": 7.0, + "tier": 8.0, "currency": "USDT", "minNotional": 2500000.0, "maxNotional": 5000000.0, "maintenanceMarginRate": 0.25, "maxLeverage": 2.0, "info": { - "bracket": "7", + "bracket": "8", "initialLeverage": "2", "notionalCap": "5000000", "notionalFloor": "2500000", "maintMarginRatio": "0.25", - "cum": "415275.0" + "cum": "418150.0" } }, { - "tier": 8.0, + "tier": 9.0, "currency": "USDT", "minNotional": 5000000.0, "maxNotional": 10000000.0, "maintenanceMarginRate": 0.5, "maxLeverage": 1.0, "info": { - "bracket": "8", + "bracket": "9", "initialLeverage": "1", "notionalCap": "10000000", "notionalFloor": "5000000", "maintMarginRatio": "0.5", - "cum": "1665275.0" + "cum": "1668150.0" } } ], @@ -16191,14 +17383,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" } }, @@ -16206,96 +17398,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": 100000.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": "100000", - "notionalFloor": "25000", - "maintMarginRatio": "0.05", - "cum": "675.0" + "initialLeverage": "25", + "notionalCap": "30000", + "notionalFloor": "10000", + "maintMarginRatio": "0.02", + "cum": "75.0" } }, { "tier": 4.0, "currency": "USDT", - "minNotional": 100000.0, - "maxNotional": 200000.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": "200000", - "notionalFloor": "100000", - "maintMarginRatio": "0.1", - "cum": "5675.0" + "initialLeverage": "20", + "notionalCap": "60000", + "notionalFloor": "30000", + "maintMarginRatio": "0.025", + "cum": "225.0" } }, { "tier": 5.0, "currency": "USDT", - "minNotional": 200000.0, - "maxNotional": 500000.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": "500000", - "notionalFloor": "200000", - "maintMarginRatio": "0.125", - "cum": "10675.0" + "initialLeverage": "10", + "notionalCap": "300000", + "notionalFloor": "60000", + "maintMarginRatio": "0.05", + "cum": "1725.0" } }, { "tier": 6.0, "currency": "USDT", - "minNotional": 500000.0, - "maxNotional": 1000000.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": "1000000", - "notionalFloor": "500000", - "maintMarginRatio": "0.25", - "cum": "73175.0" + "initialLeverage": "5", + "notionalCap": "600000", + "notionalFloor": "300000", + "maintMarginRatio": "0.1", + "cum": "16725.0" } }, { "tier": 7.0, "currency": "USDT", - "minNotional": 1000000.0, - "maxNotional": 2000000.0, + "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": "7", + "bracket": "9", "initialLeverage": "1", - "notionalCap": "2000000", - "notionalFloor": "1000000", + "notionalCap": "3000000", + "notionalFloor": "1500000", "maintMarginRatio": "0.5", - "cum": "323175.0" + "cum": "500475.0" } } ], @@ -16320,13 +17544,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" @@ -16335,113 +17559,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" } } ], @@ -16482,13 +17706,13 @@ "tier": 3.0, "currency": "USDT", "minNotional": 10000.0, - "maxNotional": 20000.0, + "maxNotional": 50000.0, "maintenanceMarginRate": 0.02, "maxLeverage": 25.0, "info": { "bracket": "3", "initialLeverage": "25", - "notionalCap": "20000", + "notionalCap": "50000", "notionalFloor": "10000", "maintMarginRatio": "0.02", "cum": "75.0" @@ -16497,97 +17721,97 @@ { "tier": 4.0, "currency": "USDT", - "minNotional": 20000.0, - "maxNotional": 40000.0, + "minNotional": 50000.0, + "maxNotional": 100000.0, "maintenanceMarginRate": 0.025, "maxLeverage": 20.0, "info": { "bracket": "4", "initialLeverage": "20", - "notionalCap": "40000", - "notionalFloor": "20000", + "notionalCap": "100000", + "notionalFloor": "50000", "maintMarginRatio": "0.025", - "cum": "175.0" + "cum": "325.0" } }, { "tier": 5.0, "currency": "USDT", - "minNotional": 40000.0, - "maxNotional": 200000.0, + "minNotional": 100000.0, + "maxNotional": 500000.0, "maintenanceMarginRate": 0.05, "maxLeverage": 10.0, "info": { "bracket": "5", "initialLeverage": "10", - "notionalCap": "200000", - "notionalFloor": "40000", + "notionalCap": "500000", + "notionalFloor": "100000", "maintMarginRatio": "0.05", - "cum": "1175.0" + "cum": "2825.0" } }, { "tier": 6.0, "currency": "USDT", - "minNotional": 200000.0, - "maxNotional": 400000.0, + "minNotional": 500000.0, + "maxNotional": 1000000.0, "maintenanceMarginRate": 0.1, "maxLeverage": 5.0, "info": { "bracket": "6", "initialLeverage": "5", - "notionalCap": "400000", - "notionalFloor": "200000", + "notionalCap": "1000000", + "notionalFloor": "500000", "maintMarginRatio": "0.1", - "cum": "11175.0" + "cum": "27825.0" } }, { "tier": 7.0, "currency": "USDT", - "minNotional": 400000.0, - "maxNotional": 500000.0, + "minNotional": 1000000.0, + "maxNotional": 1250000.0, "maintenanceMarginRate": 0.125, "maxLeverage": 4.0, "info": { "bracket": "7", "initialLeverage": "4", - "notionalCap": "500000", - "notionalFloor": "400000", + "notionalCap": "1250000", + "notionalFloor": "1000000", "maintMarginRatio": "0.125", - "cum": "21175.0" + "cum": "52825.0" } }, { "tier": 8.0, "currency": "USDT", - "minNotional": 500000.0, - "maxNotional": 1000000.0, + "minNotional": 1250000.0, + "maxNotional": 2500000.0, "maintenanceMarginRate": 0.25, "maxLeverage": 2.0, "info": { "bracket": "8", "initialLeverage": "2", - "notionalCap": "1000000", - "notionalFloor": "500000", + "notionalCap": "2500000", + "notionalFloor": "1250000", "maintMarginRatio": "0.25", - "cum": "83675.0" + "cum": "209075.0" } }, { "tier": 9.0, "currency": "USDT", - "minNotional": 1000000.0, - "maxNotional": 2000000.0, + "minNotional": 2500000.0, + "maxNotional": 5000000.0, "maintenanceMarginRate": 0.5, "maxLeverage": 1.0, "info": { "bracket": "9", "initialLeverage": "1", - "notionalCap": "2000000", - "notionalFloor": "1000000", + "notionalCap": "5000000", + "notionalFloor": "2500000", "maintMarginRatio": "0.5", - "cum": "333675.0" + "cum": "834075.0" } } ], @@ -16936,13 +18160,13 @@ "tier": 3.0, "currency": "USDT", "minNotional": 10000.0, - "maxNotional": 20000.0, + "maxNotional": 30000.0, "maintenanceMarginRate": 0.02, "maxLeverage": 25.0, "info": { "bracket": "3", "initialLeverage": "25", - "notionalCap": "20000", + "notionalCap": "30000", "notionalFloor": "10000", "maintMarginRatio": "0.02", "cum": "75.0" @@ -16951,97 +18175,97 @@ { "tier": 4.0, "currency": "USDT", - "minNotional": 20000.0, - "maxNotional": 40000.0, + "minNotional": 30000.0, + "maxNotional": 60000.0, "maintenanceMarginRate": 0.025, "maxLeverage": 20.0, "info": { "bracket": "4", "initialLeverage": "20", - "notionalCap": "40000", - "notionalFloor": "20000", + "notionalCap": "60000", + "notionalFloor": "30000", "maintMarginRatio": "0.025", - "cum": "175.0" + "cum": "225.0" } }, { "tier": 5.0, "currency": "USDT", - "minNotional": 40000.0, - "maxNotional": 200000.0, + "minNotional": 60000.0, + "maxNotional": 300000.0, "maintenanceMarginRate": 0.05, "maxLeverage": 10.0, "info": { "bracket": "5", "initialLeverage": "10", - "notionalCap": "200000", - "notionalFloor": "40000", + "notionalCap": "300000", + "notionalFloor": "60000", "maintMarginRatio": "0.05", - "cum": "1175.0" + "cum": "1725.0" } }, { "tier": 6.0, "currency": "USDT", - "minNotional": 200000.0, - "maxNotional": 400000.0, + "minNotional": 300000.0, + "maxNotional": 600000.0, "maintenanceMarginRate": 0.1, "maxLeverage": 5.0, "info": { "bracket": "6", "initialLeverage": "5", - "notionalCap": "400000", - "notionalFloor": "200000", + "notionalCap": "600000", + "notionalFloor": "300000", "maintMarginRatio": "0.1", - "cum": "11175.0" + "cum": "16725.0" } }, { "tier": 7.0, "currency": "USDT", - "minNotional": 400000.0, - "maxNotional": 500000.0, + "minNotional": 600000.0, + "maxNotional": 750000.0, "maintenanceMarginRate": 0.125, "maxLeverage": 4.0, "info": { "bracket": "7", "initialLeverage": "4", - "notionalCap": "500000", - "notionalFloor": "400000", + "notionalCap": "750000", + "notionalFloor": "600000", "maintMarginRatio": "0.125", - "cum": "21175.0" + "cum": "31725.0" } }, { "tier": 8.0, "currency": "USDT", - "minNotional": 500000.0, - "maxNotional": 1000000.0, + "minNotional": 750000.0, + "maxNotional": 1500000.0, "maintenanceMarginRate": 0.25, "maxLeverage": 2.0, "info": { "bracket": "8", "initialLeverage": "2", - "notionalCap": "1000000", - "notionalFloor": "500000", + "notionalCap": "1500000", + "notionalFloor": "750000", "maintMarginRatio": "0.25", - "cum": "83675.0" + "cum": "125475.0" } }, { "tier": 9.0, "currency": "USDT", - "minNotional": 1000000.0, - "maxNotional": 2000000.0, + "minNotional": 1500000.0, + "maxNotional": 3000000.0, "maintenanceMarginRate": 0.5, "maxLeverage": 1.0, "info": { "bracket": "9", "initialLeverage": "1", - "notionalCap": "2000000", - "notionalFloor": "1000000", + "notionalCap": "3000000", + "notionalFloor": "1500000", "maintMarginRatio": "0.5", - "cum": "333675.0" + "cum": "500475.0" } } ], @@ -18059,14 +19283,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" } }, @@ -18074,96 +19298,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": 100000.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": "100000", - "notionalFloor": "25000", - "maintMarginRatio": "0.05", - "cum": "675.0" + "initialLeverage": "25", + "notionalCap": "30000", + "notionalFloor": "10000", + "maintMarginRatio": "0.02", + "cum": "75.0" } }, { "tier": 4.0, "currency": "USDT", - "minNotional": 100000.0, - "maxNotional": 200000.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": "200000", - "notionalFloor": "100000", - "maintMarginRatio": "0.1", - "cum": "5675.0" + "initialLeverage": "20", + "notionalCap": "60000", + "notionalFloor": "30000", + "maintMarginRatio": "0.025", + "cum": "225.0" } }, { "tier": 5.0, "currency": "USDT", - "minNotional": 200000.0, - "maxNotional": 500000.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": "500000", - "notionalFloor": "200000", - "maintMarginRatio": "0.125", - "cum": "10675.0" + "initialLeverage": "10", + "notionalCap": "300000", + "notionalFloor": "60000", + "maintMarginRatio": "0.05", + "cum": "1725.0" } }, { "tier": 6.0, "currency": "USDT", - "minNotional": 500000.0, - "maxNotional": 1000000.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": "1000000", - "notionalFloor": "500000", - "maintMarginRatio": "0.25", - "cum": "73175.0" + "initialLeverage": "5", + "notionalCap": "600000", + "notionalFloor": "300000", + "maintMarginRatio": "0.1", + "cum": "16725.0" } }, { "tier": 7.0, "currency": "USDT", - "minNotional": 1000000.0, - "maxNotional": 2000000.0, + "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": "7", + "bracket": "9", "initialLeverage": "1", - "notionalCap": "2000000", - "notionalFloor": "1000000", + "notionalCap": "3000000", + "notionalFloor": "1500000", "maintMarginRatio": "0.5", - "cum": "323175.0" + "cum": "500475.0" } } ], @@ -18769,6 +20025,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, @@ -18889,14 +20291,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" } }, @@ -18904,112 +20306,128 @@ "tier": 2.0, "currency": "USDT", "minNotional": 5000.0, - "maxNotional": 20000.0, - "maintenanceMarginRate": 0.02, - "maxLeverage": 25.0, + "maxNotional": 10000.0, + "maintenanceMarginRate": 0.015, + "maxLeverage": 50.0, "info": { "bracket": "2", - "initialLeverage": "25", - "notionalCap": "20000", + "initialLeverage": "50", + "notionalCap": "10000", "notionalFloor": "5000", - "maintMarginRatio": "0.02", + "maintMarginRatio": "0.015", "cum": "25.0" } }, { "tier": 3.0, "currency": "USDT", - "minNotional": 20000.0, - "maxNotional": 25000.0, - "maintenanceMarginRate": 0.025, - "maxLeverage": 20.0, + "minNotional": 10000.0, + "maxNotional": 30000.0, + "maintenanceMarginRate": 0.02, + "maxLeverage": 25.0, "info": { "bracket": "3", - "initialLeverage": "20", - "notionalCap": "25000", - "notionalFloor": "20000", - "maintMarginRatio": "0.025", - "cum": "125.0" + "initialLeverage": "25", + "notionalCap": "30000", + "notionalFloor": "10000", + "maintMarginRatio": "0.02", + "cum": "75.0" } }, { "tier": 4.0, "currency": "USDT", - "minNotional": 25000.0, - "maxNotional": 200000.0, - "maintenanceMarginRate": 0.05, - "maxLeverage": 10.0, + "minNotional": 30000.0, + "maxNotional": 60000.0, + "maintenanceMarginRate": 0.025, + "maxLeverage": 20.0, "info": { "bracket": "4", - "initialLeverage": "10", - "notionalCap": "200000", - "notionalFloor": "25000", - "maintMarginRatio": "0.05", - "cum": "750.0" + "initialLeverage": "20", + "notionalCap": "60000", + "notionalFloor": "30000", + "maintMarginRatio": "0.025", + "cum": "225.0" } }, { "tier": 5.0, "currency": "USDT", - "minNotional": 200000.0, - "maxNotional": 400000.0, - "maintenanceMarginRate": 0.1, - "maxLeverage": 5.0, + "minNotional": 60000.0, + "maxNotional": 300000.0, + "maintenanceMarginRate": 0.05, + "maxLeverage": 10.0, "info": { "bracket": "5", - "initialLeverage": "5", - "notionalCap": "400000", - "notionalFloor": "200000", - "maintMarginRatio": "0.1", - "cum": "10750.0" + "initialLeverage": "10", + "notionalCap": "300000", + "notionalFloor": "60000", + "maintMarginRatio": "0.05", + "cum": "1725.0" } }, { "tier": 6.0, "currency": "USDT", - "minNotional": 400000.0, - "maxNotional": 500000.0, - "maintenanceMarginRate": 0.125, - "maxLeverage": 4.0, + "minNotional": 300000.0, + "maxNotional": 600000.0, + "maintenanceMarginRate": 0.1, + "maxLeverage": 5.0, "info": { "bracket": "6", - "initialLeverage": "4", - "notionalCap": "500000", - "notionalFloor": "400000", - "maintMarginRatio": "0.125", - "cum": "20750.0" + "initialLeverage": "5", + "notionalCap": "600000", + "notionalFloor": "300000", + "maintMarginRatio": "0.1", + "cum": "16725.0" } }, { "tier": 7.0, "currency": "USDT", - "minNotional": 500000.0, - "maxNotional": 1000000.0, - "maintenanceMarginRate": 0.25, - "maxLeverage": 2.0, + "minNotional": 600000.0, + "maxNotional": 750000.0, + "maintenanceMarginRate": 0.125, + "maxLeverage": 4.0, "info": { "bracket": "7", - "initialLeverage": "2", - "notionalCap": "1000000", - "notionalFloor": "500000", - "maintMarginRatio": "0.25", - "cum": "83250.0" + "initialLeverage": "4", + "notionalCap": "750000", + "notionalFloor": "600000", + "maintMarginRatio": "0.125", + "cum": "31725.0" } }, { "tier": 8.0, "currency": "USDT", - "minNotional": 1000000.0, - "maxNotional": 2000000.0, + "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": "8", + "bracket": "9", "initialLeverage": "1", - "notionalCap": "2000000", - "notionalFloor": "1000000", + "notionalCap": "3000000", + "notionalFloor": "1500000", "maintMarginRatio": "0.5", - "cum": "333250.0" + "cum": "500475.0" } } ], @@ -19469,6 +20887,152 @@ } } ], + "HMSTR/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": 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": "9", + "initialLeverage": "1", + "notionalCap": "10000000", + "notionalFloor": "5000000", + "maintMarginRatio": "0.5", + "cum": "1668150.0" + } + } + ], "HOOK/USDT:USDT": [ { "tier": 1.0, @@ -19670,13 +21234,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" @@ -19685,97 +21249,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" } } ], @@ -21410,128 +23006,144 @@ "tier": 1.0, "currency": "USDT", "minNotional": 0.0, - "maxNotional": 5000.0, - "maintenanceMarginRate": 0.015, - "maxLeverage": 50.0, + "maxNotional": 10000.0, + "maintenanceMarginRate": 0.01, + "maxLeverage": 75.0, "info": { "bracket": "1", - "initialLeverage": "50", - "notionalCap": "5000", + "initialLeverage": "75", + "notionalCap": "10000", "notionalFloor": "0", - "maintMarginRatio": "0.015", + "maintMarginRatio": "0.01", "cum": "0.0" } }, { "tier": 2.0, "currency": "USDT", - "minNotional": 5000.0, - "maxNotional": 25000.0, - "maintenanceMarginRate": 0.02, - "maxLeverage": 25.0, + "minNotional": 10000.0, + "maxNotional": 30000.0, + "maintenanceMarginRate": 0.015, + "maxLeverage": 50.0, "info": { "bracket": "2", - "initialLeverage": "25", - "notionalCap": "25000", - "notionalFloor": "5000", - "maintMarginRatio": "0.02", - "cum": "25.0" + "initialLeverage": "50", + "notionalCap": "30000", + "notionalFloor": "10000", + "maintMarginRatio": "0.015", + "cum": "50.0" } }, { "tier": 3.0, "currency": "USDT", - "minNotional": 25000.0, - "maxNotional": 80000.0, - "maintenanceMarginRate": 0.025, - "maxLeverage": 20.0, + "minNotional": 30000.0, + "maxNotional": 150000.0, + "maintenanceMarginRate": 0.02, + "maxLeverage": 25.0, "info": { "bracket": "3", - "initialLeverage": "20", - "notionalCap": "80000", - "notionalFloor": "25000", - "maintMarginRatio": "0.025", - "cum": "150.0" + "initialLeverage": "25", + "notionalCap": "150000", + "notionalFloor": "30000", + "maintMarginRatio": "0.02", + "cum": "200.0" } }, { "tier": 4.0, "currency": "USDT", - "minNotional": 80000.0, - "maxNotional": 800000.0, - "maintenanceMarginRate": 0.05, - "maxLeverage": 10.0, + "minNotional": 150000.0, + "maxNotional": 300000.0, + "maintenanceMarginRate": 0.025, + "maxLeverage": 20.0, "info": { "bracket": "4", - "initialLeverage": "10", - "notionalCap": "800000", - "notionalFloor": "80000", - "maintMarginRatio": "0.05", - "cum": "2150.0" + "initialLeverage": "20", + "notionalCap": "300000", + "notionalFloor": "150000", + "maintMarginRatio": "0.025", + "cum": "950.0" } }, { "tier": 5.0, "currency": "USDT", - "minNotional": 800000.0, - "maxNotional": 1600000.0, - "maintenanceMarginRate": 0.1, - "maxLeverage": 5.0, + "minNotional": 300000.0, + "maxNotional": 1500000.0, + "maintenanceMarginRate": 0.05, + "maxLeverage": 10.0, "info": { "bracket": "5", - "initialLeverage": "5", - "notionalCap": "1600000", - "notionalFloor": "800000", - "maintMarginRatio": "0.1", - "cum": "42150.0" + "initialLeverage": "10", + "notionalCap": "1500000", + "notionalFloor": "300000", + "maintMarginRatio": "0.05", + "cum": "8450.0" } }, { "tier": 6.0, "currency": "USDT", - "minNotional": 1600000.0, - "maxNotional": 2000000.0, - "maintenanceMarginRate": 0.125, - "maxLeverage": 4.0, + "minNotional": 1500000.0, + "maxNotional": 3000000.0, + "maintenanceMarginRate": 0.1, + "maxLeverage": 5.0, "info": { "bracket": "6", - "initialLeverage": "4", - "notionalCap": "2000000", - "notionalFloor": "1600000", - "maintMarginRatio": "0.125", - "cum": "82150.0" + "initialLeverage": "5", + "notionalCap": "3000000", + "notionalFloor": "1500000", + "maintMarginRatio": "0.1", + "cum": "83450.0" } }, { "tier": 7.0, "currency": "USDT", - "minNotional": 2000000.0, - "maxNotional": 4000000.0, - "maintenanceMarginRate": 0.25, - "maxLeverage": 2.0, + "minNotional": 3000000.0, + "maxNotional": 3750000.0, + "maintenanceMarginRate": 0.125, + "maxLeverage": 4.0, "info": { "bracket": "7", - "initialLeverage": "2", - "notionalCap": "4000000", - "notionalFloor": "2000000", - "maintMarginRatio": "0.25", - "cum": "332150.0" + "initialLeverage": "4", + "notionalCap": "3750000", + "notionalFloor": "3000000", + "maintMarginRatio": "0.125", + "cum": "158450.0" } }, { "tier": 8.0, "currency": "USDT", - "minNotional": 4000000.0, - "maxNotional": 8000000.0, + "minNotional": 3750000.0, + "maxNotional": 7500000.0, + "maintenanceMarginRate": 0.25, + "maxLeverage": 2.0, + "info": { + "bracket": "8", + "initialLeverage": "2", + "notionalCap": "7500000", + "notionalFloor": "3750000", + "maintMarginRatio": "0.25", + "cum": "627200.0" + } + }, + { + "tier": 9.0, + "currency": "USDT", + "minNotional": 7500000.0, + "maxNotional": 15000000.0, "maintenanceMarginRate": 0.5, "maxLeverage": 1.0, "info": { - "bracket": "8", + "bracket": "9", "initialLeverage": "1", - "notionalCap": "8000000", - "notionalFloor": "4000000", + "notionalCap": "15000000", + "notionalFloor": "7500000", "maintMarginRatio": "0.5", - "cum": "1332150.0" + "cum": "2502200.0" } } ], @@ -21541,14 +23153,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" } }, @@ -21556,64 +23168,64 @@ "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": 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": "675.0" + "initialLeverage": "25", + "notionalCap": "50000", + "notionalFloor": "10000", + "maintMarginRatio": "0.02", + "cum": "75.0" } }, { "tier": 4.0, "currency": "USDT", - "minNotional": 100000.0, - "maxNotional": 200000.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": "200000", - "notionalFloor": "100000", - "maintMarginRatio": "0.1", - "cum": "5675.0" + "initialLeverage": "20", + "notionalCap": "100000", + "notionalFloor": "50000", + "maintMarginRatio": "0.025", + "cum": "325.0" } }, { "tier": 5.0, "currency": "USDT", - "minNotional": 200000.0, + "minNotional": 100000.0, "maxNotional": 500000.0, - "maintenanceMarginRate": 0.125, - "maxLeverage": 4.0, + "maintenanceMarginRate": 0.05, + "maxLeverage": 10.0, "info": { "bracket": "5", - "initialLeverage": "4", + "initialLeverage": "10", "notionalCap": "500000", - "notionalFloor": "200000", - "maintMarginRatio": "0.125", - "cum": "10675.0" + "notionalFloor": "100000", + "maintMarginRatio": "0.05", + "cum": "2825.0" } }, { @@ -21621,31 +23233,63 @@ "currency": "USDT", "minNotional": 500000.0, "maxNotional": 1000000.0, - "maintenanceMarginRate": 0.25, - "maxLeverage": 2.0, + "maintenanceMarginRate": 0.1, + "maxLeverage": 5.0, "info": { "bracket": "6", - "initialLeverage": "2", + "initialLeverage": "5", "notionalCap": "1000000", "notionalFloor": "500000", - "maintMarginRatio": "0.25", - "cum": "73175.0" + "maintMarginRatio": "0.1", + "cum": "27825.0" } }, { "tier": 7.0, "currency": "USDT", "minNotional": 1000000.0, - "maxNotional": 2000000.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": "7", + "bracket": "9", "initialLeverage": "1", - "notionalCap": "2000000", - "notionalFloor": "1000000", + "notionalCap": "5000000", + "notionalFloor": "2500000", "maintMarginRatio": "0.5", - "cum": "323175.0" + "cum": "834075.0" } } ], @@ -22420,10 +24064,10 @@ "minNotional": 0.0, "maxNotional": 5000.0, "maintenanceMarginRate": 0.006, - "maxLeverage": 50.0, + "maxLeverage": 75.0, "info": { "bracket": "1", - "initialLeverage": "50", + "initialLeverage": "75", "notionalCap": "5000", "notionalFloor": "0", "maintMarginRatio": "0.006", @@ -22436,10 +24080,10 @@ "minNotional": 5000.0, "maxNotional": 50000.0, "maintenanceMarginRate": 0.01, - "maxLeverage": 25.0, + "maxLeverage": 50.0, "info": { "bracket": "2", - "initialLeverage": "25", + "initialLeverage": "50", "notionalCap": "50000", "notionalFloor": "5000", "maintMarginRatio": "0.01", @@ -22450,96 +24094,128 @@ "tier": 3.0, "currency": "USDT", "minNotional": 50000.0, - "maxNotional": 400000.0, - "maintenanceMarginRate": 0.025, - "maxLeverage": 20.0, + "maxNotional": 80000.0, + "maintenanceMarginRate": 0.015, + "maxLeverage": 40.0, "info": { "bracket": "3", - "initialLeverage": "20", - "notionalCap": "400000", + "initialLeverage": "40", + "notionalCap": "80000", "notionalFloor": "50000", - "maintMarginRatio": "0.025", - "cum": "770.0" + "maintMarginRatio": "0.015", + "cum": "270.0" } }, { "tier": 4.0, "currency": "USDT", - "minNotional": 400000.0, - "maxNotional": 800000.0, - "maintenanceMarginRate": 0.05, - "maxLeverage": 10.0, + "minNotional": 80000.0, + "maxNotional": 200000.0, + "maintenanceMarginRate": 0.02, + "maxLeverage": 25.0, "info": { "bracket": "4", - "initialLeverage": "10", - "notionalCap": "800000", - "notionalFloor": "400000", - "maintMarginRatio": "0.05", - "cum": "10770.0" + "initialLeverage": "25", + "notionalCap": "200000", + "notionalFloor": "80000", + "maintMarginRatio": "0.02", + "cum": "670.0" } }, { "tier": 5.0, "currency": "USDT", - "minNotional": 800000.0, - "maxNotional": 2000000.0, - "maintenanceMarginRate": 0.1, - "maxLeverage": 5.0, + "minNotional": 200000.0, + "maxNotional": 400000.0, + "maintenanceMarginRate": 0.025, + "maxLeverage": 20.0, "info": { "bracket": "5", - "initialLeverage": "5", - "notionalCap": "2000000", - "notionalFloor": "800000", - "maintMarginRatio": "0.1", - "cum": "50770.0" + "initialLeverage": "20", + "notionalCap": "400000", + "notionalFloor": "200000", + "maintMarginRatio": "0.025", + "cum": "1670.0" } }, { "tier": 6.0, "currency": "USDT", - "minNotional": 2000000.0, - "maxNotional": 5000000.0, - "maintenanceMarginRate": 0.125, - "maxLeverage": 4.0, + "minNotional": 400000.0, + "maxNotional": 2000000.0, + "maintenanceMarginRate": 0.05, + "maxLeverage": 10.0, "info": { "bracket": "6", - "initialLeverage": "4", - "notionalCap": "5000000", - "notionalFloor": "2000000", - "maintMarginRatio": "0.125", - "cum": "100770.0" + "initialLeverage": "10", + "notionalCap": "2000000", + "notionalFloor": "400000", + "maintMarginRatio": "0.05", + "cum": "11670.0" } }, { "tier": 7.0, "currency": "USDT", + "minNotional": 2000000.0, + "maxNotional": 4000000.0, + "maintenanceMarginRate": 0.1, + "maxLeverage": 5.0, + "info": { + "bracket": "7", + "initialLeverage": "5", + "notionalCap": "4000000", + "notionalFloor": "2000000", + "maintMarginRatio": "0.1", + "cum": "111670.0" + } + }, + { + "tier": 8.0, + "currency": "USDT", + "minNotional": 4000000.0, + "maxNotional": 5000000.0, + "maintenanceMarginRate": 0.125, + "maxLeverage": 4.0, + "info": { + "bracket": "8", + "initialLeverage": "4", + "notionalCap": "5000000", + "notionalFloor": "4000000", + "maintMarginRatio": "0.125", + "cum": "211670.0" + } + }, + { + "tier": 9.0, + "currency": "USDT", "minNotional": 5000000.0, "maxNotional": 12000000.0, "maintenanceMarginRate": 0.25, "maxLeverage": 2.0, "info": { - "bracket": "7", + "bracket": "9", "initialLeverage": "2", "notionalCap": "12000000", "notionalFloor": "5000000", "maintMarginRatio": "0.25", - "cum": "725770.0" + "cum": "836670.0" } }, { - "tier": 8.0, + "tier": 10.0, "currency": "USDT", "minNotional": 12000000.0, "maxNotional": 20000000.0, "maintenanceMarginRate": 0.5, "maxLeverage": 1.0, "info": { - "bracket": "8", + "bracket": "10", "initialLeverage": "1", "notionalCap": "20000000", "notionalFloor": "12000000", "maintMarginRatio": "0.5", - "cum": "3725770.0" + "cum": "3836670.0" } } ], @@ -25443,14 +27119,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" } }, @@ -25458,112 +27134,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" } } ], @@ -25702,13 +27394,13 @@ "tier": 2.0, "currency": "USDT", "minNotional": 10000.0, - "maxNotional": 20000.0, + "maxNotional": 60000.0, "maintenanceMarginRate": 0.015, "maxLeverage": 50.0, "info": { "bracket": "2", "initialLeverage": "50", - "notionalCap": "20000", + "notionalCap": "60000", "notionalFloor": "10000", "maintMarginRatio": "0.015", "cum": "50.0" @@ -25717,113 +27409,113 @@ { "tier": 3.0, "currency": "USDT", - "minNotional": 20000.0, - "maxNotional": 100000.0, + "minNotional": 60000.0, + "maxNotional": 300000.0, "maintenanceMarginRate": 0.02, "maxLeverage": 25.0, "info": { "bracket": "3", "initialLeverage": "25", - "notionalCap": "100000", - "notionalFloor": "20000", + "notionalCap": "300000", + "notionalFloor": "60000", "maintMarginRatio": "0.02", - "cum": "150.0" + "cum": "350.0" } }, { "tier": 4.0, "currency": "USDT", - "minNotional": 100000.0, - "maxNotional": 200000.0, + "minNotional": 300000.0, + "maxNotional": 600000.0, "maintenanceMarginRate": 0.025, "maxLeverage": 20.0, "info": { "bracket": "4", "initialLeverage": "20", - "notionalCap": "200000", - "notionalFloor": "100000", + "notionalCap": "600000", + "notionalFloor": "300000", "maintMarginRatio": "0.025", - "cum": "650.0" + "cum": "1850.0" } }, { "tier": 5.0, "currency": "USDT", - "minNotional": 200000.0, - "maxNotional": 1000000.0, + "minNotional": 600000.0, + "maxNotional": 3000000.0, "maintenanceMarginRate": 0.05, "maxLeverage": 10.0, "info": { "bracket": "5", "initialLeverage": "10", - "notionalCap": "1000000", - "notionalFloor": "200000", + "notionalCap": "3000000", + "notionalFloor": "600000", "maintMarginRatio": "0.05", - "cum": "5650.0" + "cum": "16850.0" } }, { "tier": 6.0, "currency": "USDT", - "minNotional": 1000000.0, - "maxNotional": 2000000.0, + "minNotional": 3000000.0, + "maxNotional": 6000000.0, "maintenanceMarginRate": 0.1, "maxLeverage": 5.0, "info": { "bracket": "6", "initialLeverage": "5", - "notionalCap": "2000000", - "notionalFloor": "1000000", + "notionalCap": "6000000", + "notionalFloor": "3000000", "maintMarginRatio": "0.1", - "cum": "55650.0" + "cum": "166850.0" } }, { "tier": 7.0, "currency": "USDT", - "minNotional": 2000000.0, - "maxNotional": 2500000.0, + "minNotional": 6000000.0, + "maxNotional": 7500000.0, "maintenanceMarginRate": 0.125, "maxLeverage": 4.0, "info": { "bracket": "7", "initialLeverage": "4", - "notionalCap": "2500000", - "notionalFloor": "2000000", + "notionalCap": "7500000", + "notionalFloor": "6000000", "maintMarginRatio": "0.125", - "cum": "105650.0" + "cum": "316850.0" } }, { "tier": 8.0, "currency": "USDT", - "minNotional": 2500000.0, - "maxNotional": 5000000.0, + "minNotional": 7500000.0, + "maxNotional": 15000000.0, "maintenanceMarginRate": 0.25, "maxLeverage": 2.0, "info": { "bracket": "8", "initialLeverage": "2", - "notionalCap": "5000000", - "notionalFloor": "2500000", + "notionalCap": "15000000", + "notionalFloor": "7500000", "maintMarginRatio": "0.25", - "cum": "418150.0" + "cum": "1254350.0" } }, { "tier": 9.0, "currency": "USDT", - "minNotional": 5000000.0, - "maxNotional": 10000000.0, + "minNotional": 15000000.0, + "maxNotional": 30000000.0, "maintenanceMarginRate": 0.5, "maxLeverage": 1.0, "info": { "bracket": "9", "initialLeverage": "1", - "notionalCap": "10000000", - "notionalFloor": "5000000", + "notionalCap": "30000000", + "notionalFloor": "15000000", "maintMarginRatio": "0.5", - "cum": "1668150.0" + "cum": "5004350.0" } } ], @@ -25948,10 +27640,10 @@ "minNotional": 0.0, "maxNotional": 5000.0, "maintenanceMarginRate": 0.006, - "maxLeverage": 50.0, + "maxLeverage": 75.0, "info": { "bracket": "1", - "initialLeverage": "50", + "initialLeverage": "75", "notionalCap": "5000", "notionalFloor": "0", "maintMarginRatio": "0.006", @@ -25964,10 +27656,10 @@ "minNotional": 5000.0, "maxNotional": 25000.0, "maintenanceMarginRate": 0.01, - "maxLeverage": 25.0, + "maxLeverage": 50.0, "info": { "bracket": "2", - "initialLeverage": "25", + "initialLeverage": "50", "notionalCap": "25000", "notionalFloor": "5000", "maintMarginRatio": "0.01", @@ -25978,96 +27670,274 @@ "tier": 3.0, "currency": "USDT", "minNotional": 25000.0, - "maxNotional": 450000.0, - "maintenanceMarginRate": 0.025, - "maxLeverage": 20.0, + "maxNotional": 40000.0, + "maintenanceMarginRate": 0.015, + "maxLeverage": 40.0, "info": { "bracket": "3", - "initialLeverage": "20", - "notionalCap": "450000", + "initialLeverage": "40", + "notionalCap": "40000", "notionalFloor": "25000", - "maintMarginRatio": "0.025", - "cum": "395.0" + "maintMarginRatio": "0.015", + "cum": "145.0" } }, { "tier": 4.0, "currency": "USDT", - "minNotional": 450000.0, - "maxNotional": 900000.0, - "maintenanceMarginRate": 0.05, - "maxLeverage": 10.0, + "minNotional": 40000.0, + "maxNotional": 200000.0, + "maintenanceMarginRate": 0.02, + "maxLeverage": 25.0, "info": { "bracket": "4", - "initialLeverage": "10", - "notionalCap": "900000", - "notionalFloor": "450000", - "maintMarginRatio": "0.05", - "cum": "11645.0" + "initialLeverage": "25", + "notionalCap": "200000", + "notionalFloor": "40000", + "maintMarginRatio": "0.02", + "cum": "345.0" } }, { "tier": 5.0, "currency": "USDT", - "minNotional": 900000.0, - "maxNotional": 2400000.0, - "maintenanceMarginRate": 0.1, - "maxLeverage": 5.0, + "minNotional": 200000.0, + "maxNotional": 450000.0, + "maintenanceMarginRate": 0.025, + "maxLeverage": 20.0, "info": { "bracket": "5", - "initialLeverage": "5", - "notionalCap": "2400000", - "notionalFloor": "900000", - "maintMarginRatio": "0.1", - "cum": "56645.0" + "initialLeverage": "20", + "notionalCap": "450000", + "notionalFloor": "200000", + "maintMarginRatio": "0.025", + "cum": "1345.0" } }, { "tier": 6.0, "currency": "USDT", - "minNotional": 2400000.0, - "maxNotional": 3000000.0, - "maintenanceMarginRate": 0.125, - "maxLeverage": 4.0, + "minNotional": 450000.0, + "maxNotional": 2000000.0, + "maintenanceMarginRate": 0.05, + "maxLeverage": 10.0, "info": { "bracket": "6", - "initialLeverage": "4", - "notionalCap": "3000000", - "notionalFloor": "2400000", - "maintMarginRatio": "0.125", - "cum": "116645.0" + "initialLeverage": "10", + "notionalCap": "2000000", + "notionalFloor": "450000", + "maintMarginRatio": "0.05", + "cum": "12595.0" } }, { "tier": 7.0, "currency": "USDT", - "minNotional": 3000000.0, - "maxNotional": 9000000.0, - "maintenanceMarginRate": 0.25, - "maxLeverage": 2.0, + "minNotional": 2000000.0, + "maxNotional": 4000000.0, + "maintenanceMarginRate": 0.1, + "maxLeverage": 5.0, "info": { "bracket": "7", - "initialLeverage": "2", - "notionalCap": "9000000", - "notionalFloor": "3000000", - "maintMarginRatio": "0.25", - "cum": "491645.0" + "initialLeverage": "5", + "notionalCap": "4000000", + "notionalFloor": "2000000", + "maintMarginRatio": "0.1", + "cum": "112595.0" } }, { "tier": 8.0, "currency": "USDT", - "minNotional": 9000000.0, - "maxNotional": 15000000.0, + "minNotional": 4000000.0, + "maxNotional": 5000000.0, + "maintenanceMarginRate": 0.125, + "maxLeverage": 4.0, + "info": { + "bracket": "8", + "initialLeverage": "4", + "notionalCap": "5000000", + "notionalFloor": "4000000", + "maintMarginRatio": "0.125", + "cum": "212595.0" + } + }, + { + "tier": 9.0, + "currency": "USDT", + "minNotional": 5000000.0, + "maxNotional": 10000000.0, + "maintenanceMarginRate": 0.25, + "maxLeverage": 2.0, + "info": { + "bracket": "9", + "initialLeverage": "2", + "notionalCap": "10000000", + "notionalFloor": "5000000", + "maintMarginRatio": "0.25", + "cum": "837595.0" + } + }, + { + "tier": 10.0, + "currency": "USDT", + "minNotional": 10000000.0, + "maxNotional": 20000000.0, "maintenanceMarginRate": 0.5, "maxLeverage": 1.0, "info": { - "bracket": "8", + "bracket": "10", "initialLeverage": "1", - "notionalCap": "15000000", - "notionalFloor": "9000000", + "notionalCap": "20000000", + "notionalFloor": "10000000", "maintMarginRatio": "0.5", - "cum": "2741645.0" + "cum": "3337595.0" + } + } + ], + "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" } } ], @@ -26305,14 +28175,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" } }, @@ -26320,64 +28190,64 @@ "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": 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": "675.0" + "initialLeverage": "25", + "notionalCap": "50000", + "notionalFloor": "10000", + "maintMarginRatio": "0.02", + "cum": "75.0" } }, { "tier": 4.0, "currency": "USDT", - "minNotional": 100000.0, - "maxNotional": 200000.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": "200000", - "notionalFloor": "100000", - "maintMarginRatio": "0.1", - "cum": "5675.0" + "initialLeverage": "20", + "notionalCap": "100000", + "notionalFloor": "50000", + "maintMarginRatio": "0.025", + "cum": "325.0" } }, { "tier": 5.0, "currency": "USDT", - "minNotional": 200000.0, + "minNotional": 100000.0, "maxNotional": 500000.0, - "maintenanceMarginRate": 0.125, - "maxLeverage": 4.0, + "maintenanceMarginRate": 0.05, + "maxLeverage": 10.0, "info": { "bracket": "5", - "initialLeverage": "4", + "initialLeverage": "10", "notionalCap": "500000", - "notionalFloor": "200000", - "maintMarginRatio": "0.125", - "cum": "10675.0" + "notionalFloor": "100000", + "maintMarginRatio": "0.05", + "cum": "2825.0" } }, { @@ -26385,31 +28255,63 @@ "currency": "USDT", "minNotional": 500000.0, "maxNotional": 1000000.0, - "maintenanceMarginRate": 0.25, - "maxLeverage": 2.0, + "maintenanceMarginRate": 0.1, + "maxLeverage": 5.0, "info": { "bracket": "6", - "initialLeverage": "2", + "initialLeverage": "5", "notionalCap": "1000000", "notionalFloor": "500000", - "maintMarginRatio": "0.25", - "cum": "73175.0" + "maintMarginRatio": "0.1", + "cum": "27825.0" } }, { "tier": 7.0, "currency": "USDT", "minNotional": 1000000.0, - "maxNotional": 2000000.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": "7", + "bracket": "9", "initialLeverage": "1", - "notionalCap": "2000000", - "notionalFloor": "1000000", + "notionalCap": "5000000", + "notionalFloor": "2500000", "maintMarginRatio": "0.5", - "cum": "323175.0" + "cum": "834075.0" } } ], @@ -26564,13 +28466,13 @@ "tier": 2.0, "currency": "USDT", "minNotional": 10000.0, - "maxNotional": 40000.0, + "maxNotional": 100000.0, "maintenanceMarginRate": 0.015, "maxLeverage": 50.0, "info": { "bracket": "2", "initialLeverage": "50", - "notionalCap": "40000", + "notionalCap": "100000", "notionalFloor": "10000", "maintMarginRatio": "0.015", "cum": "50.0" @@ -26579,113 +28481,113 @@ { "tier": 3.0, "currency": "USDT", - "minNotional": 40000.0, - "maxNotional": 200000.0, + "minNotional": 100000.0, + "maxNotional": 500000.0, "maintenanceMarginRate": 0.02, "maxLeverage": 25.0, "info": { "bracket": "3", "initialLeverage": "25", - "notionalCap": "200000", - "notionalFloor": "40000", + "notionalCap": "500000", + "notionalFloor": "100000", "maintMarginRatio": "0.02", - "cum": "250.0" + "cum": "550.0" } }, { "tier": 4.0, "currency": "USDT", - "minNotional": 200000.0, - "maxNotional": 750000.0, + "minNotional": 500000.0, + "maxNotional": 1000000.0, "maintenanceMarginRate": 0.025, "maxLeverage": 20.0, "info": { "bracket": "4", "initialLeverage": "20", - "notionalCap": "750000", - "notionalFloor": "200000", + "notionalCap": "1000000", + "notionalFloor": "500000", "maintMarginRatio": "0.025", - "cum": "1250.0" + "cum": "3050.0" } }, { "tier": 5.0, "currency": "USDT", - "minNotional": 750000.0, - "maxNotional": 2000000.0, + "minNotional": 1000000.0, + "maxNotional": 5000000.0, "maintenanceMarginRate": 0.05, "maxLeverage": 10.0, "info": { "bracket": "5", "initialLeverage": "10", - "notionalCap": "2000000", - "notionalFloor": "750000", + "notionalCap": "5000000", + "notionalFloor": "1000000", "maintMarginRatio": "0.05", - "cum": "20000.0" + "cum": "28050.0" } }, { "tier": 6.0, "currency": "USDT", - "minNotional": 2000000.0, - "maxNotional": 4000000.0, + "minNotional": 5000000.0, + "maxNotional": 10000000.0, "maintenanceMarginRate": 0.1, "maxLeverage": 5.0, "info": { "bracket": "6", "initialLeverage": "5", - "notionalCap": "4000000", - "notionalFloor": "2000000", + "notionalCap": "10000000", + "notionalFloor": "5000000", "maintMarginRatio": "0.1", - "cum": "120000.0" + "cum": "278050.0" } }, { "tier": 7.0, "currency": "USDT", - "minNotional": 4000000.0, - "maxNotional": 5000000.0, + "minNotional": 10000000.0, + "maxNotional": 12500000.0, "maintenanceMarginRate": 0.125, "maxLeverage": 4.0, "info": { "bracket": "7", "initialLeverage": "4", - "notionalCap": "5000000", - "notionalFloor": "4000000", + "notionalCap": "12500000", + "notionalFloor": "10000000", "maintMarginRatio": "0.125", - "cum": "220000.0" + "cum": "528050.0" } }, { "tier": 8.0, "currency": "USDT", - "minNotional": 5000000.0, - "maxNotional": 12000000.0, + "minNotional": 12500000.0, + "maxNotional": 25000000.0, "maintenanceMarginRate": 0.25, "maxLeverage": 2.0, "info": { "bracket": "8", "initialLeverage": "2", - "notionalCap": "12000000", - "notionalFloor": "5000000", + "notionalCap": "25000000", + "notionalFloor": "12500000", "maintMarginRatio": "0.25", - "cum": "845000.0" + "cum": "2090550.0" } }, { "tier": 9.0, "currency": "USDT", - "minNotional": 12000000.0, - "maxNotional": 20000000.0, + "minNotional": 25000000.0, + "maxNotional": 50000000.0, "maintenanceMarginRate": 0.5, "maxLeverage": 1.0, "info": { "bracket": "9", "initialLeverage": "1", - "notionalCap": "20000000", - "notionalFloor": "12000000", + "notionalCap": "50000000", + "notionalFloor": "25000000", "maintMarginRatio": "0.5", - "cum": "3845000.0" + "cum": "8340550.0" } } ], @@ -26694,13 +28596,13 @@ "tier": 1.0, "currency": "USDT", "minNotional": 0.0, - "maxNotional": 10000.0, + "maxNotional": 20000.0, "maintenanceMarginRate": 0.01, "maxLeverage": 75.0, "info": { "bracket": "1", "initialLeverage": "75", - "notionalCap": "10000", + "notionalCap": "20000", "notionalFloor": "0", "maintMarginRatio": "0.01", "cum": "0.0" @@ -26709,129 +28611,129 @@ { "tier": 2.0, "currency": "USDT", - "minNotional": 10000.0, - "maxNotional": 40000.0, + "minNotional": 20000.0, + "maxNotional": 160000.0, "maintenanceMarginRate": 0.015, "maxLeverage": 50.0, "info": { "bracket": "2", "initialLeverage": "50", - "notionalCap": "40000", - "notionalFloor": "10000", + "notionalCap": "160000", + "notionalFloor": "20000", "maintMarginRatio": "0.015", - "cum": "50.0" + "cum": "100.0" } }, { "tier": 3.0, "currency": "USDT", - "minNotional": 40000.0, - "maxNotional": 200000.0, + "minNotional": 160000.0, + "maxNotional": 800000.0, "maintenanceMarginRate": 0.02, "maxLeverage": 25.0, "info": { "bracket": "3", "initialLeverage": "25", - "notionalCap": "200000", - "notionalFloor": "40000", + "notionalCap": "800000", + "notionalFloor": "160000", "maintMarginRatio": "0.02", - "cum": "250.0" + "cum": "900.0" } }, { "tier": 4.0, "currency": "USDT", - "minNotional": 200000.0, - "maxNotional": 400000.0, + "minNotional": 800000.0, + "maxNotional": 1600000.0, "maintenanceMarginRate": 0.025, "maxLeverage": 20.0, "info": { "bracket": "4", "initialLeverage": "20", - "notionalCap": "400000", - "notionalFloor": "200000", + "notionalCap": "1600000", + "notionalFloor": "800000", "maintMarginRatio": "0.025", - "cum": "1250.0" + "cum": "4900.0" } }, { "tier": 5.0, "currency": "USDT", - "minNotional": 400000.0, - "maxNotional": 2000000.0, + "minNotional": 1600000.0, + "maxNotional": 8000000.0, "maintenanceMarginRate": 0.05, "maxLeverage": 10.0, "info": { "bracket": "5", "initialLeverage": "10", - "notionalCap": "2000000", - "notionalFloor": "400000", + "notionalCap": "8000000", + "notionalFloor": "1600000", "maintMarginRatio": "0.05", - "cum": "11250.0" + "cum": "44900.0" } }, { "tier": 6.0, "currency": "USDT", - "minNotional": 2000000.0, - "maxNotional": 4000000.0, + "minNotional": 8000000.0, + "maxNotional": 16000000.0, "maintenanceMarginRate": 0.1, "maxLeverage": 5.0, "info": { "bracket": "6", "initialLeverage": "5", - "notionalCap": "4000000", - "notionalFloor": "2000000", + "notionalCap": "16000000", + "notionalFloor": "8000000", "maintMarginRatio": "0.1", - "cum": "111250.0" + "cum": "444900.0" } }, { "tier": 7.0, "currency": "USDT", - "minNotional": 4000000.0, - "maxNotional": 5000000.0, + "minNotional": 16000000.0, + "maxNotional": 20000000.0, "maintenanceMarginRate": 0.125, "maxLeverage": 4.0, "info": { "bracket": "7", "initialLeverage": "4", - "notionalCap": "5000000", - "notionalFloor": "4000000", + "notionalCap": "20000000", + "notionalFloor": "16000000", "maintMarginRatio": "0.125", - "cum": "211250.0" + "cum": "844900.0" } }, { "tier": 8.0, "currency": "USDT", - "minNotional": 5000000.0, - "maxNotional": 10000000.0, + "minNotional": 20000000.0, + "maxNotional": 40000000.0, "maintenanceMarginRate": 0.25, "maxLeverage": 2.0, "info": { "bracket": "8", "initialLeverage": "2", - "notionalCap": "10000000", - "notionalFloor": "5000000", + "notionalCap": "40000000", + "notionalFloor": "20000000", "maintMarginRatio": "0.25", - "cum": "836250.0" + "cum": "3344900.0" } }, { "tier": 9.0, "currency": "USDT", - "minNotional": 10000000.0, - "maxNotional": 20000000.0, + "minNotional": 40000000.0, + "maxNotional": 80000000.0, "maintenanceMarginRate": 0.5, "maxLeverage": 1.0, "info": { "bracket": "9", "initialLeverage": "1", - "notionalCap": "20000000", - "notionalFloor": "10000000", + "notionalCap": "80000000", + "notionalFloor": "40000000", "maintMarginRatio": "0.5", - "cum": "3336250.0" + "cum": "13344900.0" } } ], @@ -28222,48 +30124,48 @@ "tier": 1.0, "currency": "USDT", "minNotional": 0.0, - "maxNotional": 5000.0, - "maintenanceMarginRate": 0.015, - "maxLeverage": 50.0, + "maxNotional": 10000.0, + "maintenanceMarginRate": 0.01, + "maxLeverage": 75.0, "info": { "bracket": "1", - "initialLeverage": "50", - "notionalCap": "5000", + "initialLeverage": "75", + "notionalCap": "10000", "notionalFloor": "0", - "maintMarginRatio": "0.015", + "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", + "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": "675.0" + "notionalFloor": "20000", + "maintMarginRatio": "0.02", + "cum": "150.0" } }, { @@ -28271,63 +30173,95 @@ "currency": "USDT", "minNotional": 100000.0, "maxNotional": 200000.0, - "maintenanceMarginRate": 0.1, - "maxLeverage": 5.0, + "maintenanceMarginRate": 0.025, + "maxLeverage": 20.0, "info": { "bracket": "4", - "initialLeverage": "5", + "initialLeverage": "20", "notionalCap": "200000", "notionalFloor": "100000", - "maintMarginRatio": "0.1", - "cum": "5675.0" + "maintMarginRatio": "0.025", + "cum": "650.0" } }, { "tier": 5.0, "currency": "USDT", "minNotional": 200000.0, - "maxNotional": 500000.0, - "maintenanceMarginRate": 0.125, - "maxLeverage": 4.0, + "maxNotional": 1000000.0, + "maintenanceMarginRate": 0.05, + "maxLeverage": 10.0, "info": { "bracket": "5", - "initialLeverage": "4", - "notionalCap": "500000", + "initialLeverage": "10", + "notionalCap": "1000000", "notionalFloor": "200000", - "maintMarginRatio": "0.125", - "cum": "10675.0" + "maintMarginRatio": "0.05", + "cum": "5650.0" } }, { "tier": 6.0, "currency": "USDT", - "minNotional": 500000.0, - "maxNotional": 1000000.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": "1000000", - "notionalFloor": "500000", - "maintMarginRatio": "0.25", - "cum": "73175.0" + "initialLeverage": "5", + "notionalCap": "2000000", + "notionalFloor": "1000000", + "maintMarginRatio": "0.1", + "cum": "55650.0" } }, { "tier": 7.0, "currency": "USDT", - "minNotional": 1000000.0, - "maxNotional": 2000000.0, + "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": "7", + "bracket": "9", "initialLeverage": "1", - "notionalCap": "2000000", - "notionalFloor": "1000000", + "notionalCap": "10000000", + "notionalFloor": "5000000", "maintMarginRatio": "0.5", - "cum": "323175.0" + "cum": "1668150.0" } } ], @@ -28580,128 +30514,144 @@ "tier": 1.0, "currency": "USDT", "minNotional": 0.0, - "maxNotional": 5000.0, - "maintenanceMarginRate": 0.015, - "maxLeverage": 50.0, + "maxNotional": 10000.0, + "maintenanceMarginRate": 0.01, + "maxLeverage": 75.0, "info": { "bracket": "1", - "initialLeverage": "50", - "notionalCap": "5000", + "initialLeverage": "75", + "notionalCap": "10000", "notionalFloor": "0", - "maintMarginRatio": "0.015", + "maintMarginRatio": "0.01", "cum": "0.0" } }, { "tier": 2.0, "currency": "USDT", - "minNotional": 5000.0, - "maxNotional": 50000.0, - "maintenanceMarginRate": 0.02, - "maxLeverage": 25.0, + "minNotional": 10000.0, + "maxNotional": 40000.0, + "maintenanceMarginRate": 0.015, + "maxLeverage": 50.0, "info": { "bracket": "2", - "initialLeverage": "25", - "notionalCap": "50000", - "notionalFloor": "5000", - "maintMarginRatio": "0.02", - "cum": "25.0" + "initialLeverage": "50", + "notionalCap": "40000", + "notionalFloor": "10000", + "maintMarginRatio": "0.015", + "cum": "50.0" } }, { "tier": 3.0, "currency": "USDT", - "minNotional": 50000.0, - "maxNotional": 150000.0, - "maintenanceMarginRate": 0.025, - "maxLeverage": 20.0, + "minNotional": 40000.0, + "maxNotional": 200000.0, + "maintenanceMarginRate": 0.02, + "maxLeverage": 25.0, "info": { "bracket": "3", - "initialLeverage": "20", - "notionalCap": "150000", - "notionalFloor": "50000", - "maintMarginRatio": "0.025", - "cum": "275.0" + "initialLeverage": "25", + "notionalCap": "200000", + "notionalFloor": "40000", + "maintMarginRatio": "0.02", + "cum": "250.0" } }, { "tier": 4.0, "currency": "USDT", - "minNotional": 150000.0, - "maxNotional": 1500000.0, - "maintenanceMarginRate": 0.05, - "maxLeverage": 10.0, + "minNotional": 200000.0, + "maxNotional": 400000.0, + "maintenanceMarginRate": 0.025, + "maxLeverage": 20.0, "info": { "bracket": "4", - "initialLeverage": "10", - "notionalCap": "1500000", - "notionalFloor": "150000", - "maintMarginRatio": "0.05", - "cum": "4025.0" + "initialLeverage": "20", + "notionalCap": "400000", + "notionalFloor": "200000", + "maintMarginRatio": "0.025", + "cum": "1250.0" } }, { "tier": 5.0, "currency": "USDT", - "minNotional": 1500000.0, - "maxNotional": 3000000.0, - "maintenanceMarginRate": 0.1, - "maxLeverage": 5.0, + "minNotional": 400000.0, + "maxNotional": 2000000.0, + "maintenanceMarginRate": 0.05, + "maxLeverage": 10.0, "info": { "bracket": "5", - "initialLeverage": "5", - "notionalCap": "3000000", - "notionalFloor": "1500000", - "maintMarginRatio": "0.1", - "cum": "79025.0" + "initialLeverage": "10", + "notionalCap": "2000000", + "notionalFloor": "400000", + "maintMarginRatio": "0.05", + "cum": "11250.0" } }, { "tier": 6.0, "currency": "USDT", - "minNotional": 3000000.0, - "maxNotional": 3750000.0, - "maintenanceMarginRate": 0.125, - "maxLeverage": 4.0, + "minNotional": 2000000.0, + "maxNotional": 4000000.0, + "maintenanceMarginRate": 0.1, + "maxLeverage": 5.0, "info": { "bracket": "6", - "initialLeverage": "4", - "notionalCap": "3750000", - "notionalFloor": "3000000", - "maintMarginRatio": "0.125", - "cum": "154025.0" + "initialLeverage": "5", + "notionalCap": "4000000", + "notionalFloor": "2000000", + "maintMarginRatio": "0.1", + "cum": "111250.0" } }, { "tier": 7.0, "currency": "USDT", - "minNotional": 3750000.0, - "maxNotional": 7500000.0, - "maintenanceMarginRate": 0.25, - "maxLeverage": 2.0, + "minNotional": 4000000.0, + "maxNotional": 5000000.0, + "maintenanceMarginRate": 0.125, + "maxLeverage": 4.0, "info": { "bracket": "7", - "initialLeverage": "2", - "notionalCap": "7500000", - "notionalFloor": "3750000", - "maintMarginRatio": "0.25", - "cum": "622775.0" + "initialLeverage": "4", + "notionalCap": "5000000", + "notionalFloor": "4000000", + "maintMarginRatio": "0.125", + "cum": "211250.0" } }, { "tier": 8.0, "currency": "USDT", - "minNotional": 7500000.0, - "maxNotional": 15000000.0, + "minNotional": 5000000.0, + "maxNotional": 10000000.0, + "maintenanceMarginRate": 0.25, + "maxLeverage": 2.0, + "info": { + "bracket": "8", + "initialLeverage": "2", + "notionalCap": "10000000", + "notionalFloor": "5000000", + "maintMarginRatio": "0.25", + "cum": "836250.0" + } + }, + { + "tier": 9.0, + "currency": "USDT", + "minNotional": 10000000.0, + "maxNotional": 20000000.0, "maintenanceMarginRate": 0.5, "maxLeverage": 1.0, "info": { - "bracket": "8", + "bracket": "9", "initialLeverage": "1", - "notionalCap": "15000000", - "notionalFloor": "7500000", + "notionalCap": "20000000", + "notionalFloor": "10000000", "maintMarginRatio": "0.5", - "cum": "2497775.0" + "cum": "3336250.0" } } ], @@ -29231,14 +31181,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" } }, @@ -29246,96 +31196,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": 100000.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": "100000", - "notionalFloor": "25000", - "maintMarginRatio": "0.05", - "cum": "675.0" + "initialLeverage": "25", + "notionalCap": "30000", + "notionalFloor": "10000", + "maintMarginRatio": "0.02", + "cum": "75.0" } }, { "tier": 4.0, "currency": "USDT", - "minNotional": 100000.0, - "maxNotional": 200000.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": "200000", - "notionalFloor": "100000", - "maintMarginRatio": "0.1", - "cum": "5675.0" + "initialLeverage": "20", + "notionalCap": "60000", + "notionalFloor": "30000", + "maintMarginRatio": "0.025", + "cum": "225.0" } }, { "tier": 5.0, "currency": "USDT", - "minNotional": 200000.0, - "maxNotional": 500000.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": "500000", - "notionalFloor": "200000", - "maintMarginRatio": "0.125", - "cum": "10675.0" + "initialLeverage": "10", + "notionalCap": "300000", + "notionalFloor": "60000", + "maintMarginRatio": "0.05", + "cum": "1725.0" } }, { "tier": 6.0, "currency": "USDT", - "minNotional": 500000.0, - "maxNotional": 1000000.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": "1000000", - "notionalFloor": "500000", - "maintMarginRatio": "0.25", - "cum": "73175.0" + "initialLeverage": "5", + "notionalCap": "600000", + "notionalFloor": "300000", + "maintMarginRatio": "0.1", + "cum": "16725.0" } }, { "tier": 7.0, "currency": "USDT", - "minNotional": 1000000.0, - "maxNotional": 2000000.0, + "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": "7", + "bracket": "9", "initialLeverage": "1", - "notionalCap": "2000000", - "notionalFloor": "1000000", + "notionalCap": "3000000", + "notionalFloor": "1500000", "maintMarginRatio": "0.5", - "cum": "323175.0" + "cum": "500475.0" } } ], @@ -30287,14 +32269,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" } }, @@ -30302,112 +32284,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" } } ], @@ -30708,13 +32706,13 @@ "tier": 2.0, "currency": "USDT", "minNotional": 10000.0, - "maxNotional": 20000.0, + "maxNotional": 60000.0, "maintenanceMarginRate": 0.015, "maxLeverage": 50.0, "info": { "bracket": "2", "initialLeverage": "50", - "notionalCap": "20000", + "notionalCap": "60000", "notionalFloor": "10000", "maintMarginRatio": "0.015", "cum": "50.0" @@ -30723,113 +32721,113 @@ { "tier": 3.0, "currency": "USDT", - "minNotional": 20000.0, - "maxNotional": 100000.0, + "minNotional": 60000.0, + "maxNotional": 300000.0, "maintenanceMarginRate": 0.02, "maxLeverage": 25.0, "info": { "bracket": "3", "initialLeverage": "25", - "notionalCap": "100000", - "notionalFloor": "20000", + "notionalCap": "300000", + "notionalFloor": "60000", "maintMarginRatio": "0.02", - "cum": "150.0" + "cum": "350.0" } }, { "tier": 4.0, "currency": "USDT", - "minNotional": 100000.0, - "maxNotional": 200000.0, + "minNotional": 300000.0, + "maxNotional": 600000.0, "maintenanceMarginRate": 0.025, "maxLeverage": 20.0, "info": { "bracket": "4", "initialLeverage": "20", - "notionalCap": "200000", - "notionalFloor": "100000", + "notionalCap": "600000", + "notionalFloor": "300000", "maintMarginRatio": "0.025", - "cum": "650.0" + "cum": "1850.0" } }, { "tier": 5.0, "currency": "USDT", - "minNotional": 200000.0, - "maxNotional": 1000000.0, + "minNotional": 600000.0, + "maxNotional": 3000000.0, "maintenanceMarginRate": 0.05, "maxLeverage": 10.0, "info": { "bracket": "5", "initialLeverage": "10", - "notionalCap": "1000000", - "notionalFloor": "200000", + "notionalCap": "3000000", + "notionalFloor": "600000", "maintMarginRatio": "0.05", - "cum": "5650.0" + "cum": "16850.0" } }, { "tier": 6.0, "currency": "USDT", - "minNotional": 1000000.0, - "maxNotional": 2000000.0, + "minNotional": 3000000.0, + "maxNotional": 6000000.0, "maintenanceMarginRate": 0.1, "maxLeverage": 5.0, "info": { "bracket": "6", "initialLeverage": "5", - "notionalCap": "2000000", - "notionalFloor": "1000000", + "notionalCap": "6000000", + "notionalFloor": "3000000", "maintMarginRatio": "0.1", - "cum": "55650.0" + "cum": "166850.0" } }, { "tier": 7.0, "currency": "USDT", - "minNotional": 2000000.0, - "maxNotional": 2500000.0, + "minNotional": 6000000.0, + "maxNotional": 7500000.0, "maintenanceMarginRate": 0.125, "maxLeverage": 4.0, "info": { "bracket": "7", "initialLeverage": "4", - "notionalCap": "2500000", - "notionalFloor": "2000000", + "notionalCap": "7500000", + "notionalFloor": "6000000", "maintMarginRatio": "0.125", - "cum": "105650.0" + "cum": "316850.0" } }, { "tier": 8.0, "currency": "USDT", - "minNotional": 2500000.0, - "maxNotional": 5000000.0, + "minNotional": 7500000.0, + "maxNotional": 15000000.0, "maintenanceMarginRate": 0.25, "maxLeverage": 2.0, "info": { "bracket": "8", "initialLeverage": "2", - "notionalCap": "5000000", - "notionalFloor": "2500000", + "notionalCap": "15000000", + "notionalFloor": "7500000", "maintMarginRatio": "0.25", - "cum": "418150.0" + "cum": "1254350.0" } }, { "tier": 9.0, "currency": "USDT", - "minNotional": 5000000.0, - "maxNotional": 10000000.0, + "minNotional": 15000000.0, + "maxNotional": 30000000.0, "maintenanceMarginRate": 0.5, "maxLeverage": 1.0, "info": { "bracket": "9", "initialLeverage": "1", - "notionalCap": "10000000", - "notionalFloor": "5000000", + "notionalCap": "30000000", + "notionalFloor": "15000000", "maintMarginRatio": "0.5", - "cum": "1668150.0" + "cum": "5004350.0" } } ], @@ -32058,15 +34056,129 @@ "tier": 1.0, "currency": "USDT", "minNotional": 0.0, - "maxNotional": 5000.0, - "maintenanceMarginRate": 0.015, - "maxLeverage": 26.0, + "maxNotional": 20000.0, + "maintenanceMarginRate": 0.02, + "maxLeverage": 10.0, "info": { "bracket": "1", - "initialLeverage": "26", + "initialLeverage": "10", + "notionalCap": "20000", + "notionalFloor": "0", + "maintMarginRatio": "0.02", + "cum": "0.0" + } + }, + { + "tier": 2.0, + "currency": "USDT", + "minNotional": 20000.0, + "maxNotional": 25000.0, + "maintenanceMarginRate": 0.025, + "maxLeverage": 8.0, + "info": { + "bracket": "2", + "initialLeverage": "8", + "notionalCap": "25000", + "notionalFloor": "20000", + "maintMarginRatio": "0.025", + "cum": "100.0" + } + }, + { + "tier": 3.0, + "currency": "USDT", + "minNotional": 25000.0, + "maxNotional": 200000.0, + "maintenanceMarginRate": 0.05, + "maxLeverage": 6.0, + "info": { + "bracket": "3", + "initialLeverage": "6", + "notionalCap": "200000", + "notionalFloor": "25000", + "maintMarginRatio": "0.05", + "cum": "725.0" + } + }, + { + "tier": 4.0, + "currency": "USDT", + "minNotional": 200000.0, + "maxNotional": 400000.0, + "maintenanceMarginRate": 0.1, + "maxLeverage": 5.0, + "info": { + "bracket": "4", + "initialLeverage": "5", + "notionalCap": "400000", + "notionalFloor": "200000", + "maintMarginRatio": "0.1", + "cum": "10725.0" + } + }, + { + "tier": 5.0, + "currency": "USDT", + "minNotional": 400000.0, + "maxNotional": 500000.0, + "maintenanceMarginRate": 0.125, + "maxLeverage": 4.0, + "info": { + "bracket": "5", + "initialLeverage": "4", + "notionalCap": "500000", + "notionalFloor": "400000", + "maintMarginRatio": "0.125", + "cum": "20725.0" + } + }, + { + "tier": 6.0, + "currency": "USDT", + "minNotional": 500000.0, + "maxNotional": 1000000.0, + "maintenanceMarginRate": 0.25, + "maxLeverage": 2.0, + "info": { + "bracket": "6", + "initialLeverage": "2", + "notionalCap": "1000000", + "notionalFloor": "500000", + "maintMarginRatio": "0.25", + "cum": "83225.0" + } + }, + { + "tier": 7.0, + "currency": "USDT", + "minNotional": 1000000.0, + "maxNotional": 1500000.0, + "maintenanceMarginRate": 0.5, + "maxLeverage": 1.0, + "info": { + "bracket": "7", + "initialLeverage": "1", + "notionalCap": "1500000", + "notionalFloor": "1000000", + "maintMarginRatio": "0.5", + "cum": "333225.0" + } + } + ], + "REI/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.015", + "maintMarginRatio": "0.01", "cum": "0.0" } }, @@ -32074,112 +34186,128 @@ "tier": 2.0, "currency": "USDT", "minNotional": 5000.0, - "maxNotional": 20000.0, - "maintenanceMarginRate": 0.02, - "maxLeverage": 25.0, + "maxNotional": 10000.0, + "maintenanceMarginRate": 0.015, + "maxLeverage": 50.0, "info": { "bracket": "2", - "initialLeverage": "25", - "notionalCap": "20000", + "initialLeverage": "50", + "notionalCap": "10000", "notionalFloor": "5000", - "maintMarginRatio": "0.02", + "maintMarginRatio": "0.015", "cum": "25.0" } }, { "tier": 3.0, "currency": "USDT", - "minNotional": 20000.0, - "maxNotional": 25000.0, - "maintenanceMarginRate": 0.025, - "maxLeverage": 20.0, + "minNotional": 10000.0, + "maxNotional": 20000.0, + "maintenanceMarginRate": 0.02, + "maxLeverage": 25.0, "info": { "bracket": "3", - "initialLeverage": "20", - "notionalCap": "25000", - "notionalFloor": "20000", - "maintMarginRatio": "0.025", - "cum": "125.0" + "initialLeverage": "25", + "notionalCap": "20000", + "notionalFloor": "10000", + "maintMarginRatio": "0.02", + "cum": "75.0" } }, { "tier": 4.0, "currency": "USDT", - "minNotional": 25000.0, - "maxNotional": 200000.0, - "maintenanceMarginRate": 0.05, - "maxLeverage": 10.0, + "minNotional": 20000.0, + "maxNotional": 40000.0, + "maintenanceMarginRate": 0.025, + "maxLeverage": 20.0, "info": { "bracket": "4", - "initialLeverage": "10", - "notionalCap": "200000", - "notionalFloor": "25000", - "maintMarginRatio": "0.05", - "cum": "750.0" + "initialLeverage": "20", + "notionalCap": "40000", + "notionalFloor": "20000", + "maintMarginRatio": "0.025", + "cum": "175.0" } }, { "tier": 5.0, "currency": "USDT", + "minNotional": 40000.0, + "maxNotional": 200000.0, + "maintenanceMarginRate": 0.05, + "maxLeverage": 10.0, + "info": { + "bracket": "5", + "initialLeverage": "10", + "notionalCap": "200000", + "notionalFloor": "40000", + "maintMarginRatio": "0.05", + "cum": "1175.0" + } + }, + { + "tier": 6.0, + "currency": "USDT", "minNotional": 200000.0, "maxNotional": 400000.0, "maintenanceMarginRate": 0.1, "maxLeverage": 5.0, "info": { - "bracket": "5", + "bracket": "6", "initialLeverage": "5", "notionalCap": "400000", "notionalFloor": "200000", "maintMarginRatio": "0.1", - "cum": "10750.0" + "cum": "11175.0" } }, { - "tier": 6.0, + "tier": 7.0, "currency": "USDT", "minNotional": 400000.0, "maxNotional": 500000.0, "maintenanceMarginRate": 0.125, "maxLeverage": 4.0, "info": { - "bracket": "6", + "bracket": "7", "initialLeverage": "4", "notionalCap": "500000", "notionalFloor": "400000", "maintMarginRatio": "0.125", - "cum": "20750.0" + "cum": "21175.0" } }, { - "tier": 7.0, + "tier": 8.0, "currency": "USDT", "minNotional": 500000.0, "maxNotional": 1000000.0, "maintenanceMarginRate": 0.25, "maxLeverage": 2.0, "info": { - "bracket": "7", + "bracket": "8", "initialLeverage": "2", "notionalCap": "1000000", "notionalFloor": "500000", "maintMarginRatio": "0.25", - "cum": "83250.0" + "cum": "83675.0" } }, { - "tier": 8.0, + "tier": 9.0, "currency": "USDT", "minNotional": 1000000.0, - "maxNotional": 1500000.0, + "maxNotional": 2000000.0, "maintenanceMarginRate": 0.5, "maxLeverage": 1.0, "info": { - "bracket": "8", + "bracket": "9", "initialLeverage": "1", - "notionalCap": "1500000", + "notionalCap": "2000000", "notionalFloor": "1000000", "maintMarginRatio": "0.5", - "cum": "333250.0" + "cum": "333675.0" } } ], @@ -32303,14 +34431,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" } }, @@ -32318,112 +34446,128 @@ "tier": 2.0, "currency": "USDT", "minNotional": 5000.0, - "maxNotional": 20000.0, - "maintenanceMarginRate": 0.02, - "maxLeverage": 25.0, + "maxNotional": 16000.0, + "maintenanceMarginRate": 0.015, + "maxLeverage": 50.0, "info": { "bracket": "2", - "initialLeverage": "25", - "notionalCap": "20000", + "initialLeverage": "50", + "notionalCap": "16000", "notionalFloor": "5000", - "maintMarginRatio": "0.02", + "maintMarginRatio": "0.015", "cum": "25.0" } }, { "tier": 3.0, "currency": "USDT", - "minNotional": 20000.0, - "maxNotional": 25000.0, - "maintenanceMarginRate": 0.025, - "maxLeverage": 20.0, + "minNotional": 16000.0, + "maxNotional": 80000.0, + "maintenanceMarginRate": 0.02, + "maxLeverage": 25.0, "info": { "bracket": "3", - "initialLeverage": "20", - "notionalCap": "25000", - "notionalFloor": "20000", - "maintMarginRatio": "0.025", - "cum": "125.0" + "initialLeverage": "25", + "notionalCap": "80000", + "notionalFloor": "16000", + "maintMarginRatio": "0.02", + "cum": "105.0" } }, { "tier": 4.0, "currency": "USDT", - "minNotional": 25000.0, - "maxNotional": 200000.0, - "maintenanceMarginRate": 0.05, - "maxLeverage": 10.0, + "minNotional": 80000.0, + "maxNotional": 160000.0, + "maintenanceMarginRate": 0.025, + "maxLeverage": 20.0, "info": { "bracket": "4", - "initialLeverage": "10", - "notionalCap": "200000", - "notionalFloor": "25000", - "maintMarginRatio": "0.05", - "cum": "750.0" + "initialLeverage": "20", + "notionalCap": "160000", + "notionalFloor": "80000", + "maintMarginRatio": "0.025", + "cum": "505.0" } }, { "tier": 5.0, "currency": "USDT", - "minNotional": 200000.0, - "maxNotional": 400000.0, - "maintenanceMarginRate": 0.1, - "maxLeverage": 5.0, + "minNotional": 160000.0, + "maxNotional": 800000.0, + "maintenanceMarginRate": 0.05, + "maxLeverage": 10.0, "info": { "bracket": "5", - "initialLeverage": "5", - "notionalCap": "400000", - "notionalFloor": "200000", - "maintMarginRatio": "0.1", - "cum": "10750.0" + "initialLeverage": "10", + "notionalCap": "800000", + "notionalFloor": "160000", + "maintMarginRatio": "0.05", + "cum": "4505.0" } }, { "tier": 6.0, "currency": "USDT", - "minNotional": 400000.0, - "maxNotional": 500000.0, - "maintenanceMarginRate": 0.125, - "maxLeverage": 4.0, + "minNotional": 800000.0, + "maxNotional": 1600000.0, + "maintenanceMarginRate": 0.1, + "maxLeverage": 5.0, "info": { "bracket": "6", - "initialLeverage": "4", - "notionalCap": "500000", - "notionalFloor": "400000", - "maintMarginRatio": "0.125", - "cum": "20750.0" + "initialLeverage": "5", + "notionalCap": "1600000", + "notionalFloor": "800000", + "maintMarginRatio": "0.1", + "cum": "44505.0" } }, { "tier": 7.0, "currency": "USDT", - "minNotional": 500000.0, - "maxNotional": 1000000.0, - "maintenanceMarginRate": 0.25, - "maxLeverage": 2.0, + "minNotional": 1600000.0, + "maxNotional": 2000000.0, + "maintenanceMarginRate": 0.125, + "maxLeverage": 4.0, "info": { "bracket": "7", - "initialLeverage": "2", - "notionalCap": "1000000", - "notionalFloor": "500000", - "maintMarginRatio": "0.25", - "cum": "83250.0" + "initialLeverage": "4", + "notionalCap": "2000000", + "notionalFloor": "1600000", + "maintMarginRatio": "0.125", + "cum": "84505.0" } }, { "tier": 8.0, "currency": "USDT", - "minNotional": 1000000.0, - "maxNotional": 2000000.0, + "minNotional": 2000000.0, + "maxNotional": 4000000.0, + "maintenanceMarginRate": 0.25, + "maxLeverage": 2.0, + "info": { + "bracket": "8", + "initialLeverage": "2", + "notionalCap": "4000000", + "notionalFloor": "2000000", + "maintMarginRatio": "0.25", + "cum": "334505.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": "2000000", - "notionalFloor": "1000000", + "notionalCap": "8000000", + "notionalFloor": "4000000", "maintMarginRatio": "0.5", - "cum": "333250.0" + "cum": "1334505.0" } } ], @@ -32906,10 +35050,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", @@ -32920,96 +35064,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" } } ], @@ -33165,14 +35341,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" } }, @@ -33180,96 +35356,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" } } ], @@ -33278,112 +35486,144 @@ "tier": 1.0, "currency": "USDT", "minNotional": 0.0, - "maxNotional": 5000.0, - "maintenanceMarginRate": 0.015, - "maxLeverage": 50.0, + "maxNotional": 10000.0, + "maintenanceMarginRate": 0.01, + "maxLeverage": 75.0, "info": { "bracket": "1", - "initialLeverage": "50", - "notionalCap": "5000", + "initialLeverage": "75", + "notionalCap": "10000", "notionalFloor": "0", - "maintMarginRatio": "0.015", + "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": 30000.0, + "maintenanceMarginRate": 0.015, + "maxLeverage": 50.0, "info": { "bracket": "2", - "initialLeverage": "20", - "notionalCap": "25000", - "notionalFloor": "5000", - "maintMarginRatio": "0.025", + "initialLeverage": "50", + "notionalCap": "30000", + "notionalFloor": "10000", + "maintMarginRatio": "0.015", "cum": "50.0" } }, { "tier": 3.0, "currency": "USDT", - "minNotional": 25000.0, - "maxNotional": 100000.0, - "maintenanceMarginRate": 0.05, - "maxLeverage": 10.0, + "minNotional": 30000.0, + "maxNotional": 150000.0, + "maintenanceMarginRate": 0.02, + "maxLeverage": 25.0, "info": { "bracket": "3", - "initialLeverage": "10", - "notionalCap": "100000", - "notionalFloor": "25000", - "maintMarginRatio": "0.05", - "cum": "675.0" + "initialLeverage": "25", + "notionalCap": "150000", + "notionalFloor": "30000", + "maintMarginRatio": "0.02", + "cum": "200.0" } }, { "tier": 4.0, "currency": "USDT", - "minNotional": 100000.0, - "maxNotional": 250000.0, - "maintenanceMarginRate": 0.1, - "maxLeverage": 5.0, + "minNotional": 150000.0, + "maxNotional": 300000.0, + "maintenanceMarginRate": 0.025, + "maxLeverage": 20.0, "info": { "bracket": "4", - "initialLeverage": "5", - "notionalCap": "250000", - "notionalFloor": "100000", - "maintMarginRatio": "0.1", - "cum": "5675.0" + "initialLeverage": "20", + "notionalCap": "300000", + "notionalFloor": "150000", + "maintMarginRatio": "0.025", + "cum": "950.0" } }, { "tier": 5.0, "currency": "USDT", - "minNotional": 250000.0, - "maxNotional": 1000000.0, - "maintenanceMarginRate": 0.125, - "maxLeverage": 4.0, + "minNotional": 300000.0, + "maxNotional": 1500000.0, + "maintenanceMarginRate": 0.05, + "maxLeverage": 10.0, "info": { "bracket": "5", - "initialLeverage": "4", - "notionalCap": "1000000", - "notionalFloor": "250000", - "maintMarginRatio": "0.125", - "cum": "11925.0" + "initialLeverage": "10", + "notionalCap": "1500000", + "notionalFloor": "300000", + "maintMarginRatio": "0.05", + "cum": "8450.0" } }, { "tier": 6.0, "currency": "USDT", - "minNotional": 1000000.0, - "maxNotional": 2000000.0, - "maintenanceMarginRate": 0.25, - "maxLeverage": 2.0, + "minNotional": 1500000.0, + "maxNotional": 3000000.0, + "maintenanceMarginRate": 0.1, + "maxLeverage": 5.0, "info": { "bracket": "6", - "initialLeverage": "2", - "notionalCap": "2000000", - "notionalFloor": "1000000", - "maintMarginRatio": "0.25", - "cum": "136925.0" + "initialLeverage": "5", + "notionalCap": "3000000", + "notionalFloor": "1500000", + "maintMarginRatio": "0.1", + "cum": "83450.0" } }, { "tier": 7.0, "currency": "USDT", - "minNotional": 2000000.0, - "maxNotional": 5000000.0, + "minNotional": 3000000.0, + "maxNotional": 3750000.0, + "maintenanceMarginRate": 0.125, + "maxLeverage": 4.0, + "info": { + "bracket": "7", + "initialLeverage": "4", + "notionalCap": "3750000", + "notionalFloor": "3000000", + "maintMarginRatio": "0.125", + "cum": "158450.0" + } + }, + { + "tier": 8.0, + "currency": "USDT", + "minNotional": 3750000.0, + "maxNotional": 7500000.0, + "maintenanceMarginRate": 0.25, + "maxLeverage": 2.0, + "info": { + "bracket": "8", + "initialLeverage": "2", + "notionalCap": "7500000", + "notionalFloor": "3750000", + "maintMarginRatio": "0.25", + "cum": "627200.0" + } + }, + { + "tier": 9.0, + "currency": "USDT", + "minNotional": 7500000.0, + "maxNotional": 15000000.0, "maintenanceMarginRate": 0.5, "maxLeverage": 1.0, "info": { - "bracket": "7", + "bracket": "9", "initialLeverage": "1", - "notionalCap": "5000000", - "notionalFloor": "2000000", + "notionalCap": "15000000", + "notionalFloor": "7500000", "maintMarginRatio": "0.5", - "cum": "636925.0" + "cum": "2502200.0" } } ], @@ -33517,6 +35757,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, @@ -33809,6 +36195,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, @@ -33907,6 +36439,152 @@ } } ], + "SCR/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" + } + } + ], "SEI/USDT:USDT": [ { "tier": 1.0, @@ -33928,13 +36606,13 @@ "tier": 2.0, "currency": "USDT", "minNotional": 10000.0, - "maxNotional": 20000.0, + "maxNotional": 60000.0, "maintenanceMarginRate": 0.015, "maxLeverage": 50.0, "info": { "bracket": "2", "initialLeverage": "50", - "notionalCap": "20000", + "notionalCap": "60000", "notionalFloor": "10000", "maintMarginRatio": "0.015", "cum": "50.0" @@ -33943,113 +36621,113 @@ { "tier": 3.0, "currency": "USDT", - "minNotional": 20000.0, - "maxNotional": 100000.0, + "minNotional": 60000.0, + "maxNotional": 300000.0, "maintenanceMarginRate": 0.02, "maxLeverage": 25.0, "info": { "bracket": "3", "initialLeverage": "25", - "notionalCap": "100000", - "notionalFloor": "20000", + "notionalCap": "300000", + "notionalFloor": "60000", "maintMarginRatio": "0.02", - "cum": "150.0" + "cum": "350.0" } }, { "tier": 4.0, "currency": "USDT", - "minNotional": 100000.0, - "maxNotional": 200000.0, + "minNotional": 300000.0, + "maxNotional": 600000.0, "maintenanceMarginRate": 0.025, "maxLeverage": 20.0, "info": { "bracket": "4", "initialLeverage": "20", - "notionalCap": "200000", - "notionalFloor": "100000", + "notionalCap": "600000", + "notionalFloor": "300000", "maintMarginRatio": "0.025", - "cum": "650.0" + "cum": "1850.0" } }, { "tier": 5.0, "currency": "USDT", - "minNotional": 200000.0, - "maxNotional": 1000000.0, + "minNotional": 600000.0, + "maxNotional": 3000000.0, "maintenanceMarginRate": 0.05, "maxLeverage": 10.0, "info": { "bracket": "5", "initialLeverage": "10", - "notionalCap": "1000000", - "notionalFloor": "200000", + "notionalCap": "3000000", + "notionalFloor": "600000", "maintMarginRatio": "0.05", - "cum": "5650.0" + "cum": "16850.0" } }, { "tier": 6.0, "currency": "USDT", - "minNotional": 1000000.0, - "maxNotional": 2000000.0, + "minNotional": 3000000.0, + "maxNotional": 6000000.0, "maintenanceMarginRate": 0.1, "maxLeverage": 5.0, "info": { "bracket": "6", "initialLeverage": "5", - "notionalCap": "2000000", - "notionalFloor": "1000000", + "notionalCap": "6000000", + "notionalFloor": "3000000", "maintMarginRatio": "0.1", - "cum": "55650.0" + "cum": "166850.0" } }, { "tier": 7.0, "currency": "USDT", - "minNotional": 2000000.0, - "maxNotional": 2500000.0, + "minNotional": 6000000.0, + "maxNotional": 7500000.0, "maintenanceMarginRate": 0.125, "maxLeverage": 4.0, "info": { "bracket": "7", "initialLeverage": "4", - "notionalCap": "2500000", - "notionalFloor": "2000000", + "notionalCap": "7500000", + "notionalFloor": "6000000", "maintMarginRatio": "0.125", - "cum": "105650.0" + "cum": "316850.0" } }, { "tier": 8.0, "currency": "USDT", - "minNotional": 2500000.0, - "maxNotional": 5000000.0, + "minNotional": 7500000.0, + "maxNotional": 15000000.0, "maintenanceMarginRate": 0.25, "maxLeverage": 2.0, "info": { "bracket": "8", "initialLeverage": "2", - "notionalCap": "5000000", - "notionalFloor": "2500000", + "notionalCap": "15000000", + "notionalFloor": "7500000", "maintMarginRatio": "0.25", - "cum": "418150.0" + "cum": "1254350.0" } }, { "tier": 9.0, "currency": "USDT", - "minNotional": 5000000.0, - "maxNotional": 10000000.0, + "minNotional": 15000000.0, + "maxNotional": 30000000.0, "maintenanceMarginRate": 0.5, "maxLeverage": 1.0, "info": { "bracket": "9", "initialLeverage": "1", - "notionalCap": "10000000", - "notionalFloor": "5000000", + "notionalCap": "30000000", + "notionalFloor": "15000000", "maintMarginRatio": "0.5", - "cum": "1668150.0" + "cum": "5004350.0" } } ], @@ -35067,14 +37745,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" } }, @@ -35082,96 +37760,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" } } ], @@ -35832,143 +38542,13 @@ "tier": 1.0, "currency": "USDT", "minNotional": 0.0, - "maxNotional": 5000.0, - "maintenanceMarginRate": 0.015, - "maxLeverage": 50.0, - "info": { - "bracket": "1", - "initialLeverage": "50", - "notionalCap": "5000", - "notionalFloor": "0", - "maintMarginRatio": "0.015", - "cum": "0.0" - } - }, - { - "tier": 2.0, - "currency": "USDT", - "minNotional": 5000.0, - "maxNotional": 25000.0, - "maintenanceMarginRate": 0.02, - "maxLeverage": 25.0, - "info": { - "bracket": "2", - "initialLeverage": "25", - "notionalCap": "25000", - "notionalFloor": "5000", - "maintMarginRatio": "0.02", - "cum": "25.0" - } - }, - { - "tier": 3.0, - "currency": "USDT", - "minNotional": 25000.0, - "maxNotional": 80000.0, - "maintenanceMarginRate": 0.025, - "maxLeverage": 20.0, - "info": { - "bracket": "3", - "initialLeverage": "20", - "notionalCap": "80000", - "notionalFloor": "25000", - "maintMarginRatio": "0.025", - "cum": "150.0" - } - }, - { - "tier": 4.0, - "currency": "USDT", - "minNotional": 80000.0, - "maxNotional": 800000.0, - "maintenanceMarginRate": 0.05, - "maxLeverage": 10.0, - "info": { - "bracket": "4", - "initialLeverage": "10", - "notionalCap": "800000", - "notionalFloor": "80000", - "maintMarginRatio": "0.05", - "cum": "2150.0" - } - }, - { - "tier": 5.0, - "currency": "USDT", - "minNotional": 800000.0, - "maxNotional": 1600000.0, - "maintenanceMarginRate": 0.1, - "maxLeverage": 5.0, - "info": { - "bracket": "5", - "initialLeverage": "5", - "notionalCap": "1600000", - "notionalFloor": "800000", - "maintMarginRatio": "0.1", - "cum": "42150.0" - } - }, - { - "tier": 6.0, - "currency": "USDT", - "minNotional": 1600000.0, - "maxNotional": 2000000.0, - "maintenanceMarginRate": 0.125, - "maxLeverage": 4.0, - "info": { - "bracket": "6", - "initialLeverage": "4", - "notionalCap": "2000000", - "notionalFloor": "1600000", - "maintMarginRatio": "0.125", - "cum": "82150.0" - } - }, - { - "tier": 7.0, - "currency": "USDT", - "minNotional": 2000000.0, - "maxNotional": 4000000.0, - "maintenanceMarginRate": 0.25, - "maxLeverage": 2.0, - "info": { - "bracket": "7", - "initialLeverage": "2", - "notionalCap": "4000000", - "notionalFloor": "2000000", - "maintMarginRatio": "0.25", - "cum": "332150.0" - } - }, - { - "tier": 8.0, - "currency": "USDT", - "minNotional": 4000000.0, - "maxNotional": 8000000.0, - "maintenanceMarginRate": 0.5, - "maxLeverage": 1.0, - "info": { - "bracket": "8", - "initialLeverage": "1", - "notionalCap": "8000000", - "notionalFloor": "4000000", - "maintMarginRatio": "0.5", - "cum": "1332150.0" - } - } - ], - "STX/USDT:USDT": [ - { - "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" @@ -35977,97 +38557,275 @@ { "tier": 2.0, "currency": "USDT", - "minNotional": 5000.0, - "maxNotional": 50000.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": "50000", - "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": 50000.0, - "maxNotional": 720000.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": "720000", - "notionalFloor": "50000", - "maintMarginRatio": "0.05", - "cum": "1325.0" + "initialLeverage": "25", + "notionalCap": "100000", + "notionalFloor": "20000", + "maintMarginRatio": "0.02", + "cum": "150.0" } }, { "tier": 4.0, "currency": "USDT", - "minNotional": 720000.0, - "maxNotional": 2000000.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": "2000000", - "notionalFloor": "720000", - "maintMarginRatio": "0.1", - "cum": "37325.0" + "initialLeverage": "20", + "notionalCap": "200000", + "notionalFloor": "100000", + "maintMarginRatio": "0.025", + "cum": "650.0" } }, { "tier": 5.0, "currency": "USDT", - "minNotional": 2000000.0, - "maxNotional": 2400000.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": "2400000", - "notionalFloor": "2000000", - "maintMarginRatio": "0.125", - "cum": "87325.0" + "initialLeverage": "10", + "notionalCap": "1000000", + "notionalFloor": "200000", + "maintMarginRatio": "0.05", + "cum": "5650.0" } }, { "tier": 6.0, "currency": "USDT", - "minNotional": 2400000.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": "2400000", - "maintMarginRatio": "0.25", - "cum": "387325.0" + "initialLeverage": "5", + "notionalCap": "2000000", + "notionalFloor": "1000000", + "maintMarginRatio": "0.1", + "cum": "55650.0" } }, { "tier": 7.0, "currency": "USDT", - "minNotional": 6000000.0, + "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": "7", + "bracket": "9", "initialLeverage": "1", "notionalCap": "10000000", - "notionalFloor": "6000000", + "notionalFloor": "5000000", "maintMarginRatio": "0.5", - "cum": "1887325.0" + "cum": "1668150.0" + } + } + ], + "STX/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": 30000.0, + "maintenanceMarginRate": 0.015, + "maxLeverage": 50.0, + "info": { + "bracket": "2", + "initialLeverage": "50", + "notionalCap": "30000", + "notionalFloor": "10000", + "maintMarginRatio": "0.015", + "cum": "50.0" + } + }, + { + "tier": 3.0, + "currency": "USDT", + "minNotional": 30000.0, + "maxNotional": 150000.0, + "maintenanceMarginRate": 0.02, + "maxLeverage": 25.0, + "info": { + "bracket": "3", + "initialLeverage": "25", + "notionalCap": "150000", + "notionalFloor": "30000", + "maintMarginRatio": "0.02", + "cum": "200.0" + } + }, + { + "tier": 4.0, + "currency": "USDT", + "minNotional": 150000.0, + "maxNotional": 300000.0, + "maintenanceMarginRate": 0.025, + "maxLeverage": 20.0, + "info": { + "bracket": "4", + "initialLeverage": "20", + "notionalCap": "300000", + "notionalFloor": "150000", + "maintMarginRatio": "0.025", + "cum": "950.0" + } + }, + { + "tier": 5.0, + "currency": "USDT", + "minNotional": 300000.0, + "maxNotional": 1500000.0, + "maintenanceMarginRate": 0.05, + "maxLeverage": 10.0, + "info": { + "bracket": "5", + "initialLeverage": "10", + "notionalCap": "1500000", + "notionalFloor": "300000", + "maintMarginRatio": "0.05", + "cum": "8450.0" + } + }, + { + "tier": 6.0, + "currency": "USDT", + "minNotional": 1500000.0, + "maxNotional": 3000000.0, + "maintenanceMarginRate": 0.1, + "maxLeverage": 5.0, + "info": { + "bracket": "6", + "initialLeverage": "5", + "notionalCap": "3000000", + "notionalFloor": "1500000", + "maintMarginRatio": "0.1", + "cum": "83450.0" + } + }, + { + "tier": 7.0, + "currency": "USDT", + "minNotional": 3000000.0, + "maxNotional": 3750000.0, + "maintenanceMarginRate": 0.125, + "maxLeverage": 4.0, + "info": { + "bracket": "7", + "initialLeverage": "4", + "notionalCap": "3750000", + "notionalFloor": "3000000", + "maintMarginRatio": "0.125", + "cum": "158450.0" + } + }, + { + "tier": 8.0, + "currency": "USDT", + "minNotional": 3750000.0, + "maxNotional": 7500000.0, + "maintenanceMarginRate": 0.25, + "maxLeverage": 2.0, + "info": { + "bracket": "8", + "initialLeverage": "2", + "notionalCap": "7500000", + "notionalFloor": "3750000", + "maintMarginRatio": "0.25", + "cum": "627200.0" + } + }, + { + "tier": 9.0, + "currency": "USDT", + "minNotional": 7500000.0, + "maxNotional": 15000000.0, + "maintenanceMarginRate": 0.5, + "maxLeverage": 1.0, + "info": { + "bracket": "9", + "initialLeverage": "1", + "notionalCap": "15000000", + "notionalFloor": "7500000", + "maintMarginRatio": "0.5", + "cum": "2502200.0" } } ], @@ -36206,13 +38964,13 @@ "tier": 1.0, "currency": "USDT", "minNotional": 0.0, - "maxNotional": 10000.0, + "maxNotional": 20000.0, "maintenanceMarginRate": 0.01, "maxLeverage": 75.0, "info": { "bracket": "1", "initialLeverage": "75", - "notionalCap": "10000", + "notionalCap": "20000", "notionalFloor": "0", "maintMarginRatio": "0.01", "cum": "0.0" @@ -36221,129 +38979,129 @@ { "tier": 2.0, "currency": "USDT", - "minNotional": 10000.0, - "maxNotional": 80000.0, + "minNotional": 20000.0, + "maxNotional": 200000.0, "maintenanceMarginRate": 0.015, "maxLeverage": 50.0, "info": { "bracket": "2", "initialLeverage": "50", - "notionalCap": "80000", - "notionalFloor": "10000", + "notionalCap": "200000", + "notionalFloor": "20000", "maintMarginRatio": "0.015", - "cum": "50.0" + "cum": "100.0" } }, { "tier": 3.0, "currency": "USDT", - "minNotional": 80000.0, - "maxNotional": 400000.0, + "minNotional": 200000.0, + "maxNotional": 1000000.0, "maintenanceMarginRate": 0.02, "maxLeverage": 25.0, "info": { "bracket": "3", "initialLeverage": "25", - "notionalCap": "400000", - "notionalFloor": "80000", + "notionalCap": "1000000", + "notionalFloor": "200000", "maintMarginRatio": "0.02", - "cum": "450.0" + "cum": "1100.0" } }, { "tier": 4.0, "currency": "USDT", - "minNotional": 400000.0, - "maxNotional": 800000.0, + "minNotional": 1000000.0, + "maxNotional": 2000000.0, "maintenanceMarginRate": 0.025, "maxLeverage": 20.0, "info": { "bracket": "4", "initialLeverage": "20", - "notionalCap": "800000", - "notionalFloor": "400000", + "notionalCap": "2000000", + "notionalFloor": "1000000", "maintMarginRatio": "0.025", - "cum": "2450.0" + "cum": "6100.0" } }, { "tier": 5.0, "currency": "USDT", - "minNotional": 800000.0, - "maxNotional": 4000000.0, + "minNotional": 2000000.0, + "maxNotional": 10000000.0, "maintenanceMarginRate": 0.05, "maxLeverage": 10.0, "info": { "bracket": "5", "initialLeverage": "10", - "notionalCap": "4000000", - "notionalFloor": "800000", + "notionalCap": "10000000", + "notionalFloor": "2000000", "maintMarginRatio": "0.05", - "cum": "22450.0" + "cum": "56100.0" } }, { "tier": 6.0, "currency": "USDT", - "minNotional": 4000000.0, - "maxNotional": 8000000.0, + "minNotional": 10000000.0, + "maxNotional": 20000000.0, "maintenanceMarginRate": 0.1, "maxLeverage": 5.0, "info": { "bracket": "6", "initialLeverage": "5", - "notionalCap": "8000000", - "notionalFloor": "4000000", + "notionalCap": "20000000", + "notionalFloor": "10000000", "maintMarginRatio": "0.1", - "cum": "222450.0" + "cum": "556100.0" } }, { "tier": 7.0, "currency": "USDT", - "minNotional": 8000000.0, - "maxNotional": 10000000.0, + "minNotional": 20000000.0, + "maxNotional": 25000000.0, "maintenanceMarginRate": 0.125, "maxLeverage": 4.0, "info": { "bracket": "7", "initialLeverage": "4", - "notionalCap": "10000000", - "notionalFloor": "8000000", + "notionalCap": "25000000", + "notionalFloor": "20000000", "maintMarginRatio": "0.125", - "cum": "422450.0" + "cum": "1056100.0" } }, { "tier": 8.0, "currency": "USDT", - "minNotional": 10000000.0, - "maxNotional": 20000000.0, + "minNotional": 25000000.0, + "maxNotional": 50000000.0, "maintenanceMarginRate": 0.25, "maxLeverage": 2.0, "info": { "bracket": "8", "initialLeverage": "2", - "notionalCap": "20000000", - "notionalFloor": "10000000", + "notionalCap": "50000000", + "notionalFloor": "25000000", "maintMarginRatio": "0.25", - "cum": "1672450.0" + "cum": "4181100.0" } }, { "tier": 9.0, "currency": "USDT", - "minNotional": 20000000.0, - "maxNotional": 40000000.0, + "minNotional": 50000000.0, + "maxNotional": 100000000.0, "maintenanceMarginRate": 0.5, "maxLeverage": 1.0, "info": { "bracket": "9", "initialLeverage": "1", - "notionalCap": "40000000", - "notionalFloor": "20000000", + "notionalCap": "100000000", + "notionalFloor": "50000000", "maintMarginRatio": "0.5", - "cum": "6672450.0" + "cum": "16681100.0" } } ], @@ -36499,14 +39257,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" } }, @@ -36514,96 +39272,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": 100000.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": "100000", - "notionalFloor": "25000", - "maintMarginRatio": "0.05", - "cum": "675.0" + "initialLeverage": "25", + "notionalCap": "30000", + "notionalFloor": "10000", + "maintMarginRatio": "0.02", + "cum": "75.0" } }, { "tier": 4.0, "currency": "USDT", - "minNotional": 100000.0, - "maxNotional": 200000.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": "200000", - "notionalFloor": "100000", - "maintMarginRatio": "0.1", - "cum": "5675.0" + "initialLeverage": "20", + "notionalCap": "60000", + "notionalFloor": "30000", + "maintMarginRatio": "0.025", + "cum": "225.0" } }, { "tier": 5.0, "currency": "USDT", - "minNotional": 200000.0, - "maxNotional": 500000.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": "500000", - "notionalFloor": "200000", - "maintMarginRatio": "0.125", - "cum": "10675.0" + "initialLeverage": "10", + "notionalCap": "300000", + "notionalFloor": "60000", + "maintMarginRatio": "0.05", + "cum": "1725.0" } }, { "tier": 6.0, "currency": "USDT", - "minNotional": 500000.0, - "maxNotional": 1000000.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": "1000000", - "notionalFloor": "500000", - "maintMarginRatio": "0.25", - "cum": "73175.0" + "initialLeverage": "5", + "notionalCap": "600000", + "notionalFloor": "300000", + "maintMarginRatio": "0.1", + "cum": "16725.0" } }, { "tier": 7.0, "currency": "USDT", - "minNotional": 1000000.0, - "maxNotional": 2000000.0, + "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": "7", + "bracket": "9", "initialLeverage": "1", - "notionalCap": "2000000", - "notionalFloor": "1000000", + "notionalCap": "3000000", + "notionalFloor": "1500000", "maintMarginRatio": "0.5", - "cum": "323175.0" + "cum": "500475.0" } } ], @@ -36726,112 +39516,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": 6500000.0, + "maxNotional": 10000000.0, "maintenanceMarginRate": 0.5, "maxLeverage": 1.0, "info": { - "bracket": "7", + "bracket": "9", "initialLeverage": "1", - "notionalCap": "6500000", + "notionalCap": "10000000", "notionalFloor": "6000000", "maintMarginRatio": "0.5", - "cum": "1820650.0" + "cum": "1918150.0" } } ], @@ -37230,13 +40052,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" @@ -37245,129 +40067,129 @@ { "tier": 2.0, "currency": "USDT", - "minNotional": 5000.0, - "maxNotional": 16000.0, + "minNotional": 10000.0, + "maxNotional": 100000.0, "maintenanceMarginRate": 0.015, "maxLeverage": 50.0, "info": { "bracket": "2", "initialLeverage": "50", - "notionalCap": "16000", - "notionalFloor": "5000", + "notionalCap": "100000", + "notionalFloor": "10000", "maintMarginRatio": "0.015", - "cum": "25.0" + "cum": "50.0" } }, { "tier": 3.0, "currency": "USDT", - "minNotional": 16000.0, - "maxNotional": 80000.0, + "minNotional": 100000.0, + "maxNotional": 500000.0, "maintenanceMarginRate": 0.02, "maxLeverage": 25.0, "info": { "bracket": "3", "initialLeverage": "25", - "notionalCap": "80000", - "notionalFloor": "16000", + "notionalCap": "500000", + "notionalFloor": "100000", "maintMarginRatio": "0.02", - "cum": "105.0" + "cum": "550.0" } }, { "tier": 4.0, "currency": "USDT", - "minNotional": 80000.0, - "maxNotional": 160000.0, + "minNotional": 500000.0, + "maxNotional": 1000000.0, "maintenanceMarginRate": 0.025, "maxLeverage": 20.0, "info": { "bracket": "4", "initialLeverage": "20", - "notionalCap": "160000", - "notionalFloor": "80000", + "notionalCap": "1000000", + "notionalFloor": "500000", "maintMarginRatio": "0.025", - "cum": "505.0" + "cum": "3050.0" } }, { "tier": 5.0, "currency": "USDT", - "minNotional": 160000.0, - "maxNotional": 800000.0, + "minNotional": 1000000.0, + "maxNotional": 5000000.0, "maintenanceMarginRate": 0.05, "maxLeverage": 10.0, "info": { "bracket": "5", "initialLeverage": "10", - "notionalCap": "800000", - "notionalFloor": "160000", + "notionalCap": "5000000", + "notionalFloor": "1000000", "maintMarginRatio": "0.05", - "cum": "4505.0" + "cum": "28050.0" } }, { "tier": 6.0, "currency": "USDT", - "minNotional": 800000.0, - "maxNotional": 1600000.0, + "minNotional": 5000000.0, + "maxNotional": 10000000.0, "maintenanceMarginRate": 0.1, "maxLeverage": 5.0, "info": { "bracket": "6", "initialLeverage": "5", - "notionalCap": "1600000", - "notionalFloor": "800000", + "notionalCap": "10000000", + "notionalFloor": "5000000", "maintMarginRatio": "0.1", - "cum": "44505.0" + "cum": "278050.0" } }, { "tier": 7.0, "currency": "USDT", - "minNotional": 1600000.0, - "maxNotional": 2000000.0, + "minNotional": 10000000.0, + "maxNotional": 12500000.0, "maintenanceMarginRate": 0.125, "maxLeverage": 4.0, "info": { "bracket": "7", "initialLeverage": "4", - "notionalCap": "2000000", - "notionalFloor": "1600000", + "notionalCap": "12500000", + "notionalFloor": "10000000", "maintMarginRatio": "0.125", - "cum": "84505.0" + "cum": "528050.0" } }, { "tier": 8.0, "currency": "USDT", - "minNotional": 2000000.0, - "maxNotional": 4000000.0, + "minNotional": 12500000.0, + "maxNotional": 25000000.0, "maintenanceMarginRate": 0.25, "maxLeverage": 2.0, "info": { "bracket": "8", "initialLeverage": "2", - "notionalCap": "4000000", - "notionalFloor": "2000000", + "notionalCap": "25000000", + "notionalFloor": "12500000", "maintMarginRatio": "0.25", - "cum": "334505.0" + "cum": "2090550.0" } }, { "tier": 9.0, "currency": "USDT", - "minNotional": 4000000.0, - "maxNotional": 8000000.0, + "minNotional": 25000000.0, + "maxNotional": 50000000.0, "maintenanceMarginRate": 0.5, "maxLeverage": 1.0, "info": { "bracket": "9", "initialLeverage": "1", - "notionalCap": "8000000", - "notionalFloor": "4000000", + "notionalCap": "50000000", + "notionalFloor": "25000000", "maintMarginRatio": "0.5", - "cum": "1334505.0" + "cum": "8340550.0" } } ], @@ -37652,13 +40474,13 @@ "tier": 2.0, "currency": "USDT", "minNotional": 10000.0, - "maxNotional": 40000.0, + "maxNotional": 80000.0, "maintenanceMarginRate": 0.015, "maxLeverage": 50.0, "info": { "bracket": "2", "initialLeverage": "50", - "notionalCap": "40000", + "notionalCap": "80000", "notionalFloor": "10000", "maintMarginRatio": "0.015", "cum": "50.0" @@ -37667,113 +40489,113 @@ { "tier": 3.0, "currency": "USDT", - "minNotional": 40000.0, - "maxNotional": 200000.0, + "minNotional": 80000.0, + "maxNotional": 400000.0, "maintenanceMarginRate": 0.02, "maxLeverage": 25.0, "info": { "bracket": "3", "initialLeverage": "25", - "notionalCap": "200000", - "notionalFloor": "40000", + "notionalCap": "400000", + "notionalFloor": "80000", "maintMarginRatio": "0.02", - "cum": "250.0" + "cum": "450.0" } }, { "tier": 4.0, "currency": "USDT", - "minNotional": 200000.0, - "maxNotional": 400000.0, + "minNotional": 400000.0, + "maxNotional": 800000.0, "maintenanceMarginRate": 0.025, "maxLeverage": 20.0, "info": { "bracket": "4", "initialLeverage": "20", - "notionalCap": "400000", - "notionalFloor": "200000", + "notionalCap": "800000", + "notionalFloor": "400000", "maintMarginRatio": "0.025", - "cum": "1250.0" + "cum": "2450.0" } }, { "tier": 5.0, "currency": "USDT", - "minNotional": 400000.0, - "maxNotional": 2000000.0, + "minNotional": 800000.0, + "maxNotional": 4000000.0, "maintenanceMarginRate": 0.05, "maxLeverage": 10.0, "info": { "bracket": "5", "initialLeverage": "10", - "notionalCap": "2000000", - "notionalFloor": "400000", + "notionalCap": "4000000", + "notionalFloor": "800000", "maintMarginRatio": "0.05", - "cum": "11250.0" + "cum": "22450.0" } }, { "tier": 6.0, "currency": "USDT", - "minNotional": 2000000.0, - "maxNotional": 4000000.0, + "minNotional": 4000000.0, + "maxNotional": 8000000.0, "maintenanceMarginRate": 0.1, "maxLeverage": 5.0, "info": { "bracket": "6", "initialLeverage": "5", - "notionalCap": "4000000", - "notionalFloor": "2000000", + "notionalCap": "8000000", + "notionalFloor": "4000000", "maintMarginRatio": "0.1", - "cum": "111250.0" + "cum": "222450.0" } }, { "tier": 7.0, "currency": "USDT", - "minNotional": 4000000.0, - "maxNotional": 5000000.0, + "minNotional": 8000000.0, + "maxNotional": 10000000.0, "maintenanceMarginRate": 0.125, "maxLeverage": 4.0, "info": { "bracket": "7", "initialLeverage": "4", - "notionalCap": "5000000", - "notionalFloor": "4000000", + "notionalCap": "10000000", + "notionalFloor": "8000000", "maintMarginRatio": "0.125", - "cum": "211250.0" + "cum": "422450.0" } }, { "tier": 8.0, "currency": "USDT", - "minNotional": 5000000.0, - "maxNotional": 10000000.0, + "minNotional": 10000000.0, + "maxNotional": 20000000.0, "maintenanceMarginRate": 0.25, "maxLeverage": 2.0, "info": { "bracket": "8", "initialLeverage": "2", - "notionalCap": "10000000", - "notionalFloor": "5000000", + "notionalCap": "20000000", + "notionalFloor": "10000000", "maintMarginRatio": "0.25", - "cum": "836250.0" + "cum": "1672450.0" } }, { "tier": 9.0, "currency": "USDT", - "minNotional": 10000000.0, - "maxNotional": 20000000.0, + "minNotional": 20000000.0, + "maxNotional": 40000000.0, "maintenanceMarginRate": 0.5, "maxLeverage": 1.0, "info": { "bracket": "9", "initialLeverage": "1", - "notionalCap": "20000000", - "notionalFloor": "10000000", + "notionalCap": "40000000", + "notionalFloor": "20000000", "maintMarginRatio": "0.5", - "cum": "3336250.0" + "cum": "6672450.0" } } ], @@ -38692,13 +41514,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" @@ -38707,129 +41529,129 @@ { "tier": 2.0, "currency": "USDT", - "minNotional": 5000.0, - "maxNotional": 10000.0, + "minNotional": 10000.0, + "maxNotional": 60000.0, "maintenanceMarginRate": 0.015, "maxLeverage": 50.0, "info": { "bracket": "2", "initialLeverage": "50", - "notionalCap": "10000", - "notionalFloor": "5000", + "notionalCap": "60000", + "notionalFloor": "10000", "maintMarginRatio": "0.015", - "cum": "25.0" + "cum": "50.0" } }, { "tier": 3.0, "currency": "USDT", - "minNotional": 10000.0, - "maxNotional": 50000.0, + "minNotional": 60000.0, + "maxNotional": 300000.0, "maintenanceMarginRate": 0.02, "maxLeverage": 25.0, "info": { "bracket": "3", "initialLeverage": "25", - "notionalCap": "50000", - "notionalFloor": "10000", + "notionalCap": "300000", + "notionalFloor": "60000", "maintMarginRatio": "0.02", - "cum": "75.0" + "cum": "350.0" } }, { "tier": 4.0, "currency": "USDT", - "minNotional": 50000.0, - "maxNotional": 100000.0, + "minNotional": 300000.0, + "maxNotional": 600000.0, "maintenanceMarginRate": 0.025, "maxLeverage": 20.0, "info": { "bracket": "4", "initialLeverage": "20", - "notionalCap": "100000", - "notionalFloor": "50000", + "notionalCap": "600000", + "notionalFloor": "300000", "maintMarginRatio": "0.025", - "cum": "325.0" + "cum": "1850.0" } }, { "tier": 5.0, "currency": "USDT", - "minNotional": 100000.0, - "maxNotional": 500000.0, + "minNotional": 600000.0, + "maxNotional": 3000000.0, "maintenanceMarginRate": 0.05, "maxLeverage": 10.0, "info": { "bracket": "5", "initialLeverage": "10", - "notionalCap": "500000", - "notionalFloor": "100000", + "notionalCap": "3000000", + "notionalFloor": "600000", "maintMarginRatio": "0.05", - "cum": "2825.0" + "cum": "16850.0" } }, { "tier": 6.0, "currency": "USDT", - "minNotional": 500000.0, - "maxNotional": 1000000.0, + "minNotional": 3000000.0, + "maxNotional": 6000000.0, "maintenanceMarginRate": 0.1, "maxLeverage": 5.0, "info": { "bracket": "6", "initialLeverage": "5", - "notionalCap": "1000000", - "notionalFloor": "500000", + "notionalCap": "6000000", + "notionalFloor": "3000000", "maintMarginRatio": "0.1", - "cum": "27825.0" + "cum": "166850.0" } }, { "tier": 7.0, "currency": "USDT", - "minNotional": 1000000.0, - "maxNotional": 1250000.0, + "minNotional": 6000000.0, + "maxNotional": 7500000.0, "maintenanceMarginRate": 0.125, "maxLeverage": 4.0, "info": { "bracket": "7", "initialLeverage": "4", - "notionalCap": "1250000", - "notionalFloor": "1000000", + "notionalCap": "7500000", + "notionalFloor": "6000000", "maintMarginRatio": "0.125", - "cum": "52825.0" + "cum": "316850.0" } }, { "tier": 8.0, "currency": "USDT", - "minNotional": 1250000.0, - "maxNotional": 2500000.0, + "minNotional": 7500000.0, + "maxNotional": 15000000.0, "maintenanceMarginRate": 0.25, "maxLeverage": 2.0, "info": { "bracket": "8", "initialLeverage": "2", - "notionalCap": "2500000", - "notionalFloor": "1250000", + "notionalCap": "15000000", + "notionalFloor": "7500000", "maintMarginRatio": "0.25", - "cum": "209075.0" + "cum": "1254350.0" } }, { "tier": 9.0, "currency": "USDT", - "minNotional": 2500000.0, - "maxNotional": 5000000.0, + "minNotional": 15000000.0, + "maxNotional": 30000000.0, "maintenanceMarginRate": 0.5, "maxLeverage": 1.0, "info": { "bracket": "9", "initialLeverage": "1", - "notionalCap": "5000000", - "notionalFloor": "2500000", + "notionalCap": "30000000", + "notionalFloor": "15000000", "maintMarginRatio": "0.5", - "cum": "834075.0" + "cum": "5004350.0" } } ], @@ -38953,14 +41775,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" } }, @@ -38968,96 +41790,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" } } ], @@ -39068,10 +41922,10 @@ "minNotional": 0.0, "maxNotional": 25000.0, "maintenanceMarginRate": 0.025, - "maxLeverage": 20.0, + "maxLeverage": 11.0, "info": { "bracket": "1", - "initialLeverage": "20", + "initialLeverage": "11", "notionalCap": "25000", "notionalFloor": "0", "maintMarginRatio": "0.025", @@ -40236,128 +43090,144 @@ "tier": 1.0, "currency": "USDT", "minNotional": 0.0, - "maxNotional": 5000.0, - "maintenanceMarginRate": 0.015, - "maxLeverage": 50.0, + "maxNotional": 10000.0, + "maintenanceMarginRate": 0.01, + "maxLeverage": 75.0, "info": { "bracket": "1", - "initialLeverage": "50", - "notionalCap": "5000", + "initialLeverage": "75", + "notionalCap": "10000", "notionalFloor": "0", - "maintMarginRatio": "0.015", + "maintMarginRatio": "0.01", "cum": "0.0" } }, { "tier": 2.0, "currency": "USDT", - "minNotional": 5000.0, - "maxNotional": 25000.0, - "maintenanceMarginRate": 0.02, - "maxLeverage": 25.0, + "minNotional": 10000.0, + "maxNotional": 20000.0, + "maintenanceMarginRate": 0.015, + "maxLeverage": 50.0, "info": { "bracket": "2", - "initialLeverage": "25", - "notionalCap": "25000", - "notionalFloor": "5000", - "maintMarginRatio": "0.02", - "cum": "25.0" + "initialLeverage": "50", + "notionalCap": "20000", + "notionalFloor": "10000", + "maintMarginRatio": "0.015", + "cum": "50.0" } }, { "tier": 3.0, "currency": "USDT", - "minNotional": 25000.0, - "maxNotional": 80000.0, - "maintenanceMarginRate": 0.025, - "maxLeverage": 20.0, + "minNotional": 20000.0, + "maxNotional": 100000.0, + "maintenanceMarginRate": 0.02, + "maxLeverage": 25.0, "info": { "bracket": "3", - "initialLeverage": "20", - "notionalCap": "80000", - "notionalFloor": "25000", - "maintMarginRatio": "0.025", + "initialLeverage": "25", + "notionalCap": "100000", + "notionalFloor": "20000", + "maintMarginRatio": "0.02", "cum": "150.0" } }, { "tier": 4.0, "currency": "USDT", - "minNotional": 80000.0, - "maxNotional": 800000.0, - "maintenanceMarginRate": 0.05, - "maxLeverage": 10.0, + "minNotional": 100000.0, + "maxNotional": 200000.0, + "maintenanceMarginRate": 0.025, + "maxLeverage": 20.0, "info": { "bracket": "4", - "initialLeverage": "10", - "notionalCap": "800000", - "notionalFloor": "80000", - "maintMarginRatio": "0.05", - "cum": "2150.0" + "initialLeverage": "20", + "notionalCap": "200000", + "notionalFloor": "100000", + "maintMarginRatio": "0.025", + "cum": "650.0" } }, { "tier": 5.0, "currency": "USDT", - "minNotional": 800000.0, - "maxNotional": 1600000.0, - "maintenanceMarginRate": 0.1, - "maxLeverage": 5.0, + "minNotional": 200000.0, + "maxNotional": 1000000.0, + "maintenanceMarginRate": 0.05, + "maxLeverage": 10.0, "info": { "bracket": "5", - "initialLeverage": "5", - "notionalCap": "1600000", - "notionalFloor": "800000", - "maintMarginRatio": "0.1", - "cum": "42150.0" + "initialLeverage": "10", + "notionalCap": "1000000", + "notionalFloor": "200000", + "maintMarginRatio": "0.05", + "cum": "5650.0" } }, { "tier": 6.0, "currency": "USDT", - "minNotional": 1600000.0, + "minNotional": 1000000.0, "maxNotional": 2000000.0, - "maintenanceMarginRate": 0.125, - "maxLeverage": 4.0, + "maintenanceMarginRate": 0.1, + "maxLeverage": 5.0, "info": { "bracket": "6", - "initialLeverage": "4", + "initialLeverage": "5", "notionalCap": "2000000", - "notionalFloor": "1600000", - "maintMarginRatio": "0.125", - "cum": "82150.0" + "notionalFloor": "1000000", + "maintMarginRatio": "0.1", + "cum": "55650.0" } }, { "tier": 7.0, "currency": "USDT", "minNotional": 2000000.0, - "maxNotional": 4000000.0, - "maintenanceMarginRate": 0.25, - "maxLeverage": 2.0, + "maxNotional": 2500000.0, + "maintenanceMarginRate": 0.125, + "maxLeverage": 4.0, "info": { "bracket": "7", - "initialLeverage": "2", - "notionalCap": "4000000", + "initialLeverage": "4", + "notionalCap": "2500000", "notionalFloor": "2000000", - "maintMarginRatio": "0.25", - "cum": "332150.0" + "maintMarginRatio": "0.125", + "cum": "105650.0" } }, { "tier": 8.0, "currency": "USDT", - "minNotional": 4000000.0, - "maxNotional": 8000000.0, + "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": "8", + "bracket": "9", "initialLeverage": "1", - "notionalCap": "8000000", - "notionalFloor": "4000000", + "notionalCap": "10000000", + "notionalFloor": "5000000", "maintMarginRatio": "0.5", - "cum": "1332150.0" + "cum": "1668150.0" } } ], @@ -40724,13 +43594,13 @@ "tier": 1.0, "currency": "USDT", "minNotional": 0.0, - "maxNotional": 10000.0, + "maxNotional": 20000.0, "maintenanceMarginRate": 0.01, "maxLeverage": 75.0, "info": { "bracket": "1", "initialLeverage": "75", - "notionalCap": "10000", + "notionalCap": "20000", "notionalFloor": "0", "maintMarginRatio": "0.01", "cum": "0.0" @@ -40739,129 +43609,129 @@ { "tier": 2.0, "currency": "USDT", - "minNotional": 10000.0, - "maxNotional": 100000.0, + "minNotional": 20000.0, + "maxNotional": 200000.0, "maintenanceMarginRate": 0.015, "maxLeverage": 50.0, "info": { "bracket": "2", "initialLeverage": "50", - "notionalCap": "100000", - "notionalFloor": "10000", + "notionalCap": "200000", + "notionalFloor": "20000", "maintMarginRatio": "0.015", - "cum": "50.0" + "cum": "100.0" } }, { "tier": 3.0, "currency": "USDT", - "minNotional": 100000.0, - "maxNotional": 500000.0, + "minNotional": 200000.0, + "maxNotional": 1000000.0, "maintenanceMarginRate": 0.02, "maxLeverage": 25.0, "info": { "bracket": "3", "initialLeverage": "25", - "notionalCap": "500000", - "notionalFloor": "100000", + "notionalCap": "1000000", + "notionalFloor": "200000", "maintMarginRatio": "0.02", - "cum": "550.0" + "cum": "1100.0" } }, { "tier": 4.0, "currency": "USDT", - "minNotional": 500000.0, - "maxNotional": 1000000.0, + "minNotional": 1000000.0, + "maxNotional": 2000000.0, "maintenanceMarginRate": 0.025, "maxLeverage": 20.0, "info": { "bracket": "4", "initialLeverage": "20", - "notionalCap": "1000000", - "notionalFloor": "500000", + "notionalCap": "2000000", + "notionalFloor": "1000000", "maintMarginRatio": "0.025", - "cum": "3050.0" + "cum": "6100.0" } }, { "tier": 5.0, "currency": "USDT", - "minNotional": 1000000.0, - "maxNotional": 5000000.0, + "minNotional": 2000000.0, + "maxNotional": 10000000.0, "maintenanceMarginRate": 0.05, "maxLeverage": 10.0, "info": { "bracket": "5", "initialLeverage": "10", - "notionalCap": "5000000", - "notionalFloor": "1000000", + "notionalCap": "10000000", + "notionalFloor": "2000000", "maintMarginRatio": "0.05", - "cum": "28050.0" + "cum": "56100.0" } }, { "tier": 6.0, "currency": "USDT", - "minNotional": 5000000.0, - "maxNotional": 10000000.0, + "minNotional": 10000000.0, + "maxNotional": 20000000.0, "maintenanceMarginRate": 0.1, "maxLeverage": 5.0, "info": { "bracket": "6", "initialLeverage": "5", - "notionalCap": "10000000", - "notionalFloor": "5000000", + "notionalCap": "20000000", + "notionalFloor": "10000000", "maintMarginRatio": "0.1", - "cum": "278050.0" + "cum": "556100.0" } }, { "tier": 7.0, "currency": "USDT", - "minNotional": 10000000.0, - "maxNotional": 12500000.0, + "minNotional": 20000000.0, + "maxNotional": 25000000.0, "maintenanceMarginRate": 0.125, "maxLeverage": 4.0, "info": { "bracket": "7", "initialLeverage": "4", - "notionalCap": "12500000", - "notionalFloor": "10000000", + "notionalCap": "25000000", + "notionalFloor": "20000000", "maintMarginRatio": "0.125", - "cum": "528050.0" + "cum": "1056100.0" } }, { "tier": 8.0, "currency": "USDT", - "minNotional": 12500000.0, - "maxNotional": 25000000.0, + "minNotional": 25000000.0, + "maxNotional": 50000000.0, "maintenanceMarginRate": 0.25, "maxLeverage": 2.0, "info": { "bracket": "8", "initialLeverage": "2", - "notionalCap": "25000000", - "notionalFloor": "12500000", + "notionalCap": "50000000", + "notionalFloor": "25000000", "maintMarginRatio": "0.25", - "cum": "2090550.0" + "cum": "4181100.0" } }, { "tier": 9.0, "currency": "USDT", - "minNotional": 25000000.0, - "maxNotional": 50000000.0, + "minNotional": 50000000.0, + "maxNotional": 100000000.0, "maintenanceMarginRate": 0.5, "maxLeverage": 1.0, "info": { "bracket": "9", "initialLeverage": "1", - "notionalCap": "50000000", - "notionalFloor": "25000000", + "notionalCap": "100000000", + "notionalFloor": "50000000", "maintMarginRatio": "0.5", - "cum": "8340550.0" + "cum": "16681100.0" } } ], @@ -41048,112 +43918,128 @@ "tier": 3.0, "currency": "USDT", "minNotional": 60000.0, - "maxNotional": 300000.0, - "maintenanceMarginRate": 0.02, - "maxLeverage": 25.0, + "maxNotional": 100000.0, + "maintenanceMarginRate": 0.015, + "maxLeverage": 40.0, "info": { "bracket": "3", - "initialLeverage": "25", - "notionalCap": "300000", + "initialLeverage": "40", + "notionalCap": "100000", "notionalFloor": "60000", - "maintMarginRatio": "0.02", - "cum": "620.0" + "maintMarginRatio": "0.015", + "cum": "320.0" } }, { "tier": 4.0, "currency": "USDT", - "minNotional": 300000.0, - "maxNotional": 600000.0, - "maintenanceMarginRate": 0.025, - "maxLeverage": 20.0, + "minNotional": 100000.0, + "maxNotional": 500000.0, + "maintenanceMarginRate": 0.02, + "maxLeverage": 25.0, "info": { "bracket": "4", - "initialLeverage": "20", - "notionalCap": "600000", - "notionalFloor": "300000", - "maintMarginRatio": "0.025", - "cum": "2120.0" + "initialLeverage": "25", + "notionalCap": "500000", + "notionalFloor": "100000", + "maintMarginRatio": "0.02", + "cum": "820.0" } }, { "tier": 5.0, "currency": "USDT", - "minNotional": 600000.0, - "maxNotional": 3000000.0, - "maintenanceMarginRate": 0.05, - "maxLeverage": 10.0, + "minNotional": 500000.0, + "maxNotional": 1000000.0, + "maintenanceMarginRate": 0.025, + "maxLeverage": 20.0, "info": { "bracket": "5", - "initialLeverage": "10", - "notionalCap": "3000000", - "notionalFloor": "600000", - "maintMarginRatio": "0.05", - "cum": "17120.0" + "initialLeverage": "20", + "notionalCap": "1000000", + "notionalFloor": "500000", + "maintMarginRatio": "0.025", + "cum": "3320.0" } }, { "tier": 6.0, "currency": "USDT", - "minNotional": 3000000.0, - "maxNotional": 6000000.0, - "maintenanceMarginRate": 0.1, - "maxLeverage": 5.0, + "minNotional": 1000000.0, + "maxNotional": 5000000.0, + "maintenanceMarginRate": 0.05, + "maxLeverage": 10.0, "info": { "bracket": "6", - "initialLeverage": "5", - "notionalCap": "6000000", - "notionalFloor": "3000000", - "maintMarginRatio": "0.1", - "cum": "167120.0" + "initialLeverage": "10", + "notionalCap": "5000000", + "notionalFloor": "1000000", + "maintMarginRatio": "0.05", + "cum": "28320.0" } }, { "tier": 7.0, "currency": "USDT", - "minNotional": 6000000.0, - "maxNotional": 7500000.0, - "maintenanceMarginRate": 0.125, - "maxLeverage": 4.0, + "minNotional": 5000000.0, + "maxNotional": 10000000.0, + "maintenanceMarginRate": 0.1, + "maxLeverage": 5.0, "info": { "bracket": "7", - "initialLeverage": "4", - "notionalCap": "7500000", - "notionalFloor": "6000000", - "maintMarginRatio": "0.125", - "cum": "317120.0" + "initialLeverage": "5", + "notionalCap": "10000000", + "notionalFloor": "5000000", + "maintMarginRatio": "0.1", + "cum": "278320.0" } }, { "tier": 8.0, "currency": "USDT", - "minNotional": 7500000.0, - "maxNotional": 15000000.0, - "maintenanceMarginRate": 0.25, - "maxLeverage": 2.0, + "minNotional": 10000000.0, + "maxNotional": 12500000.0, + "maintenanceMarginRate": 0.125, + "maxLeverage": 4.0, "info": { "bracket": "8", - "initialLeverage": "2", - "notionalCap": "15000000", - "notionalFloor": "7500000", - "maintMarginRatio": "0.25", - "cum": "1254620.0" + "initialLeverage": "4", + "notionalCap": "12500000", + "notionalFloor": "10000000", + "maintMarginRatio": "0.125", + "cum": "528320.0" } }, { "tier": 9.0, "currency": "USDT", - "minNotional": 15000000.0, - "maxNotional": 30000000.0, + "minNotional": 12500000.0, + "maxNotional": 25000000.0, + "maintenanceMarginRate": 0.25, + "maxLeverage": 2.0, + "info": { + "bracket": "9", + "initialLeverage": "2", + "notionalCap": "25000000", + "notionalFloor": "12500000", + "maintMarginRatio": "0.25", + "cum": "2090820.0" + } + }, + { + "tier": 10.0, + "currency": "USDT", + "minNotional": 25000000.0, + "maxNotional": 50000000.0, "maintenanceMarginRate": 0.5, "maxLeverage": 1.0, "info": { - "bracket": "9", + "bracket": "10", "initialLeverage": "1", - "notionalCap": "30000000", - "notionalFloor": "15000000", + "notionalCap": "50000000", + "notionalFloor": "25000000", "maintMarginRatio": "0.5", - "cum": "5004620.0" + "cum": "8340820.0" } } ], @@ -42903,14 +45789,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" } }, @@ -42918,112 +45804,128 @@ "tier": 2.0, "currency": "USDT", "minNotional": 5000.0, - "maxNotional": 20000.0, - "maintenanceMarginRate": 0.02, - "maxLeverage": 25.0, + "maxNotional": 16000.0, + "maintenanceMarginRate": 0.015, + "maxLeverage": 50.0, "info": { "bracket": "2", - "initialLeverage": "25", - "notionalCap": "20000", + "initialLeverage": "50", + "notionalCap": "16000", "notionalFloor": "5000", - "maintMarginRatio": "0.02", + "maintMarginRatio": "0.015", "cum": "25.0" } }, { "tier": 3.0, "currency": "USDT", - "minNotional": 20000.0, - "maxNotional": 30000.0, - "maintenanceMarginRate": 0.025, - "maxLeverage": 20.0, + "minNotional": 16000.0, + "maxNotional": 80000.0, + "maintenanceMarginRate": 0.02, + "maxLeverage": 25.0, "info": { "bracket": "3", - "initialLeverage": "20", - "notionalCap": "30000", - "notionalFloor": "20000", - "maintMarginRatio": "0.025", - "cum": "125.0" + "initialLeverage": "25", + "notionalCap": "80000", + "notionalFloor": "16000", + "maintMarginRatio": "0.02", + "cum": "105.0" } }, { "tier": 4.0, "currency": "USDT", - "minNotional": 30000.0, - "maxNotional": 300000.0, - "maintenanceMarginRate": 0.05, - "maxLeverage": 10.0, + "minNotional": 80000.0, + "maxNotional": 160000.0, + "maintenanceMarginRate": 0.025, + "maxLeverage": 20.0, "info": { "bracket": "4", - "initialLeverage": "10", - "notionalCap": "300000", - "notionalFloor": "30000", - "maintMarginRatio": "0.05", - "cum": "875.0" + "initialLeverage": "20", + "notionalCap": "160000", + "notionalFloor": "80000", + "maintMarginRatio": "0.025", + "cum": "505.0" } }, { "tier": 5.0, "currency": "USDT", - "minNotional": 300000.0, - "maxNotional": 600000.0, - "maintenanceMarginRate": 0.1, - "maxLeverage": 5.0, + "minNotional": 160000.0, + "maxNotional": 800000.0, + "maintenanceMarginRate": 0.05, + "maxLeverage": 10.0, "info": { "bracket": "5", - "initialLeverage": "5", - "notionalCap": "600000", - "notionalFloor": "300000", - "maintMarginRatio": "0.1", - "cum": "15875.0" + "initialLeverage": "10", + "notionalCap": "800000", + "notionalFloor": "160000", + "maintMarginRatio": "0.05", + "cum": "4505.0" } }, { "tier": 6.0, "currency": "USDT", - "minNotional": 600000.0, - "maxNotional": 750000.0, - "maintenanceMarginRate": 0.125, - "maxLeverage": 4.0, + "minNotional": 800000.0, + "maxNotional": 1600000.0, + "maintenanceMarginRate": 0.1, + "maxLeverage": 5.0, "info": { "bracket": "6", - "initialLeverage": "4", - "notionalCap": "750000", - "notionalFloor": "600000", - "maintMarginRatio": "0.125", - "cum": "30875.0" + "initialLeverage": "5", + "notionalCap": "1600000", + "notionalFloor": "800000", + "maintMarginRatio": "0.1", + "cum": "44505.0" } }, { "tier": 7.0, "currency": "USDT", - "minNotional": 750000.0, - "maxNotional": 1500000.0, - "maintenanceMarginRate": 0.25, - "maxLeverage": 2.0, + "minNotional": 1600000.0, + "maxNotional": 2000000.0, + "maintenanceMarginRate": 0.125, + "maxLeverage": 4.0, "info": { "bracket": "7", - "initialLeverage": "2", - "notionalCap": "1500000", - "notionalFloor": "750000", - "maintMarginRatio": "0.25", - "cum": "124625.0" + "initialLeverage": "4", + "notionalCap": "2000000", + "notionalFloor": "1600000", + "maintMarginRatio": "0.125", + "cum": "84505.0" } }, { "tier": 8.0, "currency": "USDT", - "minNotional": 1500000.0, - "maxNotional": 3000000.0, + "minNotional": 2000000.0, + "maxNotional": 4000000.0, + "maintenanceMarginRate": 0.25, + "maxLeverage": 2.0, + "info": { + "bracket": "8", + "initialLeverage": "2", + "notionalCap": "4000000", + "notionalFloor": "2000000", + "maintMarginRatio": "0.25", + "cum": "334505.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": "3000000", - "notionalFloor": "1500000", + "notionalCap": "8000000", + "notionalFloor": "4000000", "maintMarginRatio": "0.5", - "cum": "499625.0" + "cum": "1334505.0" } } ], @@ -43146,112 +46048,112 @@ "tier": 1.0, "currency": "USDT", "minNotional": 0.0, - "maxNotional": 5000.0, - "maintenanceMarginRate": 0.015, - "maxLeverage": 50.0, + "maxNotional": 10000.0, + "maintenanceMarginRate": 0.01, + "maxLeverage": 75.0, "info": { "bracket": "1", - "initialLeverage": "50", - "notionalCap": "5000", + "initialLeverage": "75", + "notionalCap": "10000", "notionalFloor": "0", - "maintMarginRatio": "0.015", + "maintMarginRatio": "0.01", "cum": "0.0" } }, { "tier": 2.0, "currency": "USDT", - "minNotional": 5000.0, - "maxNotional": 25000.0, - "maintenanceMarginRate": 0.02, - "maxLeverage": 25.0, + "minNotional": 10000.0, + "maxNotional": 20000.0, + "maintenanceMarginRate": 0.015, + "maxLeverage": 50.0, "info": { "bracket": "2", - "initialLeverage": "25", - "notionalCap": "25000", - "notionalFloor": "5000", - "maintMarginRatio": "0.02", - "cum": "25.0" + "initialLeverage": "50", + "notionalCap": "20000", + "notionalFloor": "10000", + "maintMarginRatio": "0.015", + "cum": "50.0" } }, { "tier": 3.0, "currency": "USDT", - "minNotional": 25000.0, - "maxNotional": 50000.0, - "maintenanceMarginRate": 0.025, - "maxLeverage": 20.0, + "minNotional": 20000.0, + "maxNotional": 100000.0, + "maintenanceMarginRate": 0.02, + "maxLeverage": 25.0, "info": { "bracket": "3", - "initialLeverage": "20", - "notionalCap": "50000", - "notionalFloor": "25000", - "maintMarginRatio": "0.025", + "initialLeverage": "25", + "notionalCap": "100000", + "notionalFloor": "20000", + "maintMarginRatio": "0.02", "cum": "150.0" } }, { "tier": 4.0, "currency": "USDT", - "minNotional": 50000.0, - "maxNotional": 500000.0, - "maintenanceMarginRate": 0.05, - "maxLeverage": 10.0, + "minNotional": 100000.0, + "maxNotional": 200000.0, + "maintenanceMarginRate": 0.025, + "maxLeverage": 20.0, "info": { "bracket": "4", - "initialLeverage": "10", - "notionalCap": "500000", - "notionalFloor": "50000", - "maintMarginRatio": "0.05", - "cum": "1400.0" + "initialLeverage": "20", + "notionalCap": "200000", + "notionalFloor": "100000", + "maintMarginRatio": "0.025", + "cum": "650.0" } }, { "tier": 5.0, "currency": "USDT", - "minNotional": 500000.0, + "minNotional": 200000.0, "maxNotional": 1000000.0, - "maintenanceMarginRate": 0.1, - "maxLeverage": 5.0, + "maintenanceMarginRate": 0.05, + "maxLeverage": 10.0, "info": { "bracket": "5", - "initialLeverage": "5", + "initialLeverage": "10", "notionalCap": "1000000", - "notionalFloor": "500000", - "maintMarginRatio": "0.1", - "cum": "26400.0" + "notionalFloor": "200000", + "maintMarginRatio": "0.05", + "cum": "5650.0" } }, { "tier": 6.0, "currency": "USDT", "minNotional": 1000000.0, - "maxNotional": 1250000.0, - "maintenanceMarginRate": 0.125, - "maxLeverage": 4.0, + "maxNotional": 2000000.0, + "maintenanceMarginRate": 0.1, + "maxLeverage": 5.0, "info": { "bracket": "6", - "initialLeverage": "4", - "notionalCap": "1250000", + "initialLeverage": "5", + "notionalCap": "2000000", "notionalFloor": "1000000", - "maintMarginRatio": "0.125", - "cum": "51400.0" + "maintMarginRatio": "0.1", + "cum": "55650.0" } }, { "tier": 7.0, "currency": "USDT", - "minNotional": 1250000.0, + "minNotional": 2000000.0, "maxNotional": 2500000.0, - "maintenanceMarginRate": 0.25, - "maxLeverage": 2.0, + "maintenanceMarginRate": 0.125, + "maxLeverage": 4.0, "info": { "bracket": "7", - "initialLeverage": "2", + "initialLeverage": "4", "notionalCap": "2500000", - "notionalFloor": "1250000", - "maintMarginRatio": "0.25", - "cum": "207650.0" + "notionalFloor": "2000000", + "maintMarginRatio": "0.125", + "cum": "105650.0" } }, { @@ -43259,15 +46161,31 @@ "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": "8", + "bracket": "9", "initialLeverage": "1", - "notionalCap": "5000000", - "notionalFloor": "2500000", + "notionalCap": "10000000", + "notionalFloor": "5000000", "maintMarginRatio": "0.5", - "cum": "832650.0" + "cum": "1668150.0" } } ], diff --git a/freqtrade/exchange/bitpanda.py b/freqtrade/exchange/bitpanda.py index 1e93256e7..cca961cb7 100644 --- a/freqtrade/exchange/bitpanda.py +++ b/freqtrade/exchange/bitpanda.py @@ -2,7 +2,7 @@ import logging from datetime import datetime, timezone -from typing import Dict, List, Optional +from typing import Optional from freqtrade.exchange import Exchange @@ -17,8 +17,8 @@ class Bitpanda(Exchange): """ def get_trades_for_order( - self, order_id: str, pair: str, since: datetime, params: Optional[Dict] = None - ) -> List: + self, order_id: str, pair: str, since: datetime, params: Optional[dict] = None + ) -> list: """ Fetch Orders using the "fetch_my_trades" endpoint and filter them by order-id. The "since" argument passed in is coming from the database and is in UTC, diff --git a/freqtrade/exchange/bybit.py b/freqtrade/exchange/bybit.py index 719c64dc3..4face01c9 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, Dict, List, Optional, Tuple +from typing import Any, Optional import ccxt @@ -36,6 +36,12 @@ class Bybit(Exchange): "order_time_in_force": ["GTC", "FOK", "IOC", "PO"], "ws_enabled": True, "trades_has_history": False, # Endpoint doesn't support pagination + "exchange_has_overrides": { + # Bybit spot does not support fetch_order + # Unless the account is unified. + # TODO: Can be removed once bybit fully forces all accounts to unified mode. + "fetchOrder": False, + }, } _ft_has_futures: FtHas = { "ohlcv_has_history": True, @@ -51,16 +57,19 @@ class Bybit(Exchange): PriceType.MARK: "MarkPrice", PriceType.INDEX: "IndexPrice", }, + "exchange_has_overrides": { + "fetchOrder": True, + }, } - _supported_trading_mode_margin_pairs: List[Tuple[TradingMode, MarginMode]] = [ + _supported_trading_mode_margin_pairs: list[tuple[TradingMode, MarginMode]] = [ # TradingMode.SPOT always supported and not required in this list # (TradingMode.FUTURES, MarginMode.CROSS), (TradingMode.FUTURES, MarginMode.ISOLATED) ] @property - def _ccxt_config(self) -> Dict: + def _ccxt_config(self) -> dict: # Parameters to add directly to ccxt sync/async initialization. # ccxt defaults to swap mode. config = {} @@ -69,7 +78,7 @@ class Bybit(Exchange): config.update(super()._ccxt_config) return config - def market_is_future(self, market: Dict[str, Any]) -> bool: + def market_is_future(self, market: dict[str, Any]) -> bool: main = super().market_is_future(market) # For ByBit, we'll only support USDT markets for now. return main and market["settle"] == "USDT" @@ -108,7 +117,7 @@ class Bybit(Exchange): def ohlcv_candle_limit( self, timeframe: str, candle_type: CandleType, since_ms: Optional[int] = 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) @@ -126,7 +135,7 @@ class Bybit(Exchange): leverage: float, reduceOnly: bool, time_in_force: str = "GTC", - ) -> Dict: + ) -> dict: params = super()._get_params( side=side, ordertype=ordertype, @@ -147,8 +156,7 @@ class Bybit(Exchange): stake_amount: float, leverage: float, wallet_balance: float, # Or margin balance - mm_ex_1: float = 0.0, # (Binance) Cross only - upnl_ex_1: float = 0.0, # (Binance) Cross only + open_trades: list, ) -> Optional[float]: """ Important: Must be fetching data from cached values as this is used by backtesting! @@ -178,6 +186,7 @@ class Bybit(Exchange): :param wallet_balance: Amount of margin_mode in the wallet being used to trade Cross-Margin Mode: crossWalletBalance Isolated-Margin Mode: isolatedWalletBalance + :param open_trades: List of other open trades in the same wallet """ market = self.markets[pair] @@ -220,7 +229,7 @@ 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: Optional[dict] = None) -> list[dict]: """ Fetch all orders for a pair "since" :param pair: Pair for the query @@ -237,7 +246,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: Optional[dict] = None) -> dict: if self.exchange_has("fetchOrder"): # Set acknowledged to True to avoid ccxt exception params = {"acknowledged": True} @@ -255,7 +264,7 @@ class Bybit(Exchange): return order @retrier - def get_leverage_tiers(self) -> Dict[str, List[Dict]]: + def get_leverage_tiers(self) -> dict[str, list[dict]]: """ Cache leverage tiers for 1 day, since they are not expected to change often, and bybit requires pagination to fetch all tiers. diff --git a/freqtrade/exchange/common.py b/freqtrade/exchange/common.py index 1bb738dcb..b32216b05 100644 --- a/freqtrade/exchange/common.py +++ b/freqtrade/exchange/common.py @@ -2,7 +2,7 @@ import asyncio import logging import time from functools import wraps -from typing import Any, Callable, Dict, List, Optional, TypeVar, cast, overload +from typing import Any, Callable, Optional, TypeVar, cast, overload from freqtrade.constants import ExchangeConfig from freqtrade.exceptions import DDosProtection, RetryableOrderError, TemporaryError @@ -62,7 +62,7 @@ SUPPORTED_EXCHANGES = [ ] # either the main, or replacement methods (array) is required -EXCHANGE_HAS_REQUIRED: Dict[str, List[str]] = { +EXCHANGE_HAS_REQUIRED: dict[str, list[str]] = { # Required / private "fetchOrder": ["fetchOpenOrder", "fetchClosedOrder"], "fetchL2OrderBook": ["fetchTicker"], diff --git a/freqtrade/exchange/exchange.py b/freqtrade/exchange/exchange.py index 9d84f59e4..48bb84369 100644 --- a/freqtrade/exchange/exchange.py +++ b/freqtrade/exchange/exchange.py @@ -7,11 +7,12 @@ import asyncio import inspect import logging import signal +from collections.abc import Coroutine from copy import deepcopy from datetime import datetime, timedelta, timezone from math import floor, isnan from threading import Lock -from typing import Any, Coroutine, Dict, List, Literal, Optional, Tuple, Union +from typing import Any, Literal, Optional, Union import ccxt import ccxt.pro as ccxt_pro @@ -114,10 +115,10 @@ logger = logging.getLogger(__name__) class Exchange: # Parameters to add directly to buy/sell calls (like agreeing to trading agreement) - _params: Dict = {} + _params: dict = {} # Additional parameters - added to the ccxt object - _ccxt_params: Dict = {} + _ccxt_params: dict = {} # Dict to specify which options each exchange implements # This defines defaults, which can be selectively overridden by subclasses using _ft_has @@ -160,7 +161,7 @@ class Exchange: _ft_has: FtHas = {} _ft_has_futures: FtHas = {} - _supported_trading_mode_margin_pairs: List[Tuple[TradingMode, MarginMode]] = [ + _supported_trading_mode_margin_pairs: list[tuple[TradingMode, MarginMode]] = [ # TradingMode.SPOT always supported and not required in this list ] @@ -181,9 +182,9 @@ class Exchange: self._api_async: ccxt_pro.Exchange self._ws_async: ccxt_pro.Exchange = None self._exchange_ws: Optional[ExchangeWS] = None - self._markets: Dict = {} - self._trading_fees: Dict[str, Any] = {} - self._leverage_tiers: Dict[str, List[Dict]] = {} + self._markets: dict = {} + self._trading_fees: dict[str, Any] = {} + self._leverage_tiers: dict[str, list[dict]] = {} # Lock event loop. This is necessary to avoid race-conditions when using force* commands # Due to funding fee fetching. self._loop_lock = Lock() @@ -193,7 +194,7 @@ class Exchange: self._config.update(config) # Holds last candle refreshed time of each pair - self._pairs_last_refresh_time: Dict[PairWithTimeframe, int] = {} + self._pairs_last_refresh_time: dict[PairWithTimeframe, int] = {} # Timestamp of last markets refresh self._last_markets_refresh: int = 0 @@ -208,19 +209,19 @@ class Exchange: self._entry_rate_cache: TTLCache = TTLCache(maxsize=100, ttl=300) # Holds candles - self._klines: Dict[PairWithTimeframe, DataFrame] = {} - self._expiring_candle_cache: Dict[Tuple[str, int], PeriodicCache] = {} + self._klines: dict[PairWithTimeframe, DataFrame] = {} + self._expiring_candle_cache: dict[tuple[str, int], PeriodicCache] = {} # Holds public_trades - self._trades: Dict[PairWithTimeframe, DataFrame] = {} + self._trades: dict[PairWithTimeframe, DataFrame] = {} # Holds all open sell orders for dry_run - self._dry_run_open_orders: Dict[str, Any] = {} + self._dry_run_open_orders: dict[str, Any] = {} if config["dry_run"]: logger.info("Instance is running with dry_run enabled") logger.info(f"Using CCXT {ccxt.__version__}") - exchange_conf: Dict[str, Any] = exchange_config if exchange_config else config["exchange"] + exchange_conf: dict[str, Any] = exchange_config if exchange_config else config["exchange"] remove_exchange_credentials(exchange_conf, config.get("dry_run", False)) self.log_responses = exchange_conf.get("log_responses", False) @@ -339,7 +340,7 @@ class Exchange: self.validate_freqai(config) def _init_ccxt( - self, exchange_config: Dict[str, Any], sync: bool, ccxt_kwargs: Dict[str, Any] + self, exchange_config: dict[str, Any], sync: bool, ccxt_kwargs: dict[str, Any] ) -> ccxt.Exchange: """ Initialize ccxt with given config and return valid ccxt instance. @@ -390,7 +391,7 @@ class Exchange: return api @property - def _ccxt_config(self) -> Dict: + def _ccxt_config(self) -> dict: # Parameters to add directly to ccxt sync/async initialization. if self.trading_mode == TradingMode.MARGIN: return {"options": {"defaultType": "margin"}} @@ -410,11 +411,11 @@ class Exchange: return self._api.id @property - def timeframes(self) -> List[str]: + def timeframes(self) -> list[str]: return list((self._api.timeframes or {}).keys()) @property - def markets(self) -> Dict[str, Any]: + def markets(self) -> dict[str, Any]: """exchange ccxt markets""" if not self._markets: logger.info("Markets were not loaded. Loading them now..") @@ -471,14 +472,14 @@ class Exchange: def get_markets( self, - base_currencies: Optional[List[str]] = None, - quote_currencies: Optional[List[str]] = None, + base_currencies: Optional[list[str]] = None, + quote_currencies: Optional[list[str]] = None, spot_only: bool = False, margin_only: bool = False, futures_only: bool = False, tradable_only: bool = True, active_only: bool = False, - ) -> Dict[str, Any]: + ) -> dict[str, Any]: """ Return exchange ccxt markets, filtered out by base currency and quote currency if this was requested in parameters. @@ -503,7 +504,7 @@ class Exchange: markets = {k: v for k, v in markets.items() if market_is_active(v)} return markets - def get_quote_currencies(self) -> List[str]: + def get_quote_currencies(self) -> list[str]: """ Return a list of supported quote currencies """ @@ -518,19 +519,19 @@ class Exchange: """Return a pair's base currency (base/quote:settlement)""" return self.markets.get(pair, {}).get("base", "") - def market_is_future(self, market: Dict[str, Any]) -> bool: + def market_is_future(self, market: dict[str, Any]) -> bool: return ( market.get(self._ft_has["ccxt_futures_name"], False) is True and market.get("linear", False) is True ) - def market_is_spot(self, market: Dict[str, Any]) -> bool: + def market_is_spot(self, market: dict[str, Any]) -> bool: return market.get("spot", False) is True - def market_is_margin(self, market: Dict[str, Any]) -> bool: + def market_is_margin(self, market: dict[str, Any]) -> bool: return market.get("margin", False) is True - def market_is_tradable(self, market: Dict[str, Any]) -> bool: + def market_is_tradable(self, market: dict[str, Any]) -> bool: """ Check if the market symbol is tradable by Freqtrade. Ensures that Configured mode aligns to @@ -578,7 +579,7 @@ class Exchange: else: return 1 - def _trades_contracts_to_amount(self, trades: List) -> List: + def _trades_contracts_to_amount(self, trades: list) -> list: if len(trades) > 0 and "symbol" in trades[0]: contract_size = self.get_contract_size(trades[0]["symbol"]) if contract_size != 1: @@ -586,7 +587,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: dict) -> dict: if "symbol" in order and order["symbol"] is not None: contract_size = self.get_contract_size(order["symbol"]) if contract_size != 1: @@ -620,7 +621,7 @@ class Exchange: if self._exchange_ws: self._exchange_ws.reset_connections() - async def _api_reload_markets(self, reload: bool = False) -> Dict[str, Any]: + async def _api_reload_markets(self, reload: bool = False) -> dict[str, Any]: try: return await self._api_async.load_markets(reload=reload, params={}) except ccxt.DDoSProtection as e: @@ -632,7 +633,7 @@ class Exchange: except ccxt.BaseError as e: raise TemporaryError(e) from e - def _load_async_markets(self, reload: bool = False) -> Dict[str, Any]: + def _load_async_markets(self, reload: bool = False) -> dict[str, Any]: try: markets = self.loop.run_until_complete(self._api_reload_markets(reload=reload)) @@ -734,7 +735,7 @@ class Exchange: ): raise ConfigurationError("Timeframes < 1m are currently not supported by Freqtrade.") - def validate_ordertypes(self, order_types: Dict) -> None: + def validate_ordertypes(self, order_types: dict) -> None: """ Checks if order-types configured in strategy/config are supported """ @@ -743,7 +744,7 @@ class Exchange: raise ConfigurationError(f"Exchange {self.name} does not support market orders.") self.validate_stop_ordertypes(order_types) - def validate_stop_ordertypes(self, order_types: Dict) -> None: + def validate_stop_ordertypes(self, order_types: dict) -> None: """ Validate stoploss order types """ @@ -762,7 +763,7 @@ class Exchange: f"On exchange stoploss price type is not supported for {self.name}." ) - def validate_pricing(self, pricing: Dict) -> None: + def validate_pricing(self, pricing: dict) -> None: if pricing.get("use_order_book", False) and not self.exchange_has("fetchL2OrderBook"): raise ConfigurationError(f"Orderbook not available for {self.name}.") if not pricing.get("use_order_book", False) and ( @@ -770,7 +771,7 @@ class Exchange: ): raise ConfigurationError(f"Ticker pricing not available for {self.name}.") - def validate_order_time_in_force(self, order_time_in_force: Dict) -> None: + def validate_order_time_in_force(self, order_time_in_force: dict) -> None: """ Checks if order time in force configured in strategy/config are supported """ @@ -782,7 +783,7 @@ class Exchange: f"Time in force policies are not supported for {self.name} yet." ) - def validate_orderflow(self, exchange: Dict) -> None: + def validate_orderflow(self, exchange: dict) -> None: if exchange.get("use_public_trades", False) and ( not self.exchange_has("fetchTrades") or not self._ft_has["trades_has_history"] ): @@ -1000,16 +1001,16 @@ class Exchange: amount: float, rate: float, leverage: float, - params: Optional[Dict] = None, + params: Optional[dict] = None, stop_loss: bool = False, - ) -> Dict[str, Any]: + ) -> dict[str, Any]: 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: dict[str, Any] = { "id": order_id, "symbol": pair, "price": rate, @@ -1071,9 +1072,9 @@ class Exchange: def add_dry_order_fee( self, pair: str, - dry_order: Dict[str, Any], + dry_order: dict[str, Any], taker_or_maker: MakerTaker, - ) -> Dict[str, Any]: + ) -> dict[str, Any]: fee = self.get_fee(pair, taker_or_maker=taker_or_maker) dry_order.update( { @@ -1157,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: dict[str, Any], immediate: bool = False, orderbook: Optional[OrderBook] = None + ) -> dict[str, Any]: """ Check dry-run limit order fill and update fee (if it filled). """ @@ -1185,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) -> dict[str, Any]: """ Return dry-run order Only call if running in dry-run mode. @@ -1221,7 +1222,7 @@ class Exchange: leverage: float, reduceOnly: bool, time_in_force: str = "GTC", - ) -> Dict: + ) -> dict: params = self._params.copy() if time_in_force != "GTC" and ordertype != "market": params.update({"timeInForce": time_in_force.upper()}) @@ -1229,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) ) @@ -1247,7 +1248,7 @@ class Exchange: leverage: float, reduceOnly: bool = False, time_in_force: str = "GTC", - ) -> Dict: + ) -> dict: if self._config["dry_run"]: dry_order = self.create_dry_run_order( pair, ordertype, side, amount, self.price_to_precision(pair, rate), leverage @@ -1259,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: @@ -1305,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: dict, side: str) -> bool: """ Verify stop_loss against stoploss-order value (limit or price) Returns True if adjustment is necessary. @@ -1318,8 +1319,8 @@ class Exchange: or (side == "buy" and stop_loss < float(order[price_param])) ) - def _get_stop_order_type(self, user_order_type) -> Tuple[str, str]: - available_order_Types: Dict[str, str] = self._ft_has["stoploss_order_types"] + def _get_stop_order_type(self, user_order_type) -> tuple[str, str]: + available_order_Types: dict[str, str] = self._ft_has["stoploss_order_types"] if user_order_type in available_order_Types.keys(): ordertype = available_order_Types[user_order_type] @@ -1329,7 +1330,7 @@ class Exchange: user_order_type = list(available_order_Types.keys())[0] return ordertype, user_order_type - def _get_stop_limit_rate(self, stop_price: float, order_types: Dict, side: str) -> float: + def _get_stop_limit_rate(self, stop_price: float, order_types: dict, side: str) -> float: # Limit price threshold: As limit price should always be below stop-price limit_price_pct = order_types.get("stoploss_on_exchange_limit_ratio", 0.99) if side == "sell": @@ -1351,7 +1352,7 @@ class Exchange: ) return limit_rate - def _get_stop_params(self, side: BuySell, ordertype: str, stop_price: float) -> Dict: + def _get_stop_params(self, side: BuySell, ordertype: str, stop_price: float) -> dict: params = self._params.copy() # Verify if stopPrice works for your exchange, else configure stop_price_param params.update({self._ft_has["stop_price_param"]: stop_price}) @@ -1363,10 +1364,10 @@ class Exchange: pair: str, amount: float, stop_price: float, - order_types: Dict, + order_types: dict, side: BuySell, leverage: float, - ) -> Dict: + ) -> dict: """ creates a stoploss order. requires `_ft_has['stoploss_order_types']` to be set as a dict mapping limit and market @@ -1459,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) -> dict: """ Emulated fetch_order if the exchange doesn't support fetch_order, but requires separate calls for open and closed orders. @@ -1493,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: Optional[dict] = None) -> dict: if self._config["dry_run"]: return self.fetch_dry_run_order(order_id) if params is None: @@ -1522,12 +1523,12 @@ 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: Optional[dict] = None) -> dict: 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: + ) -> dict: """ Simple wrapper calling either fetch_order or fetch_stoploss_order depending on the stoploss_order parameter @@ -1539,7 +1540,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: dict) -> bool: """ Verify if an order has been cancelled without being partially filled :param order: Order dict as returned from fetch_order() @@ -1548,7 +1549,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: Optional[dict] = None) -> dict: if self._config["dry_run"]: try: order = self.fetch_dry_run_order(order_id) @@ -1577,8 +1578,8 @@ class Exchange: raise OperationalException(e) from e def cancel_stoploss_order( - self, order_id: str, pair: str, params: Optional[Dict] = None - ) -> Dict: + self, order_id: str, pair: str, params: Optional[dict] = None + ) -> dict: return self.cancel_order(order_id, pair, params) def is_cancel_order_result_suitable(self, corder) -> bool: @@ -1588,7 +1589,7 @@ class Exchange: 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) -> dict: """ Cancel order returning a result. Creates a fake result if cancel order returns a non-usable result @@ -1619,7 +1620,7 @@ 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) -> dict: """ Cancel stoploss order returning a result. Creates a fake result if cancel order returns a non-usable result @@ -1661,7 +1662,7 @@ class Exchange: raise OperationalException(e) from e @retrier - def fetch_positions(self, pair: Optional[str] = None) -> List[CcxtPosition]: + def fetch_positions(self, pair: Optional[str] = None) -> list[CcxtPosition]: """ Fetch positions from the exchange. If no pair is given, all positions are returned. @@ -1673,7 +1674,7 @@ class Exchange: symbols = [] if pair: symbols.append(pair) - positions: List[CcxtPosition] = self._api.fetch_positions(symbols) + positions: list[CcxtPosition] = self._api.fetch_positions(symbols) self._log_exchange_response("fetch_positions", positions) return positions except ccxt.DDoSProtection as e: @@ -1685,7 +1686,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[dict]: orders = [] if self.exchange_has("fetchClosedOrders"): orders = self._api.fetch_closed_orders(pair, since=since_ms) @@ -1695,7 +1696,7 @@ 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: Optional[dict] = None) -> list[dict]: """ Fetch all orders for a pair "since" :param pair: Pair for the query @@ -1711,7 +1712,7 @@ class Exchange: if not params: params = {} try: - orders: List[Dict] = self._api.fetch_orders(pair, since=since_ms, params=params) + orders: list[dict] = 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 @@ -1731,7 +1732,7 @@ class Exchange: raise OperationalException(e) from e @retrier - def fetch_trading_fees(self) -> Dict[str, Any]: + def fetch_trading_fees(self) -> dict[str, Any]: """ Fetch user account trading fees Can be cached, should not update often. @@ -1743,7 +1744,7 @@ class Exchange: ): return {} try: - trading_fees: Dict[str, Any] = self._api.fetch_trading_fees() + trading_fees: dict[str, Any] = self._api.fetch_trading_fees() self._log_exchange_response("fetch_trading_fees", trading_fees) return trading_fees except ccxt.DDoSProtection as e: @@ -1756,7 +1757,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: Optional[list[str]] = None, cached: bool = False) -> dict: """ :param symbols: List of symbols to fetch :param cached: Allow cached result @@ -1789,7 +1790,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: Optional[list[str]] = None, cached: bool = False) -> Tickers: """ :param cached: Allow cached result :return: fetch_tickers result @@ -1849,7 +1850,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: Optional[list[int]], range_required: bool = True ): """ Get next greater value in the list. @@ -1890,7 +1891,7 @@ class Exchange: except ccxt.BaseError as e: raise OperationalException(e) from e - def _get_price_side(self, side: str, is_short: bool, conf_strategy: Dict) -> BidAsk: + def _get_price_side(self, side: str, is_short: bool, conf_strategy: dict) -> BidAsk: price_side = conf_strategy["price_side"] if price_side in ("same", "other"): @@ -1962,7 +1963,7 @@ class Exchange: return rate def _get_rate_from_ticker( - self, side: EntryExit, ticker: Ticker, conf_strategy: Dict[str, Any], price_side: BidAsk + self, side: EntryExit, ticker: Ticker, conf_strategy: dict[str, Any], price_side: BidAsk ) -> Optional[float]: """ Get rate from ticker. @@ -2008,7 +2009,7 @@ class Exchange: ) return rate - def get_rates(self, pair: str, refresh: bool, is_short: bool) -> Tuple[float, float]: + def get_rates(self, pair: str, refresh: bool, is_short: bool) -> tuple[float, float]: entry_rate = None exit_rate = None if not refresh: @@ -2042,8 +2043,8 @@ class Exchange: @retrier def get_trades_for_order( - self, order_id: str, pair: str, since: datetime, params: Optional[Dict] = None - ) -> List: + self, order_id: str, pair: str, since: datetime, params: Optional[dict] = None + ) -> list: """ Fetch Orders using the "fetch_my_trades" endpoint and filter them by order-id. The "since" argument passed in is coming from the database and is in UTC, @@ -2089,7 +2090,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: dict[str, Any]) -> str: return order["id"] @retrier @@ -2138,7 +2139,7 @@ class Exchange: raise OperationalException(e) from e @staticmethod - def order_has_fee(order: Dict) -> bool: + def order_has_fee(order: dict) -> bool: """ Verifies if the passed in order dict has the needed keys to extract fees, and that these keys (currency, cost) are not empty. @@ -2156,7 +2157,7 @@ class Exchange: ) def calculate_fee_rate( - self, fee: Dict, symbol: str, cost: float, amount: float + self, fee: dict, symbol: str, cost: float, amount: float ) -> Optional[float]: """ Calculate fee rate if it's not given by the exchange. @@ -2196,8 +2197,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, symbol: str, cost: float, amount: float + ) -> tuple[float, str, Optional[float]]: """ Extract tuple of cost, currency, rate. Requires order_has_fee to run first! @@ -2277,7 +2278,7 @@ class Exchange: for since in range(since_ms, until_ms or dt_ts(), one_call) ] - data: List = [] + data: list = [] # Chunk requests into batches of 100 to avoid overwhelming ccxt Throttling for input_coro in chunks(input_coroutines, 100): results = await asyncio.gather(*input_coro, return_exceptions=True) @@ -2371,11 +2372,11 @@ class Exchange: def _build_ohlcv_dl_jobs( self, pair_list: ListPairsWithTimeframes, since_ms: Optional[int], cache: bool - ) -> Tuple[List[Coroutine], List[PairWithTimeframe]]: + ) -> tuple[list[Coroutine], list[PairWithTimeframe]]: """ Build Coroutines to execute as part of refresh_latest_ohlcv """ - input_coroutines: List[Coroutine[Any, Any, OHLCVResponse]] = [] + input_coroutines: list[Coroutine[Any, Any, OHLCVResponse]] = [] cached_pairs = [] for pair, timeframe, candle_type in set(pair_list): if timeframe not in self.timeframes and candle_type in ( @@ -2411,7 +2412,7 @@ class Exchange: pair: str, timeframe: str, c_type: CandleType, - ticks: List[List], + ticks: list[list], cache: bool, drop_incomplete: bool, ) -> DataFrame: @@ -2450,7 +2451,7 @@ class Exchange: since_ms: Optional[int] = None, cache: bool = True, drop_incomplete: Optional[bool] = None, - ) -> Dict[PairWithTimeframe, DataFrame]: + ) -> dict[PairWithTimeframe, DataFrame]: """ Refresh in-memory OHLCV asynchronously and set `_klines` with the result Loops asynchronously over pair_list and downloads all pairs async (semi-parallel). @@ -2499,8 +2500,8 @@ class Exchange: return results_df def refresh_ohlcv_with_cache( - self, pairs: List[PairWithTimeframe], since_ms: int - ) -> Dict[PairWithTimeframe, DataFrame]: + self, pairs: list[PairWithTimeframe], since_ms: int + ) -> dict[PairWithTimeframe, DataFrame]: """ Refresh ohlcv data for all pairs in needed_pairs if necessary. Caches data with expiring per timeframe. @@ -2618,7 +2619,7 @@ class Exchange: timeframe: str, limit: int, since_ms: Optional[int] = None, - ) -> List[List]: + ) -> list[list]: """ Fetch funding rate history - used to selectively override this by subclasses. """ @@ -2652,7 +2653,7 @@ class Exchange: pair: str, timeframe: str, c_type: CandleType, - ticks: List[List], + ticks: list[list], cache: bool, first_required_candle_date: int, ) -> DataFrame: @@ -2676,13 +2677,13 @@ class Exchange: async def _build_trades_dl_jobs( self, pairwt: PairWithTimeframe, data_handler, cache: bool - ) -> Tuple[PairWithTimeframe, Optional[DataFrame]]: + ) -> tuple[PairWithTimeframe, Optional[DataFrame]]: """ Build coroutines to refresh trades for (they're then called through async.gather) """ pair, timeframe, candle_type = pairwt since_ms = None - new_ticks: List = [] + new_ticks: list = [] all_stored_ticks_df = DataFrame(columns=DEFAULT_TRADES_COLUMNS + ["date"]) first_candle_ms = self.needed_candle_for_trades_ms(timeframe, candle_type) # refresh, if @@ -2767,7 +2768,7 @@ class Exchange: pair_list: ListPairsWithTimeframes, *, cache: bool = True, - ) -> Dict[PairWithTimeframe, DataFrame]: + ) -> dict[PairWithTimeframe, DataFrame]: """ Refresh in-memory TRADES asynchronously and set `_trades` with the result Loops asynchronously over pair_list and downloads all pairs async (semi-parallel). @@ -2821,7 +2822,7 @@ class Exchange: @retrier_async async def _async_fetch_trades( self, pair: str, since: Optional[int] = None, params: Optional[dict] = None - ) -> Tuple[List[List], Any]: + ) -> tuple[list[list], Any]: """ Asynchronously gets trade history using fetch_trades. Handles exchange errors, does one call to the exchange. @@ -2867,7 +2868,7 @@ class Exchange: """ return True - def _get_trade_pagination_next_value(self, trades: List[Dict]): + def _get_trade_pagination_next_value(self, trades: list[dict]): """ Extract pagination id for the next "from_id" value Applies only to fetch_trade_history by id. @@ -2881,7 +2882,7 @@ class Exchange: async def _async_get_trade_history_id( self, pair: str, until: int, since: Optional[int] = None, from_id: Optional[str] = None - ) -> Tuple[str, List[List]]: + ) -> tuple[str, list[list]]: """ Asynchronously gets trade history using fetch_trades use this when exchange uses id-based iteration (check `self._trades_pagination`) @@ -2892,7 +2893,7 @@ class Exchange: returns tuple: (pair, trades-list) """ - trades: List[List] = [] + trades: list[list] = [] # DEFAULT_TRADES_COLUMNS: 0 -> timestamp # DEFAULT_TRADES_COLUMNS: 1 -> id has_overlap = self._ft_has.get("trades_pagination_overlap", True) @@ -2936,7 +2937,7 @@ class Exchange: async def _async_get_trade_history_time( self, pair: str, until: int, since: Optional[int] = None - ) -> Tuple[str, List[List]]: + ) -> tuple[str, list[list]]: """ Asynchronously gets trade history using fetch_trades, when the exchange uses time-based iteration (check `self._trades_pagination`) @@ -2946,7 +2947,7 @@ class Exchange: returns tuple: (pair, trades-list) """ - trades: List[List] = [] + trades: list[list] = [] # DEFAULT_TRADES_COLUMNS: 0 -> timestamp # DEFAULT_TRADES_COLUMNS: 1 -> id while True: @@ -2979,7 +2980,7 @@ class Exchange: since: Optional[int] = None, until: Optional[int] = None, from_id: Optional[str] = None, - ) -> Tuple[str, List[List]]: + ) -> tuple[str, list[list]]: """ Async wrapper handling downloading trades using either time or id based methods. """ @@ -3010,7 +3011,7 @@ class Exchange: since: Optional[int] = None, until: Optional[int] = None, from_id: Optional[str] = None, - ) -> Tuple[str, List]: + ) -> tuple[str, list]: """ Get trade history data using asyncio. Handles all async work and returns the list of candles. @@ -3070,7 +3071,7 @@ class Exchange: raise OperationalException(e) from e @retrier - def get_leverage_tiers(self) -> Dict[str, List[Dict]]: + def get_leverage_tiers(self) -> dict[str, list[dict]]: try: return self._api.fetch_leverage_tiers() except ccxt.DDoSProtection as e: @@ -3083,7 +3084,7 @@ class Exchange: raise OperationalException(e) from e @retrier_async - async def get_market_leverage_tiers(self, symbol: str) -> Tuple[str, List[Dict]]: + async def get_market_leverage_tiers(self, symbol: str) -> tuple[str, list[dict]]: """Leverage tiers per symbol""" try: tier = await self._api_async.fetch_market_leverage_tiers(symbol) @@ -3098,7 +3099,7 @@ class Exchange: except ccxt.BaseError as e: raise OperationalException(e) from e - def load_leverage_tiers(self) -> Dict[str, List[Dict]]: + def load_leverage_tiers(self) -> dict[str, list[dict]]: if self.trading_mode == TradingMode.FUTURES: if self.exchange_has("fetchLeverageTiers"): # Fetch all leverage tiers at once @@ -3117,7 +3118,7 @@ class Exchange: ) ] - tiers: Dict[str, List[Dict]] = {} + tiers: dict[str, list[dict]] = {} tiers_cached = self.load_cached_leverage_tiers(self._config["stake_currency"]) if tiers_cached: @@ -3158,7 +3159,7 @@ class Exchange: return tiers return {} - def cache_leverage_tiers(self, tiers: Dict[str, List[Dict]], stake_currency: str) -> None: + def cache_leverage_tiers(self, tiers: dict[str, list[dict]], stake_currency: str) -> None: filename = self._config["datadir"] / "futures" / f"leverage_tiers_{stake_currency}.json" if not filename.parent.is_dir(): filename.parent.mkdir(parents=True) @@ -3170,7 +3171,7 @@ class Exchange: def load_cached_leverage_tiers( self, stake_currency: str, cache_time: Optional[timedelta] = None - ) -> Optional[Dict[str, List[Dict]]]: + ) -> Optional[dict[str, list[dict]]]: """ Load cached leverage tiers from disk :param cache_time: The maximum age of the cache before it is considered outdated @@ -3188,7 +3189,7 @@ class Exchange: if updated_dt < datetime.now(timezone.utc) - cache_time: logger.info("Cached leverage tiers are outdated. Will update.") return None - return tiers["data"] + return tiers.get("data") except Exception: logger.exception("Error loading cached leverage tiers. Refreshing.") return None @@ -3205,7 +3206,7 @@ class Exchange: pair_tiers.append(self.parse_leverage_tier(tier)) self._leverage_tiers[pair] = pair_tiers - def parse_leverage_tier(self, tier) -> Dict: + def parse_leverage_tier(self, tier) -> dict: info = tier.get("info", {}) return { "minNotional": tier["minNotional"], @@ -3345,7 +3346,7 @@ class Exchange: pair: str, margin_mode: MarginMode, accept_fail: bool = False, - params: Optional[Dict] = None, + params: Optional[dict] = None, ): """ Set's the margin mode on the exchange to cross or isolated for a specific pair @@ -3532,8 +3533,7 @@ class Exchange: stake_amount: float, leverage: float, wallet_balance: float, - mm_ex_1: float = 0.0, # (Binance) Cross only - upnl_ex_1: float = 0.0, # (Binance) Cross only + open_trades: Optional[list] = None, ) -> Optional[float]: """ Set's the margin mode on the exchange to cross or isolated for a specific pair @@ -3555,8 +3555,7 @@ class Exchange: leverage=leverage, stake_amount=stake_amount, wallet_balance=wallet_balance, - mm_ex_1=mm_ex_1, - upnl_ex_1=upnl_ex_1, + open_trades=open_trades or [], ) else: positions = self.fetch_positions(pair) @@ -3582,8 +3581,7 @@ class Exchange: stake_amount: float, leverage: float, wallet_balance: float, # Or margin balance - mm_ex_1: float = 0.0, # (Binance) Cross only - upnl_ex_1: float = 0.0, # (Binance) Cross only + open_trades: list, ) -> Optional[float]: """ Important: Must be fetching data from cached values as this is used by backtesting! @@ -3608,10 +3606,7 @@ class Exchange: :param wallet_balance: Amount of margin_mode in the wallet being used to trade Cross-Margin Mode: crossWalletBalance Isolated-Margin Mode: isolatedWalletBalance - - # * Not required by Gate or OKX - :param mm_ex_1: - :param upnl_ex_1: + :param open_trades: List of other open trades in the same wallet """ market = self.markets[pair] @@ -3638,7 +3633,7 @@ class Exchange: self, pair: str, notional_value: float, - ) -> Tuple[float, Optional[float]]: + ) -> tuple[float, Optional[float]]: """ 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 ef3ed274b..e9c58ec38 100644 --- a/freqtrade/exchange/exchange_types.py +++ b/freqtrade/exchange/exchange_types.py @@ -1,11 +1,11 @@ -from typing import Dict, List, Optional, Tuple, TypedDict +from typing import Optional, TypedDict from freqtrade.enums import CandleType class FtHas(TypedDict, total=False): - order_time_in_force: List[str] - exchange_has_overrides: Dict[str, bool] + order_time_in_force: list[str] + exchange_has_overrides: dict[str, bool] marketOrderRequiresPrice: bool # Stoploss on exchange @@ -13,16 +13,16 @@ class FtHas(TypedDict, total=False): stop_price_param: str stop_price_prop: str stop_price_type_field: str - stop_price_type_value_mapping: Dict - stoploss_order_types: Dict[str, str] + stop_price_type_value_mapping: dict + stoploss_order_types: dict[str, str] # ohlcv - ohlcv_params: Dict + ohlcv_params: dict ohlcv_candle_limit: int ohlcv_has_history: bool ohlcv_partial_candle: bool ohlcv_require_since: bool ohlcv_volume_currency: str - ohlcv_candle_limit_per_timeframe: Dict[str, int] + ohlcv_candle_limit_per_timeframe: dict[str, int] # Tickers tickers_have_quoteVolume: bool tickers_have_percentage: bool @@ -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: Optional[list[int]] 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[str] # Websocket control ws_enabled: bool @@ -63,13 +63,13 @@ class Ticker(TypedDict): # Several more - only listing required. -Tickers = Dict[str, Ticker] +Tickers = dict[str, Ticker] class OrderBook(TypedDict): symbol: str - bids: List[Tuple[float, float]] - asks: List[Tuple[float, float]] + bids: list[tuple[float, float]] + asks: list[tuple[float, float]] timestamp: Optional[int] datetime: Optional[str] nonce: Optional[int] @@ -81,7 +81,7 @@ class CcxtBalance(TypedDict): total: float -CcxtBalances = Dict[str, CcxtBalance] +CcxtBalances = dict[str, CcxtBalance] class CcxtPosition(TypedDict): @@ -95,4 +95,4 @@ class CcxtPosition(TypedDict): # pair, timeframe, candleType, OHLCV, drop last?, -OHLCVResponse = Tuple[str, str, CandleType, List, bool] +OHLCVResponse = tuple[str, str, CandleType, list, bool] diff --git a/freqtrade/exchange/exchange_utils.py b/freqtrade/exchange/exchange_utils.py index c150d751b..f7b53a836 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, Dict, List, Optional, Tuple +from typing import Any, Optional import ccxt from ccxt import ( @@ -39,14 +39,14 @@ def is_exchange_known_ccxt( return exchange_name in ccxt_exchanges(ccxt_module) -def ccxt_exchanges(ccxt_module: Optional[CcxtModuleType] = None) -> List[str]: +def ccxt_exchanges(ccxt_module: Optional[CcxtModuleType] = 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: Optional[CcxtModuleType] = None) -> list[str]: """ Return exchanges available to the bot, i.e. non-bad exchanges in the ccxt list """ @@ -54,7 +54,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, Optional[ccxt.Exchange]]: """ returns: can_use, reason, exchange_object with Reason including both missing and missing_opt @@ -91,7 +91,7 @@ def validate_exchange(exchange: str) -> Tuple[bool, str, Optional[ccxt.Exchange] def _build_exchange_list_entry( - exchange_name: str, exchangeClasses: Dict[str, Any] + exchange_name: str, exchangeClasses: dict[str, Any] ) -> ValidExchangesType: valid, comment, ex_mod = validate_exchange(exchange_name) result: ValidExchangesType = { @@ -121,7 +121,7 @@ def _build_exchange_list_entry( return result -def list_available_exchanges(all_exchanges: bool) -> List[ValidExchangesType]: +def list_available_exchanges(all_exchanges: bool) -> list[ValidExchangesType]: """ :return: List of tuples with exchangename, valid, reason. """ @@ -130,7 +130,7 @@ def list_available_exchanges(all_exchanges: bool) -> List[ValidExchangesType]: subclassed = {e["name"].lower(): e for e in ExchangeResolver.search_all_objects({}, False)} - exchanges_valid: List[ValidExchangesType] = [ + exchanges_valid: list[ValidExchangesType] = [ _build_exchange_list_entry(e, subclassed) for e in exchanges ] @@ -155,7 +155,7 @@ def date_minus_candles( return new_date -def market_is_active(market: Dict) -> bool: +def market_is_active(market: dict) -> bool: """ Return True if the market is active. """ diff --git a/freqtrade/exchange/exchange_ws.py b/freqtrade/exchange/exchange_ws.py index 5851cdea6..bddd2ca86 100644 --- a/freqtrade/exchange/exchange_ws.py +++ b/freqtrade/exchange/exchange_ws.py @@ -4,7 +4,6 @@ import time from copy import deepcopy from functools import partial from threading import Thread -from typing import Dict, Set import ccxt @@ -22,12 +21,12 @@ class ExchangeWS: def __init__(self, config: Config, ccxt_object: ccxt.Exchange) -> None: self.config = config self.ccxt_object = ccxt_object - self._background_tasks: Set[asyncio.Task] = set() + self._background_tasks: set[asyncio.Task] = set() - self._klines_watching: Set[PairWithTimeframe] = set() - self._klines_scheduled: Set[PairWithTimeframe] = set() - self.klines_last_refresh: Dict[PairWithTimeframe, float] = {} - self.klines_last_request: Dict[PairWithTimeframe, float] = {} + self._klines_watching: set[PairWithTimeframe] = set() + self._klines_scheduled: set[PairWithTimeframe] = set() + self.klines_last_refresh: dict[PairWithTimeframe, float] = {} + self.klines_last_request: dict[PairWithTimeframe, float] = {} self._thread = Thread(name="ccxt_ws", target=self._start_forever) self._thread.start() self.__cleanup_called = False diff --git a/freqtrade/exchange/gate.py b/freqtrade/exchange/gate.py index 4096a851a..70f877210 100644 --- a/freqtrade/exchange/gate.py +++ b/freqtrade/exchange/gate.py @@ -2,7 +2,7 @@ import logging from datetime import datetime -from typing import Any, Dict, List, Optional, Tuple +from typing import Any, Optional from freqtrade.constants import BuySell from freqtrade.enums import MarginMode, PriceType, TradingMode @@ -46,7 +46,7 @@ class Gate(Exchange): }, } - _supported_trading_mode_margin_pairs: List[Tuple[TradingMode, MarginMode]] = [ + _supported_trading_mode_margin_pairs: list[tuple[TradingMode, MarginMode]] = [ # TradingMode.SPOT always supported and not required in this list # (TradingMode.MARGIN, MarginMode.CROSS), # (TradingMode.FUTURES, MarginMode.CROSS), @@ -60,7 +60,7 @@ class Gate(Exchange): leverage: float, reduceOnly: bool, time_in_force: str = "GTC", - ) -> Dict: + ) -> dict: params = super()._get_params( side=side, ordertype=ordertype, @@ -74,8 +74,8 @@ class Gate(Exchange): return params def get_trades_for_order( - self, order_id: str, pair: str, since: datetime, params: Optional[Dict] = None - ) -> List: + self, order_id: str, pair: str, since: datetime, params: Optional[dict] = None + ) -> list: trades = super().get_trades_for_order(order_id, pair, since, params) if self.trading_mode == TradingMode.FUTURES: @@ -99,10 +99,10 @@ class Gate(Exchange): } return trades - def get_order_id_conditional(self, order: Dict[str, Any]) -> str: + def get_order_id_conditional(self, order: dict[str, Any]) -> 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: Optional[dict] = None) -> dict: 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. @@ -120,6 +120,6 @@ class Gate(Exchange): return order def cancel_stoploss_order( - self, order_id: str, pair: str, params: Optional[Dict] = None - ) -> Dict: + self, order_id: str, pair: str, params: Optional[dict] = None + ) -> dict: return self.cancel_order(order_id=order_id, pair=pair, params={"stop": True}) diff --git a/freqtrade/exchange/htx.py b/freqtrade/exchange/htx.py index 9bd931f51..ba158d4c5 100644 --- a/freqtrade/exchange/htx.py +++ b/freqtrade/exchange/htx.py @@ -1,7 +1,6 @@ """HTX exchange subclass""" import logging -from typing import Dict from freqtrade.constants import BuySell from freqtrade.exchange import Exchange @@ -32,7 +31,7 @@ class Htx(Exchange): "trades_has_history": False, # Endpoint doesn't have a "since" parameter } - def _get_stop_params(self, side: BuySell, ordertype: str, stop_price: float) -> Dict: + def _get_stop_params(self, side: BuySell, ordertype: str, stop_price: float) -> dict: params = self._params.copy() params.update( { diff --git a/freqtrade/exchange/hyperliquid.py b/freqtrade/exchange/hyperliquid.py index 69905c416..144edbf3a 100644 --- a/freqtrade/exchange/hyperliquid.py +++ b/freqtrade/exchange/hyperliquid.py @@ -1,9 +1,6 @@ """Hyperliquid exchange subclass""" import logging -from typing import Dict - -from ccxt import SIGNIFICANT_DIGITS from freqtrade.enums import TradingMode from freqtrade.exchange import Exchange @@ -28,7 +25,7 @@ class Hyperliquid(Exchange): } @property - def _ccxt_config(self) -> Dict: + def _ccxt_config(self) -> dict: # Parameters to add directly to ccxt sync/async initialization. # ccxt defaults to swap mode. config = {} @@ -36,10 +33,3 @@ class Hyperliquid(Exchange): config.update({"options": {"defaultType": "spot"}}) config.update(super()._ccxt_config) return config - - @property - def precision_mode_price(self) -> int: - """ - Override the default precision mode for price. - """ - return SIGNIFICANT_DIGITS diff --git a/freqtrade/exchange/kraken.py b/freqtrade/exchange/kraken.py index 9df9836b0..7dea0e435 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, Dict, List, Optional, Tuple +from typing import Any, Optional import ccxt from pandas import DataFrame @@ -19,7 +19,7 @@ logger = logging.getLogger(__name__) class Kraken(Exchange): - _params: Dict = {"trading_agreement": "agree"} + _params: dict = {"trading_agreement": "agree"} _ft_has: FtHas = { "stoploss_on_exchange": True, "stop_price_param": "stopLossPrice", @@ -35,13 +35,13 @@ class Kraken(Exchange): "mark_ohlcv_timeframe": "4h", } - _supported_trading_mode_margin_pairs: List[Tuple[TradingMode, MarginMode]] = [ + _supported_trading_mode_margin_pairs: list[tuple[TradingMode, MarginMode]] = [ # TradingMode.SPOT always supported and not required in this list # (TradingMode.MARGIN, MarginMode.CROSS), # (TradingMode.FUTURES, MarginMode.CROSS) ] - def market_is_tradable(self, market: Dict[str, Any]) -> bool: + def market_is_tradable(self, market: dict[str, Any]) -> bool: """ Check if the market symbol is tradable by Freqtrade. Default checks + check if pair is darkpool pair. @@ -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: Optional[list[str]] = 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"]])) @@ -115,7 +115,7 @@ class Kraken(Exchange): leverage: float, reduceOnly: bool, time_in_force: str = "GTC", - ) -> Dict: + ) -> dict: params = super()._get_params( side=side, ordertype=ordertype, @@ -165,7 +165,7 @@ class Kraken(Exchange): return fees if is_short else -fees - def _get_trade_pagination_next_value(self, trades: List[Dict]): + def _get_trade_pagination_next_value(self, trades: list[dict]): """ Extract pagination id for the next "from_id" value Applies only to fetch_trade_history by id. diff --git a/freqtrade/exchange/kucoin.py b/freqtrade/exchange/kucoin.py index bbf120d40..fc4433f0b 100644 --- a/freqtrade/exchange/kucoin.py +++ b/freqtrade/exchange/kucoin.py @@ -1,7 +1,6 @@ """Kucoin exchange subclass.""" import logging -from typing import Dict from freqtrade.constants import BuySell from freqtrade.exchange import Exchange @@ -32,7 +31,7 @@ class Kucoin(Exchange): "ohlcv_candle_limit": 1500, } - def _get_stop_params(self, side: BuySell, ordertype: str, stop_price: float) -> Dict: + def _get_stop_params(self, side: BuySell, ordertype: str, stop_price: float) -> dict: params = self._params.copy() params.update({"stopPrice": stop_price, "stop": "loss"}) return params @@ -48,7 +47,7 @@ class Kucoin(Exchange): leverage: float, reduceOnly: bool = False, time_in_force: str = "GTC", - ) -> Dict: + ) -> dict: res = super().create_order( pair=pair, ordertype=ordertype, diff --git a/freqtrade/exchange/lbank.py b/freqtrade/exchange/lbank.py new file mode 100644 index 000000000..02156d2c2 --- /dev/null +++ b/freqtrade/exchange/lbank.py @@ -0,0 +1,21 @@ +"""Lbank exchange subclass""" + +import logging + +from freqtrade.exchange import Exchange +from freqtrade.exchange.exchange_types import FtHas + + +logger = logging.getLogger(__name__) + + +class Lbank(Exchange): + """ + Lbank exchange class. Contains adjustments needed for Freqtrade to work + with this exchange. + """ + + _ft_has: FtHas = { + "ohlcv_candle_limit": 1998, # lower than the allowed 2000 to avoid current_candle issue + "trades_has_history": False, + } diff --git a/freqtrade/exchange/okx.py b/freqtrade/exchange/okx.py index a0fbb6729..fbbf21757 100644 --- a/freqtrade/exchange/okx.py +++ b/freqtrade/exchange/okx.py @@ -1,6 +1,6 @@ import logging from datetime import timedelta -from typing import Any, Dict, List, Optional, Tuple +from typing import Any, Optional import ccxt @@ -48,7 +48,7 @@ class Okx(Exchange): "ws_enabled": True, } - _supported_trading_mode_margin_pairs: List[Tuple[TradingMode, MarginMode]] = [ + _supported_trading_mode_margin_pairs: list[tuple[TradingMode, MarginMode]] = [ # TradingMode.SPOT always supported and not required in this list # (TradingMode.MARGIN, MarginMode.CROSS), # (TradingMode.FUTURES, MarginMode.CROSS), @@ -57,7 +57,7 @@ class Okx(Exchange): net_only = True - _ccxt_params: Dict = {"options": {"brokerId": "ffb5405ad327SUDE"}} + _ccxt_params: dict = {"options": {"brokerId": "ffb5405ad327SUDE"}} def ohlcv_candle_limit( self, timeframe: str, candle_type: CandleType, since_ms: Optional[int] = None @@ -119,7 +119,7 @@ class Okx(Exchange): leverage: float, reduceOnly: bool, time_in_force: str = "GTC", - ) -> Dict: + ) -> dict: params = super()._get_params( side=side, ordertype=ordertype, @@ -184,14 +184,14 @@ class Okx(Exchange): pair_tiers = self._leverage_tiers[pair] return pair_tiers[-1]["maxNotional"] / leverage - def _get_stop_params(self, side: BuySell, ordertype: str, stop_price: float) -> Dict: + def _get_stop_params(self, side: BuySell, ordertype: str, stop_price: float) -> dict: params = super()._get_stop_params(side, ordertype, stop_price) if self.trading_mode == TradingMode.FUTURES and self.margin_mode: params["tdMode"] = self.margin_mode.value 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: dict) -> dict: if ( order.get("status", "open") == "closed" and (real_order_id := order.get("info", {}).get("ordId")) is not None @@ -209,7 +209,7 @@ 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: Optional[dict] = None) -> dict: if self._config["dry_run"]: return self.fetch_dry_run_order(order_id) @@ -231,7 +231,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) -> dict: params2 = {"stop": True, "ordType": "conditional"} for method in ( self._api.fetch_open_orders, @@ -256,14 +256,14 @@ 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: dict[str, Any]) -> 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: + self, order_id: str, pair: str, params: Optional[dict] = None + ) -> dict: params1 = {"stop": True} # 'ordType': 'conditional' # @@ -273,7 +273,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[dict]: 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 5ddfdeb68..021a2fc58 100644 --- a/freqtrade/freqai/RL/BaseEnvironment.py +++ b/freqtrade/freqai/RL/BaseEnvironment.py @@ -2,7 +2,7 @@ import logging import random from abc import abstractmethod from enum import Enum -from typing import List, Optional, Type, Union +from typing import Optional, Union import gymnasium as gym import numpy as np @@ -89,7 +89,7 @@ class BaseEnvironment(gym.Env): self.fee = fee # set here to default 5Ac, but all children envs can override this - self.actions: Type[Enum] = BaseActions + self.actions: type[Enum] = BaseActions self.tensorboard_metrics: dict = {} self.can_short: bool = can_short self.live: bool = live @@ -163,7 +163,7 @@ class BaseEnvironment(gym.Env): Unique to the environment action count. Must be inherited. """ - def action_masks(self) -> List[bool]: + def action_masks(self) -> list[bool]: return [self._is_valid(action.value) for action in self.actions] def seed(self, seed: int = 1): @@ -375,7 +375,7 @@ class BaseEnvironment(gym.Env): def current_price(self) -> float: return self.prices.iloc[self._current_tick].open - def get_actions(self) -> Type[Enum]: + def get_actions(self) -> type[Enum]: """ Used by SubprocVecEnv to get actions from initialized env for tensorboard callback diff --git a/freqtrade/freqai/RL/BaseReinforcementLearningModel.py b/freqtrade/freqai/RL/BaseReinforcementLearningModel.py index 225ed3d50..5da88adb6 100644 --- a/freqtrade/freqai/RL/BaseReinforcementLearningModel.py +++ b/freqtrade/freqai/RL/BaseReinforcementLearningModel.py @@ -4,7 +4,7 @@ import logging from abc import abstractmethod from datetime import datetime, timezone from pathlib import Path -from typing import Any, Callable, Dict, Optional, Tuple, Type, Union +from typing import Any, Callable, Optional, Union import gymnasium as gym import numpy as np @@ -114,7 +114,7 @@ class BaseReinforcementLearningModel(IFreqaiModel): training_filter=True, ) - dd: Dict[str, Any] = dk.make_train_test_datasets(features_filtered, labels_filtered) + dd: dict[str, Any] = dk.make_train_test_datasets(features_filtered, labels_filtered) self.df_raw = copy.deepcopy(dd["train_features"]) dk.fit_labels() # FIXME useless for now, but just satiating append methods @@ -151,7 +151,7 @@ class BaseReinforcementLearningModel(IFreqaiModel): def set_train_and_eval_environments( self, - data_dictionary: Dict[str, DataFrame], + data_dictionary: dict[str, DataFrame], prices_train: DataFrame, prices_test: DataFrame, dk: FreqaiDataKitchen, @@ -183,7 +183,7 @@ class BaseReinforcementLearningModel(IFreqaiModel): actions = self.train_env.get_actions() self.tensorboard_callback = TensorboardCallback(verbose=1, actions=actions) - def pack_env_dict(self, pair: str) -> Dict[str, Any]: + def pack_env_dict(self, pair: str) -> dict[str, Any]: """ Create dictionary of environment arguments """ @@ -204,7 +204,7 @@ class BaseReinforcementLearningModel(IFreqaiModel): return env_info @abstractmethod - def fit(self, data_dictionary: Dict[str, Any], dk: FreqaiDataKitchen, **kwargs): + def fit(self, data_dictionary: dict[str, Any], dk: FreqaiDataKitchen, **kwargs): """ Agent customizations and abstract Reinforcement Learning customizations go in here. Abstract method, so this function must be overridden by @@ -212,7 +212,7 @@ class BaseReinforcementLearningModel(IFreqaiModel): """ return - def get_state_info(self, pair: str) -> Tuple[float, float, int]: + def get_state_info(self, pair: str) -> tuple[float, float, int]: """ State info during dry/live (not backtesting) which is fed back into the model. @@ -250,7 +250,7 @@ class BaseReinforcementLearningModel(IFreqaiModel): def predict( self, unfiltered_df: DataFrame, dk: FreqaiDataKitchen, **kwargs - ) -> Tuple[DataFrame, npt.NDArray[np.int_]]: + ) -> tuple[DataFrame, npt.NDArray[np.int_]]: """ Filter the prediction features data and predict with it. :param unfiltered_dataframe: Full dataframe for the current backtest period. @@ -303,7 +303,7 @@ class BaseReinforcementLearningModel(IFreqaiModel): def build_ohlc_price_dataframes( self, data_dictionary: dict, pair: str, dk: FreqaiDataKitchen - ) -> Tuple[DataFrame, DataFrame]: + ) -> tuple[DataFrame, DataFrame]: """ Builds the train prices and test prices for the environment. """ @@ -482,13 +482,13 @@ class BaseReinforcementLearningModel(IFreqaiModel): def make_env( - MyRLEnv: Type[BaseEnvironment], + MyRLEnv: type[BaseEnvironment], env_id: str, rank: int, seed: int, train_df: DataFrame, price: DataFrame, - env_info: Dict[str, Any] = {}, + env_info: dict[str, Any] = {}, ) -> Callable: """ Utility function for multiprocessed env. diff --git a/freqtrade/freqai/base_models/BaseClassifierModel.py b/freqtrade/freqai/base_models/BaseClassifierModel.py index dfe5ae3b0..fe3254792 100644 --- a/freqtrade/freqai/base_models/BaseClassifierModel.py +++ b/freqtrade/freqai/base_models/BaseClassifierModel.py @@ -1,6 +1,6 @@ import logging from time import time -from typing import Any, Tuple +from typing import Any import numpy as np import numpy.typing as npt @@ -86,7 +86,7 @@ class BaseClassifierModel(IFreqaiModel): def predict( self, unfiltered_df: DataFrame, dk: FreqaiDataKitchen, **kwargs - ) -> Tuple[DataFrame, npt.NDArray[np.int_]]: + ) -> tuple[DataFrame, npt.NDArray[np.int_]]: """ Filter the prediction features data and predict with it. :param unfiltered_df: Full dataframe for the current backtest period. diff --git a/freqtrade/freqai/base_models/BasePyTorchClassifier.py b/freqtrade/freqai/base_models/BasePyTorchClassifier.py index 86eadb7bd..b1db396ae 100644 --- a/freqtrade/freqai/base_models/BasePyTorchClassifier.py +++ b/freqtrade/freqai/base_models/BasePyTorchClassifier.py @@ -1,6 +1,6 @@ import logging from time import time -from typing import Any, Dict, List, Tuple +from typing import Any import numpy as np import numpy.typing as npt @@ -39,12 +39,12 @@ class BasePyTorchClassifier(BasePyTorchModel): def __init__(self, **kwargs): super().__init__(**kwargs) - self.class_name_to_index = None - self.index_to_class_name = None + self.class_name_to_index = {} + self.index_to_class_name = {} def predict( self, unfiltered_df: DataFrame, dk: FreqaiDataKitchen, **kwargs - ) -> Tuple[DataFrame, npt.NDArray[np.int_]]: + ) -> tuple[DataFrame, npt.NDArray[np.int_]]: """ Filter the prediction features data and predict with it. :param dk: dk: The datakitchen object @@ -100,9 +100,9 @@ class BasePyTorchClassifier(BasePyTorchModel): def encode_class_names( self, - data_dictionary: Dict[str, pd.DataFrame], + data_dictionary: dict[str, pd.DataFrame], dk: FreqaiDataKitchen, - class_names: List[str], + class_names: list[str], ): """ encode class name, str -> int @@ -119,7 +119,7 @@ class BasePyTorchClassifier(BasePyTorchModel): ) @staticmethod - def assert_valid_class_names(target_column: pd.Series, class_names: List[str]): + def assert_valid_class_names(target_column: pd.Series, class_names: list[str]): non_defined_labels = set(target_column) - set(class_names) if len(non_defined_labels) != 0: raise OperationalException( @@ -127,7 +127,7 @@ class BasePyTorchClassifier(BasePyTorchModel): f"expecting labels: {class_names}", ) - def decode_class_names(self, class_ints: torch.Tensor) -> List[str]: + def decode_class_names(self, class_ints: torch.Tensor) -> list[str]: """ decode class name, int -> str """ @@ -141,14 +141,14 @@ class BasePyTorchClassifier(BasePyTorchModel): def convert_label_column_to_int( self, - data_dictionary: Dict[str, pd.DataFrame], + data_dictionary: dict[str, pd.DataFrame], dk: FreqaiDataKitchen, - class_names: List[str], + class_names: list[str], ): self.init_class_names_to_index_mapping(class_names) self.encode_class_names(data_dictionary, dk, class_names) - def get_class_names(self) -> List[str]: + def get_class_names(self) -> list[str]: if not self.class_names: raise ValueError( "self.class_names is empty, " diff --git a/freqtrade/freqai/base_models/BasePyTorchModel.py b/freqtrade/freqai/base_models/BasePyTorchModel.py index 50b023021..51de5fc00 100644 --- a/freqtrade/freqai/base_models/BasePyTorchModel.py +++ b/freqtrade/freqai/base_models/BasePyTorchModel.py @@ -20,7 +20,11 @@ class BasePyTorchModel(IFreqaiModel, ABC): def __init__(self, **kwargs): super().__init__(config=kwargs["config"]) self.dd.model_type = "pytorch" - self.device = "cuda" if torch.cuda.is_available() else "cpu" + self.device = ( + "mps" + if torch.backends.mps.is_available() and torch.backends.mps.is_built() + else ("cuda" if torch.cuda.is_available() else "cpu") + ) test_size = self.freqai_info.get("data_split_parameters", {}).get("test_size") self.splits = ["train", "test"] if test_size != 0 else ["train"] self.window_size = self.freqai_info.get("conv_width", 1) diff --git a/freqtrade/freqai/base_models/BasePyTorchRegressor.py b/freqtrade/freqai/base_models/BasePyTorchRegressor.py index 5f53e7d07..6e1db22c7 100644 --- a/freqtrade/freqai/base_models/BasePyTorchRegressor.py +++ b/freqtrade/freqai/base_models/BasePyTorchRegressor.py @@ -1,6 +1,6 @@ import logging from time import time -from typing import Any, Tuple +from typing import Any import numpy as np import numpy.typing as npt @@ -24,7 +24,7 @@ class BasePyTorchRegressor(BasePyTorchModel): def predict( self, unfiltered_df: DataFrame, dk: FreqaiDataKitchen, **kwargs - ) -> Tuple[DataFrame, npt.NDArray[np.int_]]: + ) -> tuple[DataFrame, npt.NDArray[np.int_]]: """ Filter the prediction features data and predict with it. :param unfiltered_df: Full dataframe for the current backtest period. diff --git a/freqtrade/freqai/base_models/BaseRegressionModel.py b/freqtrade/freqai/base_models/BaseRegressionModel.py index bbadac0f0..495ee5e39 100644 --- a/freqtrade/freqai/base_models/BaseRegressionModel.py +++ b/freqtrade/freqai/base_models/BaseRegressionModel.py @@ -1,6 +1,6 @@ import logging from time import time -from typing import Any, Tuple +from typing import Any import numpy as np import numpy.typing as npt @@ -88,7 +88,7 @@ class BaseRegressionModel(IFreqaiModel): def predict( self, unfiltered_df: DataFrame, dk: FreqaiDataKitchen, **kwargs - ) -> Tuple[DataFrame, npt.NDArray[np.int_]]: + ) -> tuple[DataFrame, npt.NDArray[np.int_]]: """ Filter the prediction features data and predict with it. :param unfiltered_df: Full dataframe for the current backtest period. diff --git a/freqtrade/freqai/data_drawer.py b/freqtrade/freqai/data_drawer.py index 124ed9e26..4ab13440a 100644 --- a/freqtrade/freqai/data_drawer.py +++ b/freqtrade/freqai/data_drawer.py @@ -7,7 +7,7 @@ import threading import warnings from datetime import datetime, timedelta, timezone from pathlib import Path -from typing import Any, Dict, Tuple, TypedDict +from typing import Any, TypedDict import numpy as np import pandas as pd @@ -69,14 +69,14 @@ class FreqaiDataDrawer: self.config = config self.freqai_info = config.get("freqai", {}) # dictionary holding all pair metadata necessary to load in from disk - self.pair_dict: Dict[str, pair_info] = {} + self.pair_dict: dict[str, pair_info] = {} # dictionary holding all actively inferenced models in memory given a model filename - self.model_dictionary: Dict[str, Any] = {} + self.model_dictionary: dict[str, Any] = {} # all additional metadata that we want to keep in ram - self.meta_data_dictionary: Dict[str, Dict[str, Any]] = {} - self.model_return_values: Dict[str, DataFrame] = {} - self.historic_data: Dict[str, Dict[str, DataFrame]] = {} - self.historic_predictions: Dict[str, DataFrame] = {} + self.meta_data_dictionary: dict[str, dict[str, Any]] = {} + self.model_return_values: dict[str, DataFrame] = {} + self.historic_data: dict[str, dict[str, DataFrame]] = {} + self.historic_predictions: dict[str, DataFrame] = {} self.full_path = full_path self.historic_predictions_path = Path(self.full_path / "historic_predictions.pkl") self.historic_predictions_bkp_path = Path( @@ -87,14 +87,14 @@ class FreqaiDataDrawer: self.metric_tracker_path = Path(self.full_path / "metric_tracker.json") self.load_drawer_from_disk() self.load_historic_predictions_from_disk() - self.metric_tracker: Dict[str, Dict[str, Dict[str, list]]] = {} + self.metric_tracker: dict[str, dict[str, dict[str, list]]] = {} self.load_metric_tracker_from_disk() - self.training_queue: Dict[str, int] = {} + self.training_queue: dict[str, int] = {} self.history_lock = threading.Lock() self.save_lock = threading.Lock() self.pair_dict_lock = threading.Lock() self.metric_tracker_lock = threading.Lock() - self.old_DBSCAN_eps: Dict[str, float] = {} + self.old_DBSCAN_eps: dict[str, float] = {} self.empty_pair_dict: pair_info = { "model_filename": "", "trained_timestamp": 0, @@ -228,7 +228,7 @@ class FreqaiDataDrawer: self.pair_dict, fp, default=self.np_encoder, number_mode=rapidjson.NM_NATIVE ) - def save_global_metadata_to_disk(self, metadata: Dict[str, Any]): + def save_global_metadata_to_disk(self, metadata: dict[str, Any]): """ Save global metadata json to disk """ @@ -242,7 +242,7 @@ class FreqaiDataDrawer: if isinstance(obj, np.generic): return obj.item() - def get_pair_dict_info(self, pair: str) -> Tuple[str, int]: + def get_pair_dict_info(self, pair: str) -> tuple[str, int]: """ Locate and load existing model metadata from persistent storage. If not located, create a new one and append the current pair to it and prepare it for its first @@ -446,7 +446,7 @@ class FreqaiDataDrawer: pattern = re.compile(r"sub-train-(\w+)_(\d{10})") - delete_dict: Dict[str, Any] = {} + delete_dict: dict[str, Any] = {} for directory in model_folders: result = pattern.match(str(directory.name)) @@ -704,7 +704,7 @@ class FreqaiDataDrawer: def get_base_and_corr_dataframes( self, timerange: TimeRange, pair: str, dk: FreqaiDataKitchen - ) -> Tuple[Dict[Any, Any], Dict[Any, Any]]: + ) -> tuple[dict[Any, Any], dict[Any, Any]]: """ Searches through our historic_data in memory and returns the dataframes relevant to the present pair. @@ -713,8 +713,8 @@ class FreqaiDataDrawer: :param metadata: dict = strategy furnished pair metadata """ with self.history_lock: - corr_dataframes: Dict[Any, Any] = {} - base_dataframes: Dict[Any, Any] = {} + corr_dataframes: dict[Any, Any] = {} + base_dataframes: dict[Any, Any] = {} historic_data = self.historic_data pairs = self.freqai_info["feature_parameters"].get("include_corr_pairlist", []) diff --git a/freqtrade/freqai/data_kitchen.py b/freqtrade/freqai/data_kitchen.py index d43f569d8..4663c41b0 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, Dict, List, Optional, Tuple +from typing import Any, Optional import numpy as np import numpy.typing as npt @@ -64,15 +64,15 @@ class FreqaiDataKitchen: live: bool = False, pair: str = "", ): - self.data: Dict[str, Any] = {} - self.data_dictionary: Dict[str, DataFrame] = {} + self.data: dict[str, Any] = {} + self.data_dictionary: dict[str, DataFrame] = {} self.config = config - self.freqai_config: Dict[str, Any] = config["freqai"] + self.freqai_config: dict[str, Any] = config["freqai"] self.full_df: DataFrame = DataFrame() self.append_df: DataFrame = DataFrame() self.data_path = Path() - self.label_list: List = [] - self.training_features_list: List = [] + self.label_list: list = [] + self.training_features_list: list = [] self.model_filename: str = "" self.backtesting_results_path = Path() self.backtest_predictions_folder: str = "backtesting_predictions" @@ -104,9 +104,9 @@ class FreqaiDataKitchen: else: self.thread_count = self.freqai_config["data_kitchen_thread_count"] self.train_dates: DataFrame = pd.DataFrame() - self.unique_classes: Dict[str, list] = {} + self.unique_classes: dict[str, list] = {} self.unique_class_list: list = [] - self.backtest_live_models_data: Dict[str, Any] = {} + self.backtest_live_models_data: dict[str, Any] = {} def set_paths( self, @@ -127,7 +127,7 @@ class FreqaiDataKitchen: def make_train_test_datasets( self, filtered_dataframe: DataFrame, labels: DataFrame - ) -> Dict[Any, Any]: + ) -> dict[Any, Any]: """ Given the dataframe for the full history for training, split the data into training and test data according to user specified parameters in configuration @@ -213,10 +213,10 @@ class FreqaiDataKitchen: def filter_features( self, unfiltered_df: DataFrame, - training_feature_list: List, - label_list: List = list(), + training_feature_list: list, + label_list: list = list(), training_filter: bool = True, - ) -> Tuple[DataFrame, DataFrame]: + ) -> tuple[DataFrame, DataFrame]: """ Filter the unfiltered dataframe to extract the user requested features/labels and properly remove all NaNs. Any row with a NaN is removed from training dataset or replaced with @@ -306,7 +306,7 @@ class FreqaiDataKitchen: test_labels: DataFrame, train_weights: Any, test_weights: Any, - ) -> Dict: + ) -> dict: self.data_dictionary = { "train_features": train_df, "test_features": test_df, @@ -321,7 +321,7 @@ class FreqaiDataKitchen: def split_timerange( self, tr: str, train_split: int = 28, bt_split: float = 7 - ) -> Tuple[list, list]: + ) -> tuple[list, list]: """ Function which takes a single time range (tr) and splits it into sub timeranges to train and backtest on based on user input @@ -535,7 +535,7 @@ class FreqaiDataKitchen: def check_if_new_training_required( self, trained_timestamp: int - ) -> Tuple[bool, TimeRange, TimeRange]: + ) -> tuple[bool, TimeRange, TimeRange]: time = datetime.now(tz=timezone.utc).timestamp() trained_timerange = TimeRange() data_load_timerange = TimeRange() @@ -603,7 +603,7 @@ class FreqaiDataKitchen: def extract_corr_pair_columns_from_populated_indicators( self, dataframe: DataFrame - ) -> Dict[str, DataFrame]: + ) -> dict[str, DataFrame]: """ Find the columns of the dataframe corresponding to the corr_pairlist, save them in a dictionary to be reused and attached to other pairs. @@ -612,7 +612,7 @@ class FreqaiDataKitchen: :return: corr_dataframes, dictionary of dataframes to be attached to other pairs in same candle. """ - corr_dataframes: Dict[str, DataFrame] = {} + corr_dataframes: dict[str, DataFrame] = {} pairs = self.freqai_config["feature_parameters"].get("include_corr_pairlist", []) for pair in pairs: @@ -628,7 +628,7 @@ class FreqaiDataKitchen: return corr_dataframes def attach_corr_pair_columns( - self, dataframe: DataFrame, corr_dataframes: Dict[str, DataFrame], current_pair: str + self, dataframe: DataFrame, corr_dataframes: dict[str, DataFrame], current_pair: str ) -> DataFrame: """ Attach the existing corr_pair dataframes to the current pair dataframe before training @@ -731,7 +731,7 @@ class FreqaiDataKitchen: :param is_corr_pairs: bool = whether the pair is a corr pair or not :return: dataframe = populated dataframe """ - tfs: List[str] = self.freqai_config["feature_parameters"].get("include_timeframes") + tfs: list[str] = self.freqai_config["feature_parameters"].get("include_timeframes") for tf in tfs: metadata = {"pair": pair, "tf": tf} @@ -810,8 +810,8 @@ class FreqaiDataKitchen: f"{DOCS_LINK}/freqai-feature-engineering/" ) - tfs: List[str] = self.freqai_config["feature_parameters"].get("include_timeframes") - pairs: List[str] = self.freqai_config["feature_parameters"].get("include_corr_pairlist", []) + tfs: list[str] = self.freqai_config["feature_parameters"].get("include_timeframes") + pairs: list[str] = self.freqai_config["feature_parameters"].get("include_corr_pairlist", []) for tf in tfs: if tf not in base_dataframes: @@ -828,7 +828,7 @@ class FreqaiDataKitchen: else: dataframe = base_dataframes[self.config["timeframe"]].copy() - corr_pairs: List[str] = self.freqai_config["feature_parameters"].get( + corr_pairs: list[str] = self.freqai_config["feature_parameters"].get( "include_corr_pairlist", [] ) dataframe = self.populate_features( @@ -953,7 +953,7 @@ class FreqaiDataKitchen: Returns default FreqAI model path :param config: Configuration dictionary """ - freqai_config: Dict[str, Any] = config["freqai"] + freqai_config: dict[str, Any] = config["freqai"] return Path(config["user_data_dir"] / "models" / str(freqai_config.get("identifier"))) def remove_special_chars_from_feature_names(self, dataframe: pd.DataFrame) -> pd.DataFrame: @@ -992,7 +992,7 @@ class FreqaiDataKitchen: return timerange # deprecated functions - def normalize_data(self, data_dictionary: Dict) -> Dict[Any, Any]: + def normalize_data(self, data_dictionary: dict) -> dict[Any, Any]: """ Deprecation warning, migration assistance """ diff --git a/freqtrade/freqai/freqai_interface.py b/freqtrade/freqai/freqai_interface.py index c6a358c57..b0c376272 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, Dict, List, Literal, Optional, Tuple +from typing import Any, Literal, Optional import datasieve.transforms as ds import numpy as np @@ -59,11 +59,11 @@ class IFreqaiModel(ABC): def __init__(self, config: Config) -> None: self.config = config self.assert_config(self.config) - self.freqai_info: Dict[str, Any] = config["freqai"] - self.data_split_parameters: Dict[str, Any] = config.get("freqai", {}).get( + self.freqai_info: dict[str, Any] = config["freqai"] + self.data_split_parameters: dict[str, Any] = config.get("freqai", {}).get( "data_split_parameters", {} ) - self.model_training_parameters: Dict[str, Any] = config.get("freqai", {}).get( + self.model_training_parameters: dict[str, Any] = config.get("freqai", {}).get( "model_training_parameters", {} ) self.identifier: str = self.freqai_info.get("identifier", "no_id_provided") @@ -80,14 +80,14 @@ class IFreqaiModel(ABC): self.dd.current_candle = self.current_candle self.scanning = False self.ft_params = self.freqai_info["feature_parameters"] - self.corr_pairlist: List[str] = self.ft_params.get("include_corr_pairlist", []) + self.corr_pairlist: list[str] = self.ft_params.get("include_corr_pairlist", []) self.keras: bool = self.freqai_info.get("keras", False) if self.keras and self.ft_params.get("DI_threshold", 0): self.ft_params["DI_threshold"] = 0 logger.warning("DI threshold is not configured for Keras models yet. Deactivating.") self.CONV_WIDTH = self.freqai_info.get("conv_width", 1) - self.class_names: List[str] = [] # used in classification subclasses + self.class_names: list[str] = [] # used in classification subclasses self.pair_it = 0 self.pair_it_train = 0 self.total_pairs = len(self.config.get("exchange", {}).get("pair_whitelist")) @@ -99,13 +99,13 @@ class IFreqaiModel(ABC): self.base_tf_seconds = timeframe_to_seconds(self.config["timeframe"]) self.continual_learning = self.freqai_info.get("continual_learning", False) self.plot_features = self.ft_params.get("plot_feature_importances", 0) - self.corr_dataframes: Dict[str, DataFrame] = {} + self.corr_dataframes: dict[str, DataFrame] = {} # get_corr_dataframes is controlling the caching of corr_dataframes # for improved performance. Careful with this boolean. self.get_corr_dataframes: bool = True - self._threads: List[threading.Thread] = [] + self._threads: list[threading.Thread] = [] self._stop_event = threading.Event() - self.metadata: Dict[str, Any] = self.dd.load_global_metadata_from_disk() + self.metadata: dict[str, Any] = self.dd.load_global_metadata_from_disk() self.data_provider: Optional[DataProvider] = None self.max_system_threads = max(int(psutil.cpu_count() * 2 - 2), 1) self.can_short = True # overridden in start() with strategy.can_short @@ -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: """ @@ -901,7 +909,7 @@ class IFreqaiModel(ABC): return - def update_metadata(self, metadata: Dict[str, Any]): + def update_metadata(self, metadata: dict[str, Any]): """ Update global metadata and save the updated json file :param metadata: new global metadata dict @@ -954,7 +962,7 @@ class IFreqaiModel(ABC): """ @abstractmethod - def fit(self, data_dictionary: Dict[str, Any], dk: FreqaiDataKitchen, **kwargs) -> Any: + def fit(self, data_dictionary: dict[str, Any], dk: FreqaiDataKitchen, **kwargs) -> Any: """ Most regressors use the same function names and arguments e.g. user can drop in LGBMRegressor in place of CatBoostRegressor and all data @@ -968,7 +976,7 @@ class IFreqaiModel(ABC): @abstractmethod def predict( self, unfiltered_df: DataFrame, dk: FreqaiDataKitchen, **kwargs - ) -> Tuple[DataFrame, NDArray[np.int_]]: + ) -> tuple[DataFrame, NDArray[np.int_]]: """ Filter the prediction features data and predict with it. :param unfiltered_df: Full dataframe for the current backtest period. diff --git a/freqtrade/freqai/prediction_models/CatboostClassifier.py b/freqtrade/freqai/prediction_models/CatboostClassifier.py index 176139770..632dc781e 100644 --- a/freqtrade/freqai/prediction_models/CatboostClassifier.py +++ b/freqtrade/freqai/prediction_models/CatboostClassifier.py @@ -1,6 +1,6 @@ import logging from pathlib import Path -from typing import Any, Dict +from typing import Any from catboost import CatBoostClassifier, Pool @@ -21,7 +21,7 @@ class CatboostClassifier(BaseClassifierModel): top level config.json file. """ - 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, diff --git a/freqtrade/freqai/prediction_models/CatboostClassifierMultiTarget.py b/freqtrade/freqai/prediction_models/CatboostClassifierMultiTarget.py index 02cb91f5a..a277bc2d7 100644 --- a/freqtrade/freqai/prediction_models/CatboostClassifierMultiTarget.py +++ b/freqtrade/freqai/prediction_models/CatboostClassifierMultiTarget.py @@ -1,6 +1,6 @@ import logging from pathlib import Path -from typing import Any, Dict +from typing import Any from catboost import CatBoostClassifier, Pool @@ -22,7 +22,7 @@ class CatboostClassifierMultiTarget(BaseClassifierModel): top level config.json file. """ - 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, diff --git a/freqtrade/freqai/prediction_models/CatboostRegressor.py b/freqtrade/freqai/prediction_models/CatboostRegressor.py index 5401a808b..fb2727b33 100644 --- a/freqtrade/freqai/prediction_models/CatboostRegressor.py +++ b/freqtrade/freqai/prediction_models/CatboostRegressor.py @@ -1,6 +1,6 @@ import logging from pathlib import Path -from typing import Any, Dict +from typing import Any from catboost import CatBoostRegressor, Pool @@ -21,7 +21,7 @@ class CatboostRegressor(BaseRegressionModel): top level config.json file. """ - 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, diff --git a/freqtrade/freqai/prediction_models/CatboostRegressorMultiTarget.py b/freqtrade/freqai/prediction_models/CatboostRegressorMultiTarget.py index c2a5344e3..ad3ddcd9b 100644 --- a/freqtrade/freqai/prediction_models/CatboostRegressorMultiTarget.py +++ b/freqtrade/freqai/prediction_models/CatboostRegressorMultiTarget.py @@ -1,6 +1,6 @@ import logging from pathlib import Path -from typing import Any, Dict +from typing import Any from catboost import CatBoostRegressor, Pool @@ -22,7 +22,7 @@ class CatboostRegressorMultiTarget(BaseRegressionModel): top level config.json file. """ - 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, diff --git a/freqtrade/freqai/prediction_models/LightGBMClassifier.py b/freqtrade/freqai/prediction_models/LightGBMClassifier.py index 1e86a39e2..e17f7417c 100644 --- a/freqtrade/freqai/prediction_models/LightGBMClassifier.py +++ b/freqtrade/freqai/prediction_models/LightGBMClassifier.py @@ -1,5 +1,5 @@ import logging -from typing import Any, Dict +from typing import Any from lightgbm import LGBMClassifier @@ -20,7 +20,7 @@ class LightGBMClassifier(BaseClassifierModel): top level config.json file. """ - 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, diff --git a/freqtrade/freqai/prediction_models/LightGBMClassifierMultiTarget.py b/freqtrade/freqai/prediction_models/LightGBMClassifierMultiTarget.py index 696deb9c9..9fb775614 100644 --- a/freqtrade/freqai/prediction_models/LightGBMClassifierMultiTarget.py +++ b/freqtrade/freqai/prediction_models/LightGBMClassifierMultiTarget.py @@ -1,5 +1,5 @@ import logging -from typing import Any, Dict +from typing import Any from lightgbm import LGBMClassifier @@ -21,7 +21,7 @@ class LightGBMClassifierMultiTarget(BaseClassifierModel): top level config.json file. """ - 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, diff --git a/freqtrade/freqai/prediction_models/LightGBMRegressor.py b/freqtrade/freqai/prediction_models/LightGBMRegressor.py index 66bd204e7..d55cd0ca2 100644 --- a/freqtrade/freqai/prediction_models/LightGBMRegressor.py +++ b/freqtrade/freqai/prediction_models/LightGBMRegressor.py @@ -1,5 +1,5 @@ import logging -from typing import Any, Dict +from typing import Any from lightgbm import LGBMRegressor @@ -20,7 +20,7 @@ class LightGBMRegressor(BaseRegressionModel): top level config.json file. """ - 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, diff --git a/freqtrade/freqai/prediction_models/LightGBMRegressorMultiTarget.py b/freqtrade/freqai/prediction_models/LightGBMRegressorMultiTarget.py index 88752ea0b..c4669a79d 100644 --- a/freqtrade/freqai/prediction_models/LightGBMRegressorMultiTarget.py +++ b/freqtrade/freqai/prediction_models/LightGBMRegressorMultiTarget.py @@ -1,5 +1,5 @@ import logging -from typing import Any, Dict +from typing import Any from lightgbm import LGBMRegressor @@ -21,7 +21,7 @@ class LightGBMRegressorMultiTarget(BaseRegressionModel): top level config.json file. """ - 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, diff --git a/freqtrade/freqai/prediction_models/PyTorchMLPClassifier.py b/freqtrade/freqai/prediction_models/PyTorchMLPClassifier.py index 246f6bb8c..d291ade7f 100644 --- a/freqtrade/freqai/prediction_models/PyTorchMLPClassifier.py +++ b/freqtrade/freqai/prediction_models/PyTorchMLPClassifier.py @@ -1,4 +1,4 @@ -from typing import Any, Dict +from typing import Any import torch @@ -52,10 +52,10 @@ class PyTorchMLPClassifier(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, diff --git a/freqtrade/freqai/prediction_models/PyTorchMLPRegressor.py b/freqtrade/freqai/prediction_models/PyTorchMLPRegressor.py index 67ba4825a..e5ff6d969 100644 --- a/freqtrade/freqai/prediction_models/PyTorchMLPRegressor.py +++ b/freqtrade/freqai/prediction_models/PyTorchMLPRegressor.py @@ -1,4 +1,4 @@ -from typing import Any, Dict +from typing import Any import torch @@ -51,10 +51,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: """ 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, diff --git a/freqtrade/freqai/prediction_models/PyTorchTransformerRegressor.py b/freqtrade/freqai/prediction_models/PyTorchTransformerRegressor.py index 2d60d68cf..19c4ecaf3 100644 --- a/freqtrade/freqai/prediction_models/PyTorchTransformerRegressor.py +++ b/freqtrade/freqai/prediction_models/PyTorchTransformerRegressor.py @@ -1,4 +1,4 @@ -from typing import Any, Dict, Tuple +from typing import Any import numpy as np import numpy.typing as npt @@ -60,10 +60,10 @@ class PyTorchTransformerRegressor(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: """ 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, @@ -100,7 +100,7 @@ class PyTorchTransformerRegressor(BasePyTorchRegressor): def predict( self, unfiltered_df: pd.DataFrame, dk: FreqaiDataKitchen, **kwargs - ) -> Tuple[pd.DataFrame, npt.NDArray[np.int_]]: + ) -> tuple[pd.DataFrame, npt.NDArray[np.int_]]: """ Filter the prediction features data and predict with it. :param unfiltered_df: Full dataframe for the current backtest period. diff --git a/freqtrade/freqai/prediction_models/ReinforcementLearner.py b/freqtrade/freqai/prediction_models/ReinforcementLearner.py index 7c2ad35ca..5bd119fbe 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, Dict, List, Optional, Type +from typing import Any, Optional import torch as th from stable_baselines3.common.callbacks import ProgressBarCallback @@ -44,7 +44,7 @@ class ReinforcementLearner(BaseReinforcementLearningModel): take fine-tuned control over the data handling pipeline. """ - def fit(self, data_dictionary: Dict[str, Any], dk: FreqaiDataKitchen, **kwargs): + def fit(self, data_dictionary: dict[str, Any], dk: FreqaiDataKitchen, **kwargs): """ User customizable fit method :param data_dictionary: dict = common data dictionary containing all train/test @@ -77,7 +77,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] + callbacks: list[Any] = [self.eval_callback, self.tensorboard_callback] progressbar_callback: Optional[ProgressBarCallback] = None if self.rl_config.get("progress_bar", False): progressbar_callback = ProgressBarCallback() @@ -101,7 +101,7 @@ class ReinforcementLearner(BaseReinforcementLearningModel): return model - MyRLEnv: Type[BaseEnvironment] + MyRLEnv: type[BaseEnvironment] class MyRLEnv(Base5ActionRLEnv): # type: ignore[no-redef] """ diff --git a/freqtrade/freqai/prediction_models/ReinforcementLearner_multiproc.py b/freqtrade/freqai/prediction_models/ReinforcementLearner_multiproc.py index 9fab42b18..9b2ba5191 100644 --- a/freqtrade/freqai/prediction_models/ReinforcementLearner_multiproc.py +++ b/freqtrade/freqai/prediction_models/ReinforcementLearner_multiproc.py @@ -1,5 +1,5 @@ import logging -from typing import Any, Dict +from typing import Any from pandas import DataFrame from sb3_contrib.common.maskable.callbacks import MaskableEvalCallback @@ -22,7 +22,7 @@ class ReinforcementLearner_multiproc(ReinforcementLearner): def set_train_and_eval_environments( self, - data_dictionary: Dict[str, Any], + data_dictionary: dict[str, Any], prices_train: DataFrame, prices_test: DataFrame, dk: FreqaiDataKitchen, diff --git a/freqtrade/freqai/prediction_models/SKLearnRandomForestClassifier.py b/freqtrade/freqai/prediction_models/SKLearnRandomForestClassifier.py index aa2830b8c..18685f78a 100644 --- a/freqtrade/freqai/prediction_models/SKLearnRandomForestClassifier.py +++ b/freqtrade/freqai/prediction_models/SKLearnRandomForestClassifier.py @@ -1,5 +1,5 @@ import logging -from typing import Any, Dict, Tuple +from typing import Any import numpy as np import numpy.typing as npt @@ -24,7 +24,7 @@ class SKLearnRandomForestClassifier(BaseClassifierModel): top level config.json file. """ - 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, @@ -61,7 +61,7 @@ class SKLearnRandomForestClassifier(BaseClassifierModel): def predict( self, unfiltered_df: DataFrame, dk: FreqaiDataKitchen, **kwargs - ) -> Tuple[DataFrame, npt.NDArray[np.int_]]: + ) -> tuple[DataFrame, npt.NDArray[np.int_]]: """ Filter the prediction features data and predict with it. :param unfiltered_df: Full dataframe for the current backtest period. diff --git a/freqtrade/freqai/prediction_models/XGBoostClassifier.py b/freqtrade/freqai/prediction_models/XGBoostClassifier.py index 41e034227..48634ce4d 100644 --- a/freqtrade/freqai/prediction_models/XGBoostClassifier.py +++ b/freqtrade/freqai/prediction_models/XGBoostClassifier.py @@ -1,5 +1,5 @@ import logging -from typing import Any, Dict, Tuple +from typing import Any import numpy as np import numpy.typing as npt @@ -26,7 +26,7 @@ class XGBoostClassifier(BaseClassifierModel): top level config.json file. """ - 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, @@ -64,7 +64,7 @@ class XGBoostClassifier(BaseClassifierModel): def predict( self, unfiltered_df: DataFrame, dk: FreqaiDataKitchen, **kwargs - ) -> Tuple[DataFrame, npt.NDArray[np.int_]]: + ) -> tuple[DataFrame, npt.NDArray[np.int_]]: """ Filter the prediction features data and predict with it. :param unfiltered_df: Full dataframe for the current backtest period. diff --git a/freqtrade/freqai/prediction_models/XGBoostRFClassifier.py b/freqtrade/freqai/prediction_models/XGBoostRFClassifier.py index f9875e8c2..6760ad285 100644 --- a/freqtrade/freqai/prediction_models/XGBoostRFClassifier.py +++ b/freqtrade/freqai/prediction_models/XGBoostRFClassifier.py @@ -1,5 +1,5 @@ import logging -from typing import Any, Dict, Tuple +from typing import Any import numpy as np import numpy.typing as npt @@ -26,7 +26,7 @@ class XGBoostRFClassifier(BaseClassifierModel): top level config.json file. """ - 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, @@ -64,7 +64,7 @@ class XGBoostRFClassifier(BaseClassifierModel): def predict( self, unfiltered_df: DataFrame, dk: FreqaiDataKitchen, **kwargs - ) -> Tuple[DataFrame, npt.NDArray[np.int_]]: + ) -> tuple[DataFrame, npt.NDArray[np.int_]]: """ Filter the prediction features data and predict with it. :param unfiltered_df: Full dataframe for the current backtest period. diff --git a/freqtrade/freqai/prediction_models/XGBoostRFRegressor.py b/freqtrade/freqai/prediction_models/XGBoostRFRegressor.py index 66a0ab846..12231ed13 100644 --- a/freqtrade/freqai/prediction_models/XGBoostRFRegressor.py +++ b/freqtrade/freqai/prediction_models/XGBoostRFRegressor.py @@ -1,5 +1,5 @@ import logging -from typing import Any, Dict +from typing import Any from xgboost import XGBRFRegressor @@ -21,7 +21,7 @@ class XGBoostRFRegressor(BaseRegressionModel): top level config.json file. """ - 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, diff --git a/freqtrade/freqai/prediction_models/XGBoostRegressor.py b/freqtrade/freqai/prediction_models/XGBoostRegressor.py index 0755eea11..f30aca65d 100644 --- a/freqtrade/freqai/prediction_models/XGBoostRegressor.py +++ b/freqtrade/freqai/prediction_models/XGBoostRegressor.py @@ -1,5 +1,5 @@ import logging -from typing import Any, Dict +from typing import Any from xgboost import XGBRegressor @@ -21,7 +21,7 @@ class XGBoostRegressor(BaseRegressionModel): top level config.json file. """ - 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, diff --git a/freqtrade/freqai/prediction_models/XGBoostRegressorMultiTarget.py b/freqtrade/freqai/prediction_models/XGBoostRegressorMultiTarget.py index 7bc01e89a..0586882b9 100644 --- a/freqtrade/freqai/prediction_models/XGBoostRegressorMultiTarget.py +++ b/freqtrade/freqai/prediction_models/XGBoostRegressorMultiTarget.py @@ -1,5 +1,5 @@ import logging -from typing import Any, Dict +from typing import Any from xgboost import XGBRegressor @@ -21,7 +21,7 @@ class XGBoostRegressorMultiTarget(BaseRegressionModel): top level config.json file. """ - 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, diff --git a/freqtrade/freqai/tensorboard/TensorboardCallback.py b/freqtrade/freqai/tensorboard/TensorboardCallback.py index 078d25bc4..2b2b532f3 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, Dict, Type, Union +from typing import Any, Union from stable_baselines3.common.callbacks import BaseCallback from stable_baselines3.common.logger import HParam @@ -13,10 +13,10 @@ class TensorboardCallback(BaseCallback): episodic summary reports. """ - def __init__(self, verbose=1, actions: Type[Enum] = BaseActions): + def __init__(self, verbose=1, actions: type[Enum] = BaseActions): super().__init__(verbose) self.model: Any = None - self.actions: Type[Enum] = actions + self.actions: type[Enum] = actions def _on_training_start(self) -> None: hparam_dict = { @@ -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, Union[float, int]] = { "eval/mean_reward": 0, "rollout/ep_rew_mean": 0, "rollout/ep_len_mean": 0, diff --git a/freqtrade/freqai/torch/PyTorchModelTrainer.py b/freqtrade/freqai/torch/PyTorchModelTrainer.py index 54c42a284..02aa712d5 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, Dict, List, Optional +from typing import Any, Optional import pandas as pd import torch @@ -25,7 +25,7 @@ class PyTorchModelTrainer(PyTorchTrainerInterface): criterion: nn.Module, device: str, data_convertor: PyTorchDataConvertor, - model_meta_data: Dict[str, Any] = {}, + model_meta_data: dict[str, Any] = {}, window_size: int = 1, tb_logger: Any = None, **kwargs, @@ -61,7 +61,7 @@ class PyTorchModelTrainer(PyTorchTrainerInterface): self.tb_logger = tb_logger self.test_batch_counter = 0 - def fit(self, data_dictionary: Dict[str, pd.DataFrame], splits: List[str]): + def fit(self, data_dictionary: dict[str, pd.DataFrame], splits: list[str]): """ :param data_dictionary: the dictionary constructed by DataHandler to hold all the training and test data/labels. @@ -102,7 +102,7 @@ class PyTorchModelTrainer(PyTorchTrainerInterface): @torch.no_grad() def estimate_loss( self, - data_loader_dictionary: Dict[str, DataLoader], + data_loader_dictionary: dict[str, DataLoader], split: str, ) -> None: self.model.eval() @@ -119,8 +119,8 @@ class PyTorchModelTrainer(PyTorchTrainerInterface): self.model.train() def create_data_loaders_dictionary( - self, data_dictionary: Dict[str, pd.DataFrame], splits: List[str] - ) -> Dict[str, DataLoader]: + self, data_dictionary: dict[str, pd.DataFrame], splits: list[str] + ) -> dict[str, DataLoader]: """ Converts the input data to PyTorch tensors using a data loader. """ @@ -181,7 +181,7 @@ class PyTorchModelTrainer(PyTorchTrainerInterface): checkpoint = torch.load(path) return self.load_from_checkpoint(checkpoint) - def load_from_checkpoint(self, checkpoint: Dict): + def load_from_checkpoint(self, checkpoint: dict): """ when using continual_learning, DataDrawer will load the dictionary (containing state dicts and model_meta_data) by calling torch.load(path). @@ -200,8 +200,8 @@ class PyTorchTransformerTrainer(PyTorchModelTrainer): """ def create_data_loaders_dictionary( - self, data_dictionary: Dict[str, pd.DataFrame], splits: List[str] - ) -> Dict[str, DataLoader]: + self, data_dictionary: dict[str, pd.DataFrame], splits: list[str] + ) -> dict[str, DataLoader]: """ Converts the input data to PyTorch tensors using a data loader. """ diff --git a/freqtrade/freqai/torch/PyTorchTrainerInterface.py b/freqtrade/freqai/torch/PyTorchTrainerInterface.py index 2c6f0c4d7..193540205 100644 --- a/freqtrade/freqai/torch/PyTorchTrainerInterface.py +++ b/freqtrade/freqai/torch/PyTorchTrainerInterface.py @@ -1,6 +1,5 @@ from abc import ABC, abstractmethod from pathlib import Path -from typing import Dict, List import pandas as pd import torch @@ -9,7 +8,7 @@ from torch import nn class PyTorchTrainerInterface(ABC): @abstractmethod - def fit(self, data_dictionary: Dict[str, pd.DataFrame], splits: List[str]) -> None: + def fit(self, data_dictionary: dict[str, pd.DataFrame], splits: list[str]) -> None: """ :param data_dictionary: the dictionary constructed by DataHandler to hold all the training and test data/labels. @@ -41,7 +40,7 @@ class PyTorchTrainerInterface(ABC): return self.load_from_checkpoint(checkpoint) @abstractmethod - def load_from_checkpoint(self, checkpoint: Dict) -> nn.Module: + def load_from_checkpoint(self, checkpoint: dict) -> nn.Module: """ when using continual_learning, DataDrawer will load the dictionary (containing state dicts and model_meta_data) by calling torch.load(path). diff --git a/freqtrade/freqai/utils.py b/freqtrade/freqai/utils.py index 4acdad306..9e14fa930 100644 --- a/freqtrade/freqai/utils.py +++ b/freqtrade/freqai/utils.py @@ -1,7 +1,7 @@ import logging from datetime import datetime, timezone from pathlib import Path -from typing import Any, Dict +from typing import Any import numpy as np import pandas as pd @@ -155,7 +155,7 @@ def plot_feature_importance( store_plot_file(fig, f"{dk.model_filename}-{label}.html", dk.data_path) -def record_params(config: Dict[str, Any], full_path: Path) -> None: +def record_params(config: dict[str, Any], full_path: Path) -> None: """ Records run params in the full path for reproducibility """ diff --git a/freqtrade/freqtradebot.py b/freqtrade/freqtradebot.py index fe270f670..0ca107b17 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, Dict, List, Optional, Tuple +from typing import Any, Optional from schedule import Scheduler @@ -43,6 +43,7 @@ from freqtrade.exchange import ( timeframe_to_next_date, timeframe_to_seconds, ) +from freqtrade.leverage.liquidation_price import update_liquidation_prices from freqtrade.misc import safe_value_fallback, safe_value_fallback2 from freqtrade.mixins import LoggingMixin from freqtrade.persistence import Order, PairLocks, Trade, init_db @@ -82,7 +83,7 @@ class FreqtradeBot(LoggingMixin): :param config: configuration dict, you can use Configuration.get_config() to get the config dict. """ - self.active_pair_whitelist: List[str] = [] + self.active_pair_whitelist: list[str] = [] # Init bot state self.state = State.STOPPED @@ -241,6 +242,7 @@ class FreqtradeBot(LoggingMixin): # Only update open orders on startup # This will update the database after the initial migration self.startup_update_open_orders() + self.update_all_liquidation_prices() self.update_funding_fees() def process(self) -> None: @@ -256,7 +258,7 @@ class FreqtradeBot(LoggingMixin): self.update_trades_without_assigned_fees() # Query trades from persistence layer - trades: List[Trade] = Trade.get_open_trades() + trades: list[Trade] = Trade.get_open_trades() self.active_pair_whitelist = self._refresh_active_whitelist(trades) @@ -323,7 +325,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: Optional[list[Trade]] = None) -> list[str]: """ Refresh active whitelist from pairlist or edge and extend it with pairs that have open trades. @@ -357,9 +359,19 @@ class FreqtradeBot(LoggingMixin): open_trades = Trade.get_open_trade_count() return max(0, self.config["max_open_trades"] - open_trades) + def update_all_liquidation_prices(self) -> None: + if self.trading_mode == TradingMode.FUTURES and self.margin_mode == MarginMode.CROSS: + # Update liquidation prices for all trades in cross margin mode + update_liquidation_prices( + exchange=self.exchange, + wallets=self.wallets, + stake_currency=self.config["stake_currency"], + dry_run=self.config["dry_run"], + ) + def update_funding_fees(self) -> None: if self.trading_mode == TradingMode.FUTURES: - trades: List[Trade] = Trade.get_open_trades() + trades: list[Trade] = Trade.get_open_trades() for trade in trades: trade.set_funding_fees( self.exchange.get_funding_fees( @@ -438,7 +450,7 @@ class FreqtradeBot(LoggingMixin): # Updating open orders in dry-run does not make sense and will fail. return - trades: List[Trade] = Trade.get_closed_trades_without_assigned_fees() + trades: list[Trade] = Trade.get_closed_trades_without_assigned_fees() for trade in trades: if not trade.is_open and not trade.fee_updated(trade.exit_side): # Get sell fee @@ -814,7 +826,7 @@ class FreqtradeBot(LoggingMixin): exit_tag=order_tag, ) - def _check_depth_of_market(self, pair: str, conf: Dict, side: SignalDirection) -> bool: + def _check_depth_of_market(self, pair: str, conf: dict, side: SignalDirection) -> bool: """ Checks depth of market before executing an entry """ @@ -1073,7 +1085,7 @@ class FreqtradeBot(LoggingMixin): trade: Optional[Trade], mode: EntryExecuteMode, leverage_: Optional[float], - ) -> Tuple[float, float, float]: + ) -> tuple[float, float, float]: """ Validate and eventually adjust (within limits) limit, amount and leverage :return: Tuple with (price, amount, leverage) @@ -1251,7 +1263,7 @@ class FreqtradeBot(LoggingMixin): # SELL / exit positions / close trades logic and methods # - def exit_positions(self, trades: List[Trade]) -> int: + def exit_positions(self, trades: list[Trade]) -> int: """ Tries to execute exit orders for open trades (positions) """ @@ -1337,7 +1349,7 @@ class FreqtradeBot(LoggingMixin): """ Check and execute trade exit """ - exits: List[ExitCheckTuple] = self.strategy.should_exit( + exits: list[ExitCheckTuple] = self.strategy.should_exit( trade, exit_rate, datetime.now(timezone.utc), @@ -1454,7 +1466,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: dict) -> None: """ Check to see if stoploss on exchange should be updated in case of trailing stoploss on exchange @@ -1492,7 +1504,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[dict]): """ Perform required actions according to existing stoploss orders of trade :param trade: Corresponding Trade @@ -1568,7 +1580,7 @@ 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: dict, 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() @@ -1620,7 +1632,7 @@ class FreqtradeBot(LoggingMixin): ) trade.delete() - def replace_order(self, order: Dict, order_obj: Optional[Order], trade: Trade) -> None: + def replace_order(self, order: dict, order_obj: Optional[Order], 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 @@ -1724,7 +1736,7 @@ class FreqtradeBot(LoggingMixin): def handle_cancel_enter( self, trade: Trade, - order: Dict, + order: dict, order_obj: Order, reason: str, replacing: Optional[bool] = False, @@ -1808,7 +1820,7 @@ 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: dict, order_obj: Order, reason: str) -> bool: """ exit order cancel - cancel order and update trade :return: True if exit order was cancelled, false otherwise @@ -2161,7 +2173,7 @@ class FreqtradeBot(LoggingMixin): self, trade: Trade, order_id: Optional[str], - action_order: Optional[Dict[str, Any]] = None, + action_order: Optional[dict[str, Any]] = None, *, stoploss_order: bool = False, send_msg: bool = True, @@ -2233,20 +2245,13 @@ class FreqtradeBot(LoggingMixin): # Must also run for partial exits # TODO: Margin will need to use interest_rate as well. # interest_rate = self.exchange.get_interest_rate() - try: - trade.set_liquidation_price( - self.exchange.get_liquidation_price( - pair=trade.pair, - open_rate=trade.open_rate, - is_short=trade.is_short, - amount=trade.amount, - stake_amount=trade.stake_amount, - leverage=trade.leverage, - wallet_balance=trade.stake_amount, - ) - ) - except DependencyException: - logger.warning("Unable to calculate liquidation price") + update_liquidation_prices( + trade, + exchange=self.exchange, + wallets=self.wallets, + stake_currency=self.config["stake_currency"], + dry_run=self.config["dry_run"], + ) if self.strategy.use_custom_stoploss: current_rate = self.exchange.get_rate( trade.pair, side="exit", is_short=trade.is_short, refresh=True @@ -2333,7 +2338,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: dict[str, Any]) -> None: # Try update amount (binance-fix) try: fee_abs = self.get_real_amount(trade, order, order_obj) @@ -2342,7 +2347,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: dict, order_obj: Order) -> Optional[float]: """ Detect and update trade fee. Calls trade.update_fee() upon correct detection. @@ -2389,7 +2394,7 @@ class FreqtradeBot(LoggingMixin): trade, order, order_obj, order_amount, order.get("trades", []) ) - def _trades_valid_for_fee(self, trades: List[Dict[str, Any]]) -> bool: + def _trades_valid_for_fee(self, trades: list[dict[str, Any]]) -> bool: """ Check if trades are valid for fee detection. :return: True if trades are valid for fee detection, False otherwise @@ -2402,7 +2407,7 @@ class FreqtradeBot(LoggingMixin): return True def fee_detection_from_trades( - self, trade: Trade, order: Dict, order_obj: Order, order_amount: float, trades: List + self, trade: Trade, order: dict, order_obj: Order, order_amount: float, trades: list ) -> Optional[float]: """ fee-detection fallback to Trades. @@ -2421,7 +2426,7 @@ class FreqtradeBot(LoggingMixin): fee_abs = 0.0 fee_cost = 0.0 trade_base_currency = self.exchange.get_pair_base_currency(trade.pair) - fee_rate_array: List[float] = [] + fee_rate_array: list[float] = [] for exectrade in trades: amount += exectrade["amount"] if self.exchange.order_has_fee(exectrade): diff --git a/freqtrade/ft_types/backtest_result_type.py b/freqtrade/ft_types/backtest_result_type.py index cad956597..25a890349 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, Dict, List, Optional +from typing import Any, Optional from typing_extensions import TypedDict @@ -9,9 +9,9 @@ class BacktestMetadataType(TypedDict): class BacktestResultType(TypedDict): - metadata: Dict[str, Any] # BacktestMetadataType - strategy: Dict[str, Any] - strategy_comparison: List[Any] + metadata: dict[str, Any] # BacktestMetadataType + strategy: dict[str, Any] + strategy_comparison: list[Any] def get_BacktestResultType_default() -> BacktestResultType: diff --git a/freqtrade/ft_types/valid_exchanges_type.py b/freqtrade/ft_types/valid_exchanges_type.py index 497ff8a93..89a06ba40 100644 --- a/freqtrade/ft_types/valid_exchanges_type.py +++ b/freqtrade/ft_types/valid_exchanges_type.py @@ -1,5 +1,5 @@ # Used for list-exchanges -from typing import List, Optional +from typing import Optional from typing_extensions import TypedDict @@ -18,4 +18,4 @@ class ValidExchangesType(TypedDict): dex: bool is_alias: bool alias_for: Optional[str] - trade_modes: List[TradeModeType] + trade_modes: list[TradeModeType] diff --git a/freqtrade/leverage/liquidation_price.py b/freqtrade/leverage/liquidation_price.py new file mode 100644 index 000000000..af6ef0d44 --- /dev/null +++ b/freqtrade/leverage/liquidation_price.py @@ -0,0 +1,66 @@ +import logging +from typing import Optional + +from freqtrade.enums import MarginMode +from freqtrade.exceptions import DependencyException +from freqtrade.exchange import Exchange +from freqtrade.persistence import LocalTrade, Trade +from freqtrade.wallets import Wallets + + +logger = logging.getLogger(__name__) + + +def update_liquidation_prices( + trade: Optional[LocalTrade] = None, + *, + exchange: Exchange, + wallets: Wallets, + stake_currency: str, + dry_run: bool = False, +): + """ + Update trade liquidation price in isolated margin mode. + Updates liquidation price for all trades in cross margin mode. + """ + try: + if exchange.margin_mode == MarginMode.CROSS: + total_wallet_stake = 0.0 + if dry_run: + # Parameters only needed for cross margin + total_wallet_stake = wallets.get_total(stake_currency) + + logger.info("Updating liquidation price for all open trades.") + open_trades = Trade.get_open_trades() + for t in open_trades: + # TODO: This should be done in a batch update + t.set_liquidation_price( + exchange.get_liquidation_price( + pair=t.pair, + open_rate=t.open_rate, + is_short=t.is_short, + amount=t.amount, + stake_amount=t.stake_amount, + leverage=t.leverage, + wallet_balance=total_wallet_stake, + open_trades=open_trades, + ) + ) + elif trade: + trade.set_liquidation_price( + exchange.get_liquidation_price( + pair=trade.pair, + open_rate=trade.open_rate, + is_short=trade.is_short, + amount=trade.amount, + stake_amount=trade.stake_amount, + leverage=trade.leverage, + wallet_balance=trade.stake_amount, + ) + ) + else: + raise DependencyException( + "Trade object is required for updating liquidation price in isolated margin mode." + ) + except DependencyException: + logger.warning("Unable to calculate liquidation price") diff --git a/freqtrade/loggers/__init__.py b/freqtrade/loggers/__init__.py index 1cc0590a1..7e18d3cba 100644 --- a/freqtrade/loggers/__init__.py +++ b/freqtrade/loggers/__init__.py @@ -1,6 +1,7 @@ import logging from logging import Formatter from logging.handlers import RotatingFileHandler, SysLogHandler +from pathlib import Path from freqtrade.constants import Config from freqtrade.exceptions import OperationalException @@ -86,11 +87,23 @@ def setup_logging(config: Config) -> None: handler_rf = get_existing_handlers(RotatingFileHandler) if handler_rf: logging.root.removeHandler(handler_rf) - handler_rf = RotatingFileHandler( - logfile, - maxBytes=1024 * 1024 * 10, # 10Mb - backupCount=10, - ) + try: + logfile_path = Path(logfile) + logfile_path.parent.mkdir(parents=True, exist_ok=True) + handler_rf = RotatingFileHandler( + logfile_path, + maxBytes=1024 * 1024 * 10, # 10Mb + backupCount=10, + ) + except PermissionError: + raise OperationalException( + f'Failed to create or access log file "{logfile_path.absolute()}". ' + "Please make sure you have the write permission to the log file or its parent " + "directories. If you're running freqtrade using docker, you see this error " + "message probably because you've logged in as the root user, please switch to " + "non-root user, delete and recreate the directories you need, and then try " + "again." + ) handler_rf.setFormatter(Formatter(LOGFORMAT)) logging.root.addHandler(handler_rf) diff --git a/freqtrade/main.py b/freqtrade/main.py index 67584c5b7..712cc49cf 100755 --- a/freqtrade/main.py +++ b/freqtrade/main.py @@ -6,26 +6,25 @@ Read the documentation to know what cli arguments you need. import logging import sys -from typing import Any, List, Optional +from typing import Any, Optional # check min. python version -if sys.version_info < (3, 9): # pragma: no cover - sys.exit("Freqtrade requires Python version >= 3.9") +if sys.version_info < (3, 10): # pragma: no cover + sys.exit("Freqtrade requires Python version >= 3.10") from freqtrade import __version__ from freqtrade.commands import Arguments -from freqtrade.configuration import asyncio_setup from freqtrade.constants import DOCS_LINK from freqtrade.exceptions import ConfigurationError, FreqtradeException, OperationalException from freqtrade.loggers import setup_logging_pre -from freqtrade.util.gc_setup import gc_set_threshold +from freqtrade.system import asyncio_setup, gc_set_threshold logger = logging.getLogger("freqtrade") -def main(sysargv: Optional[List[str]] = None) -> None: +def main(sysargv: Optional[list[str]] = 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 7c56231c3..629bd10dd 100644 --- a/freqtrade/misc.py +++ b/freqtrade/misc.py @@ -4,9 +4,10 @@ Various tool function for Freqtrade and scripts import gzip import logging +from collections.abc import Iterator, Mapping from io import StringIO from pathlib import Path -from typing import Any, Dict, Iterator, List, Mapping, Optional, TextIO, Union +from typing import Any, Optional, TextIO, Union from urllib.parse import urlparse import pandas as pd @@ -128,7 +129,7 @@ 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 = Union[dict[str, Any], Mapping[str, Any]] def safe_value_fallback(obj: DictMap, key1: str, key2: Optional[str] = None, default_value=None): @@ -164,7 +165,7 @@ def plural(num: float, singular: str, plural: Optional[str] = None) -> str: return singular if (num == 1 or num == -1) else plural or singular + "s" -def chunks(lst: List[Any], n: int) -> Iterator[List[Any]]: +def chunks(lst: list[Any], n: int) -> Iterator[list[Any]]: """ Split lst into chunks of the size n. :param lst: list to split into chunks diff --git a/freqtrade/optimize/analysis/lookahead.py b/freqtrade/optimize/analysis/lookahead.py index a8eb0258e..3daad027b 100755 --- a/freqtrade/optimize/analysis/lookahead.py +++ b/freqtrade/optimize/analysis/lookahead.py @@ -3,7 +3,7 @@ import shutil from copy import deepcopy from datetime import datetime, timedelta from pathlib import Path -from typing import Any, Dict, List +from typing import Any from pandas import DataFrame @@ -25,16 +25,16 @@ class Analysis: self.total_signals = 0 self.false_entry_signals = 0 self.false_exit_signals = 0 - self.false_indicators: List[str] = [] + self.false_indicators: list[str] = [] self.has_bias = False class LookaheadAnalysis(BaseAnalysis): - def __init__(self, config: Dict[str, Any], strategy_obj: Dict): + def __init__(self, config: dict[str, Any], strategy_obj: dict): super().__init__(config, strategy_obj) - self.entry_varHolders: List[VarHolder] = [] - self.exit_varHolders: List[VarHolder] = [] + self.entry_varHolders: list[VarHolder] = [] + self.exit_varHolders: list[VarHolder] = [] self.current_analysis = Analysis() self.minimum_trade_amount = config["minimum_trade_amount"] @@ -99,7 +99,7 @@ class LookaheadAnalysis(BaseAnalysis): f"{str(self_value)} != {str(other_value)}" ) - def prepare_data(self, varholder: VarHolder, pairs_to_load: List[DataFrame]): + def prepare_data(self, varholder: VarHolder, pairs_to_load: list[DataFrame]): if "freqai" in self.local_config and "identifier" in self.local_config["freqai"]: # purge previous data if the freqai model is defined # (to be sure nothing is carried over from older backtests) diff --git a/freqtrade/optimize/analysis/lookahead_helpers.py b/freqtrade/optimize/analysis/lookahead_helpers.py index 730f9fd72..dccf2cb73 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, Dict, List, Union +from typing import Any, Union import pandas as pd from rich.text import Text @@ -19,8 +19,8 @@ logger = logging.getLogger(__name__) class LookaheadAnalysisSubFunctions: @staticmethod def text_table_lookahead_analysis_instances( - config: Dict[str, Any], - lookahead_instances: List[LookaheadAnalysis], + config: dict[str, Any], + lookahead_instances: list[LookaheadAnalysis], caption: Union[str, None] = None, ): headers = [ @@ -73,7 +73,7 @@ class LookaheadAnalysisSubFunctions: return data @staticmethod - def export_to_csv(config: Dict[str, Any], lookahead_analysis: List[LookaheadAnalysis]): + def export_to_csv(config: dict[str, Any], lookahead_analysis: list[LookaheadAnalysis]): def add_or_update_row(df, row_data): if ( (df["filename"] == row_data["filename"]) & (df["strategy"] == row_data["strategy"]) @@ -198,7 +198,7 @@ class LookaheadAnalysisSubFunctions: return config @staticmethod - def initialize_single_lookahead_analysis(config: Config, strategy_obj: Dict[str, Any]): + def initialize_single_lookahead_analysis(config: Config, strategy_obj: dict[str, Any]): logger.info(f"Bias test of {Path(strategy_obj['location']).name} started.") start = time.perf_counter() current_instance = LookaheadAnalysis(config, strategy_obj) diff --git a/freqtrade/optimize/analysis/recursive.py b/freqtrade/optimize/analysis/recursive.py index e6f7e4152..50d1b75d6 100644 --- a/freqtrade/optimize/analysis/recursive.py +++ b/freqtrade/optimize/analysis/recursive.py @@ -1,9 +1,10 @@ import logging +import numbers import shutil from copy import deepcopy from datetime import timedelta from pathlib import Path -from typing import Any, Dict, List +from typing import Any from pandas import DataFrame @@ -20,8 +21,12 @@ from freqtrade.resolvers import StrategyResolver logger = logging.getLogger(__name__) +def is_number(variable): + return isinstance(variable, numbers.Number) and not isinstance(variable, bool) + + class RecursiveAnalysis(BaseAnalysis): - def __init__(self, config: Dict[str, Any], strategy_obj: Dict): + def __init__(self, config: dict[str, Any], strategy_obj: dict): self._startup_candle = list( map(int, config.get("startup_candle", [199, 399, 499, 999, 1999])) ) @@ -35,10 +40,10 @@ class RecursiveAnalysis(BaseAnalysis): self._startup_candle.append(self._strat_scc) self._startup_candle.sort() - self.partial_varHolder_array: List[VarHolder] = [] - self.partial_varHolder_lookahead_array: List[VarHolder] = [] + self.partial_varHolder_array: list[VarHolder] = [] + self.partial_varHolder_lookahead_array: list[VarHolder] = [] - self.dict_recursive: Dict[str, Any] = dict() + self.dict_recursive: dict[str, Any] = dict() # For recursive bias check # analyzes two data frames with processed indicators and shows differences between them. @@ -69,7 +74,12 @@ class RecursiveAnalysis(BaseAnalysis): values_diff_self = values_diff.loc["self"] values_diff_other = values_diff.loc["other"] - if values_diff_self and values_diff_other: + if ( + values_diff_self + and values_diff_other + and is_number(values_diff_self) + and is_number(values_diff_other) + ): diff = (values_diff_other - values_diff_self) / values_diff_self * 100 str_diff = f"{diff:.3f}%" else: @@ -114,7 +124,7 @@ class RecursiveAnalysis(BaseAnalysis): else: logger.info("No lookahead bias on indicators found.") - def prepare_data(self, varholder: VarHolder, pairs_to_load: List[DataFrame]): + def prepare_data(self, varholder: VarHolder, pairs_to_load: list[DataFrame]): if "freqai" in self.local_config and "identifier" in self.local_config["freqai"]: # purge previous data if the freqai model is defined # (to be sure nothing is carried over from older backtests) diff --git a/freqtrade/optimize/analysis/recursive_helpers.py b/freqtrade/optimize/analysis/recursive_helpers.py index 474604923..5877a5864 100644 --- a/freqtrade/optimize/analysis/recursive_helpers.py +++ b/freqtrade/optimize/analysis/recursive_helpers.py @@ -1,7 +1,7 @@ import logging import time from pathlib import Path -from typing import Any, Dict, List +from typing import Any from freqtrade.constants import Config from freqtrade.exceptions import OperationalException @@ -15,7 +15,7 @@ logger = logging.getLogger(__name__) class RecursiveAnalysisSubFunctions: @staticmethod - def text_table_recursive_analysis_instances(recursive_instances: List[RecursiveAnalysis]): + def text_table_recursive_analysis_instances(recursive_instances: list[RecursiveAnalysis]): startups = recursive_instances[0]._startup_candle strat_scc = recursive_instances[0]._strat_scc headers = ["Indicators"] @@ -63,7 +63,7 @@ class RecursiveAnalysisSubFunctions: return config @staticmethod - def initialize_single_recursive_analysis(config: Config, strategy_obj: Dict[str, Any]): + def initialize_single_recursive_analysis(config: Config, strategy_obj: dict[str, Any]): logger.info(f"Recursive test of {Path(strategy_obj['location']).name} started.") start = time.perf_counter() current_instance = RecursiveAnalysis(config, strategy_obj) diff --git a/freqtrade/optimize/backtesting.py b/freqtrade/optimize/backtesting.py index 20116f670..fd4c98b4d 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, Dict, List, Optional, Tuple +from typing import Any, Optional from numpy import nan from pandas import DataFrame @@ -26,6 +26,7 @@ from freqtrade.enums import ( CandleType, ExitCheckTuple, ExitType, + MarginMode, RunMode, TradingMode, ) @@ -37,6 +38,7 @@ from freqtrade.exchange import ( ) from freqtrade.exchange.exchange import Exchange from freqtrade.ft_types import BacktestResultType, get_BacktestResultType_default +from freqtrade.leverage.liquidation_price import update_liquidation_prices from freqtrade.mixins import LoggingMixin from freqtrade.optimize.backtest_caching import get_strategy_run_id from freqtrade.optimize.bt_progress import BTProgress @@ -116,13 +118,13 @@ class Backtesting: self.order_id_counter: int = 0 config["dry_run"] = True - self.run_ids: Dict[str, str] = {} - self.strategylist: List[IStrategy] = [] - self.all_results: Dict[str, Dict] = {} - self.processed_dfs: Dict[str, Dict] = {} - self.rejected_dict: Dict[str, List] = {} - self.rejected_df: Dict[str, Dict] = {} - self.exited_dfs: Dict[str, Dict] = {} + self.run_ids: dict[str, str] = {} + self.strategylist: list[IStrategy] = [] + self.all_results: dict[str, dict] = {} + self.processed_dfs: dict[str, dict] = {} + self.rejected_dict: dict[str, list] = {} + self.rejected_df: dict[str, dict] = {} + self.exited_dfs: dict[str, dict] = {} self._exchange_name = self.config["exchange"]["name"] if not exchange: @@ -206,6 +208,7 @@ class Backtesting: self.required_startup = self.dataprovider.get_required_startup(self.timeframe) self.trading_mode: TradingMode = config.get("trading_mode", TradingMode.SPOT) + self.margin_mode: MarginMode = config.get("margin_mode", MarginMode.ISOLATED) # strategies which define "can_short=True" will fail to load in Spot mode. self._can_short = self.trading_mode != TradingMode.SPOT self._position_stacking: bool = self.config.get("position_stacking", False) @@ -243,8 +246,8 @@ class Backtesting: else: self.timeframe_detail_td = timedelta(seconds=0) - self.detail_data: Dict[str, DataFrame] = {} - self.futures_data: Dict[str, DataFrame] = {} + self.detail_data: dict[str, DataFrame] = {} + self.futures_data: dict[str, DataFrame] = {} def init_backtest(self): self.prepare_backtest(False) @@ -273,13 +276,9 @@ class Backtesting: def _load_protections(self, strategy: IStrategy): if self.config.get("enable_protections", False): - conf = self.config - if hasattr(strategy, "protections"): - conf = deepcopy(conf) - conf["protections"] = strategy.protections self.protections = ProtectionManager(self.config, strategy.protections) - def load_bt_data(self) -> Tuple[Dict[str, DataFrame], TimeRange]: + def load_bt_data(self) -> tuple[dict[str, DataFrame], TimeRange]: """ Loads backtest data and returns the data combined with the timerange as tuple. @@ -409,7 +408,7 @@ class Backtesting: self.abort = False raise DependencyException("Stop requested") - def _get_ohlcv_as_lists(self, processed: Dict[str, DataFrame]) -> Dict[str, Tuple]: + def _get_ohlcv_as_lists(self, processed: dict[str, DataFrame]) -> dict[str, tuple]: """ Helper function to convert a processed dataframes into lists for performance reasons. @@ -419,7 +418,7 @@ class Backtesting: optimize memory usage! """ - data: Dict = {} + data: dict = {} self.progress.init_step(BacktestState.CONVERT, len(processed)) # Create dict with data @@ -467,7 +466,7 @@ class Backtesting: return data def _get_close_rate( - self, row: Tuple, trade: LocalTrade, exit_: ExitCheckTuple, trade_dur: int + self, row: tuple, trade: LocalTrade, exit_: ExitCheckTuple, trade_dur: int ) -> float: """ Get close rate for backtesting result @@ -485,7 +484,7 @@ class Backtesting: return row[OPEN_IDX] def _get_close_rate_for_stoploss( - self, row: Tuple, trade: LocalTrade, exit_: ExitCheckTuple, trade_dur: int + self, row: tuple, trade: LocalTrade, exit_: ExitCheckTuple, trade_dur: int ) -> float: # our stoploss was already lower than candle high, # possibly due to a cancelled trade exit. @@ -539,7 +538,7 @@ class Backtesting: return stoploss_value def _get_close_rate_for_roi( - self, row: Tuple, trade: LocalTrade, exit_: ExitCheckTuple, trade_dur: int + self, row: tuple, trade: LocalTrade, exit_: ExitCheckTuple, trade_dur: int ) -> float: is_short = trade.is_short or False leverage = trade.leverage or 1.0 @@ -602,7 +601,7 @@ class Backtesting: return row[OPEN_IDX] def _get_adjust_trade_entry_for_candle( - self, trade: LocalTrade, row: Tuple, current_time: datetime + self, trade: LocalTrade, row: tuple, current_time: datetime ) -> LocalTrade: current_rate: float = row[OPEN_IDX] current_profit = trade.calc_profit_ratio(current_rate) @@ -670,7 +669,7 @@ class Backtesting: return trade - def _get_order_filled(self, rate: float, row: Tuple) -> bool: + def _get_order_filled(self, rate: float, row: tuple) -> bool: """Rate is within candle, therefore filled""" return row[LOW_IDX] <= rate <= row[HIGH_IDX] @@ -686,7 +685,7 @@ class Backtesting: ) def _try_close_open_order( - self, order: Optional[Order], trade: LocalTrade, current_date: datetime, row: Tuple + self, order: Optional[Order], trade: LocalTrade, current_date: datetime, row: tuple ) -> bool: """ Check if an order is open and if it should've filled. @@ -702,26 +701,25 @@ class Backtesting: current_time=current_date, ) - if not (order.ft_order_side == trade.exit_side and order.safe_amount == trade.amount): - # trade is still open - trade.set_liquidation_price( - self.exchange.get_liquidation_price( - pair=trade.pair, - open_rate=trade.open_rate, - is_short=trade.is_short, - amount=trade.amount, - stake_amount=trade.stake_amount, - leverage=trade.leverage, - wallet_balance=trade.stake_amount, - ) + if self.margin_mode == MarginMode.CROSS or not ( + order.ft_order_side == trade.exit_side and order.safe_amount == trade.amount + ): + # trade is still open or we are in cross margin mode and + # must update all liquidation prices + update_liquidation_prices( + trade, + exchange=self.exchange, + wallets=self.wallets, + stake_currency=self.config["stake_currency"], + dry_run=self.config["dry_run"], ) + if not (order.ft_order_side == trade.exit_side and order.safe_amount == trade.amount): self._call_adjust_stop(current_date, trade, order.ft_price) - # pass return True return False def _process_exit_order( - self, order: Order, trade: LocalTrade, current_time: datetime, row: Tuple, pair: str + self, order: Order, trade: LocalTrade, current_time: datetime, row: tuple, pair: str ): """ Takes an exit order and processes it, potentially closing the trade. @@ -734,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) @@ -742,7 +739,7 @@ class Backtesting: def _get_exit_for_signal( self, trade: LocalTrade, - row: Tuple, + row: tuple, exit_: ExitCheckTuple, current_time: datetime, amount: Optional[float] = None, @@ -822,7 +819,7 @@ class Backtesting: def _exit_trade( self, trade: LocalTrade, - sell_row: Tuple, + sell_row: tuple, close_rate: float, amount: float, exit_reason: Optional[str], @@ -861,7 +858,7 @@ class Backtesting: return trade def _check_trade_exit( - self, trade: LocalTrade, row: Tuple, current_time: datetime + self, trade: LocalTrade, row: tuple, current_time: datetime ) -> Optional[LocalTrade]: self._run_funding_fees(trade, current_time) @@ -907,7 +904,7 @@ class Backtesting: def get_valid_price_and_stake( self, pair: str, - row: Tuple, + row: tuple, propose_rate: float, stake_amount: float, direction: LongShort, @@ -916,7 +913,7 @@ class Backtesting: trade: Optional[LocalTrade], order_type: str, price_precision: Optional[float], - ) -> Tuple[float, float, float, float]: + ) -> tuple[float, float, float, float]: if order_type == "limit": new_rate = strategy_safe_wrapper( self.strategy.custom_entry_price, default_retval=propose_rate @@ -1005,7 +1002,7 @@ class Backtesting: def _enter_trade( self, pair: str, - row: Tuple, + row: tuple, direction: LongShort, stake_amount: Optional[float] = None, trade: Optional[LocalTrade] = None, @@ -1105,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, @@ -1152,7 +1150,7 @@ class Backtesting: return trade def handle_left_open( - self, open_trades: Dict[str, List[LocalTrade]], data: Dict[str, List[Tuple]] + self, open_trades: dict[str, list[LocalTrade]], data: dict[str, list[tuple]] ) -> None: """ Handling of left open trades at the end of backtesting @@ -1199,7 +1197,7 @@ class Backtesting: self.protections.stop_per_pair(pair, current_time, side) self.protections.global_stop(current_time, side) - def manage_open_orders(self, trade: LocalTrade, current_time: datetime, row: Tuple) -> bool: + def manage_open_orders(self, trade: LocalTrade, current_time: datetime, row: tuple) -> bool: """ Check if any open order needs to be cancelled or replaced. Returns True if the trade should be deleted. @@ -1248,7 +1246,7 @@ class Backtesting: return None def check_order_replace( - self, trade: LocalTrade, order: Order, current_time, row: Tuple + self, trade: LocalTrade, order: Order, current_time, row: tuple ) -> bool: """ Check if current analyzed entry order has to be replaced and do so. @@ -1299,8 +1297,8 @@ class Backtesting: return False def validate_row( - self, data: Dict, pair: str, row_index: int, current_time: datetime - ) -> Optional[Tuple]: + self, data: dict, pair: str, row_index: int, current_time: datetime + ) -> Optional[tuple]: try: # Row is treated as "current incomplete candle". # entry / exit signals are shifted by 1 to compensate for this. @@ -1331,12 +1329,11 @@ class Backtesting: def backtest_loop( self, - row: Tuple, + row: tuple, pair: str, current_time: datetime, - end_date: datetime, trade_dir: Optional[LongShort], - is_first: bool = True, + can_enter: bool, ) -> None: """ NOTE: This method is used by Hyperopt at each iteration. Please keep it optimized. @@ -1346,7 +1343,7 @@ class Backtesting: 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): - # Close trade + # Remove trade (initial open order never filled) LocalTrade.remove_bt_trade(t) self.wallets.update() @@ -1356,13 +1353,12 @@ class Backtesting: # don't open on the last row # We only open trades on the main candle, not on detail candles if ( - (self._position_stacking or len(LocalTrade.bt_trades_open_pp[pair]) == 0) - and is_first - and current_time != end_date + can_enter and trade_dir is not None + 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() @@ -1385,7 +1381,7 @@ class Backtesting: self._process_exit_order(order, trade, current_time, row, pair) def time_pair_generator( - self, start_date: datetime, end_date: datetime, increment: timedelta, pairs: List[str] + self, start_date: datetime, end_date: datetime, increment: timedelta, pairs: list[str] ): """ Backtest time and pair generator @@ -1406,7 +1402,7 @@ class Backtesting: self.progress.increment() current_time += increment - def backtest(self, processed: Dict, start_date: datetime, end_date: datetime) -> Dict[str, Any]: + def backtest(self, processed: dict, start_date: datetime, end_date: datetime) -> dict[str, Any]: """ Implement backtesting functionality @@ -1425,17 +1421,21 @@ class Backtesting: self.wallets.update() # Use dict of lists with data for performance # (looping lists is a lot faster than pandas DataFrames) - data: Dict = self._get_ohlcv_as_lists(processed) + data: dict = self._get_ohlcv_as_lists(processed) # Indexes per pair, so some pairs are allowed to have a missing start. - indexes: Dict = defaultdict(int) + indexes: dict = defaultdict(int) # Loop timerange and get candle for each pair at that point in time - for current_time, pair, is_first in self.time_pair_generator( + for current_time, pair, is_first_call in self.time_pair_generator( start_date, end_date, self.timeframe_td, list(data.keys()) ): - if is_first: + 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,6 +1446,7 @@ class Backtesting: row_index += 1 indexes[pair] = row_index + is_last_row = current_time == end_date 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() @@ -1468,7 +1469,8 @@ class Backtesting: ].copy() if len(detail_data) == 0: # Fall back to "regular" data if no detail data was found for this candle - self.backtest_loop(row, pair, current_time, end_date, trade_dir) + self.dataprovider._set_dataframe_max_date(current_time) + self.backtest_loop(row, pair, current_time, trade_dir, not is_last_row) continue detail_data.loc[:, "enter_long"] = row[LONG_IDX] detail_data.loc[:, "exit_long"] = row[ELONG_IDX] @@ -1484,15 +1486,14 @@ class Backtesting: det_row, pair, current_time_det, - end_date, trade_dir, - is_first, + is_first and not is_last_row, ) current_time_det += self.timeframe_detail_td is_first = False else: self.dataprovider._set_dataframe_max_date(current_time) - self.backtest_loop(row, pair, current_time, end_date, trade_dir) + self.backtest_loop(row, pair, current_time, trade_dir, not is_last_row) self.handle_left_open(LocalTrade.bt_trades_open_pp, data=data) self.wallets.update() @@ -1512,7 +1513,7 @@ class Backtesting: } def backtest_one_strategy( - self, strat: IStrategy, data: Dict[str, DataFrame], timerange: TimeRange + self, strat: IStrategy, data: dict[str, DataFrame], timerange: TimeRange ): self.progress.init_step(BacktestState.ANALYZE, 0) strategy_name = strat.get_strategy_name() @@ -1607,7 +1608,7 @@ class Backtesting: """ Run backtesting end-to-end """ - data: Dict[str, DataFrame] = {} + data: dict[str, DataFrame] = {} data, timerange = self.load_bt_data() self.load_bt_data_detail() diff --git a/freqtrade/optimize/base_analysis.py b/freqtrade/optimize/base_analysis.py index 2503ede72..e3e475742 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, Dict, Optional +from typing import Any, Optional from pandas import DataFrame @@ -14,7 +14,7 @@ logger = logging.getLogger(__name__) class VarHolder: timerange: TimeRange data: DataFrame - indicators: Dict[str, DataFrame] + indicators: dict[str, DataFrame] result: DataFrame compared: DataFrame from_dt: datetime @@ -25,7 +25,7 @@ class VarHolder: class BaseAnalysis: - def __init__(self, config: Dict[str, Any], strategy_obj: Dict): + def __init__(self, config: dict[str, Any], strategy_obj: dict): self.failed_bias_check = True self.full_varHolder = VarHolder() self.exchange: Optional[Any] = None diff --git a/freqtrade/optimize/hyperopt.py b/freqtrade/optimize/hyperopt.py index 428a5cddd..daa4661ad 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, Dict, List, Optional, Tuple +from typing import Any, Optional import rapidjson from joblib import Parallel, cpu_count, delayed, dump, load, wrap_non_picklable_objects @@ -70,14 +70,14 @@ class Hyperopt: """ def __init__(self, config: Config) -> None: - self.buy_space: List[Dimension] = [] - self.sell_space: List[Dimension] = [] - self.protection_space: List[Dimension] = [] - self.roi_space: List[Dimension] = [] - self.stoploss_space: List[Dimension] = [] - self.trailing_space: List[Dimension] = [] - self.max_open_trades_space: List[Dimension] = [] - self.dimensions: List[Dimension] = [] + self.buy_space: list[Dimension] = [] + self.sell_space: list[Dimension] = [] + self.protection_space: list[Dimension] = [] + self.roi_space: list[Dimension] = [] + self.stoploss_space: list[Dimension] = [] + self.trailing_space: list[Dimension] = [] + self.max_open_trades_space: list[Dimension] = [] + self.dimensions: list[Dimension] = [] self._hyper_out: HyperoptOutput = HyperoptOutput(streaming=True) @@ -125,7 +125,7 @@ class Hyperopt: self.market_change = 0.0 self.num_epochs_saved = 0 - self.current_best_epoch: Optional[Dict[str, Any]] = None + 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): @@ -168,8 +168,8 @@ class Hyperopt: self.hyperopt_pickle_magic(modules.__bases__) def _get_params_dict( - self, dimensions: List[Dimension], raw_params: List[Any] - ) -> Dict[str, Any]: + self, dimensions: list[Dimension], raw_params: list[Any] + ) -> dict[str, Any]: # Ensure the number of dimensions match # the number of parameters in the list. if len(raw_params) != len(dimensions): @@ -179,7 +179,7 @@ class Hyperopt: # and the values are taken from the list of parameters. return {d.name: v for d, v in zip(dimensions, raw_params)} - def _save_result(self, epoch: Dict) -> None: + def _save_result(self, epoch: dict) -> None: """ Save hyperopt results to file Store one line per epoch. @@ -205,11 +205,11 @@ class Hyperopt: latest_filename = Path.joinpath(self.results_file.parent, LAST_BT_RESULT_FN) file_dump_json(latest_filename, {"latest_hyperopt": str(self.results_file.name)}, log=False) - def _get_params_details(self, params: Dict) -> Dict: + def _get_params_details(self, params: dict) -> dict: """ Return the params for each space """ - result: Dict = {} + result: dict = {} if HyperoptTools.has_space(self.config, "buy"): result["buy"] = {p.name: params.get(p.name) for p in self.buy_space} @@ -236,11 +236,11 @@ class Hyperopt: return result - def _get_no_optimize_details(self) -> Dict[str, Any]: + def _get_no_optimize_details(self) -> dict[str, Any]: """ Get non-optimized parameters """ - result: Dict[str, Any] = {} + result: dict[str, Any] = {} strategy = self.backtesting.strategy if not HyperoptTools.has_space(self.config, "roi"): result["roi"] = {str(k): v for k, v in strategy.minimal_roi.items()} @@ -257,7 +257,7 @@ class Hyperopt: result["max_open_trades"] = {"max_open_trades": strategy.max_open_trades} return result - def print_results(self, results: Dict[str, Any]) -> None: + def print_results(self, results: dict[str, Any]) -> None: """ Log results if it is better than any previous evaluation TODO: this should be moved to HyperoptTools too @@ -318,7 +318,7 @@ class Hyperopt: + self.max_open_trades_space ) - def assign_params(self, params_dict: Dict[str, Any], category: str) -> None: + def assign_params(self, params_dict: dict[str, Any], category: str) -> None: """ Assign hyperoptable parameters """ @@ -327,7 +327,7 @@ class Hyperopt: # noinspection PyProtectedMember attr.value = params_dict[attr_name] - def generate_optimizer(self, raw_params: List[Any]) -> Dict[str, Any]: + def generate_optimizer(self, raw_params: list[Any]) -> dict[str, Any]: """ Used Optimize function. Called once per epoch to optimize whatever is configured. @@ -406,12 +406,12 @@ class Hyperopt: def _get_results_dict( self, - backtesting_results: Dict[str, Any], + backtesting_results: dict[str, Any], min_date: datetime, max_date: datetime, - params_dict: Dict[str, Any], - processed: Dict[str, DataFrame], - ) -> Dict[str, Any]: + params_dict: dict[str, Any], + processed: dict[str, DataFrame], + ) -> dict[str, Any]: params_details = self._get_params_details(params_dict) strat_stats = generate_strategy_stats( @@ -458,7 +458,7 @@ class Hyperopt: "total_profit": total_profit, } - def get_optimizer(self, dimensions: List[Dimension], cpu_count) -> Optimizer: + def get_optimizer(self, dimensions: list[Dimension], cpu_count) -> Optimizer: estimator = self.custom_hyperopt.generate_estimator(dimensions=dimensions) acq_optimizer = "sampling" @@ -479,7 +479,7 @@ class Hyperopt: model_queue_size=SKOPT_MODEL_QUEUE_SIZE, ) - def run_optimizer_parallel(self, parallel: Parallel, asked: List[List]) -> List[Dict[str, Any]]: + def run_optimizer_parallel(self, parallel: Parallel, asked: list[list]) -> list[dict[str, Any]]: """Start optimizer in a parallel way""" return parallel( delayed(wrap_non_picklable_objects(self.generate_optimizer))(v) for v in asked @@ -488,7 +488,7 @@ class Hyperopt: def _set_random_state(self, random_state: Optional[int]) -> 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]: + def advise_and_trim(self, data: dict[str, DataFrame]) -> dict[str, DataFrame]: preprocessed = self.backtesting.strategy.advise_all_indicators(data) # Trim startup period from analyzed dataframe to get correct dates for output. @@ -524,7 +524,7 @@ class Hyperopt: else: dump(data, self.data_pickle_file) - def get_asked_points(self, n_points: int) -> Tuple[List[List[Any]], List[bool]]: + def get_asked_points(self, n_points: int) -> tuple[list[list[Any]], list[bool]]: """ Enforce points returned from `self.opt.ask` have not been already evaluated @@ -545,8 +545,8 @@ class Hyperopt: return new_list i = 0 - asked_non_tried: List[List[Any]] = [] - is_random_non_tried: List[bool] = [] + asked_non_tried: list[list[Any]] = [] + is_random_non_tried: list[bool] = [] while i < 5 and len(asked_non_tried) < n_points: if i < 3: self.opt.cache_ = {} @@ -573,7 +573,7 @@ class Hyperopt: else: return self.opt.ask(n_points=n_points), [False for _ in range(n_points)] - def evaluate_result(self, val: Dict[str, Any], current: int, is_random: bool): + def evaluate_result(self, val: dict[str, Any], current: int, is_random: bool): """ Evaluate results returned from generate_optimizer """ diff --git a/freqtrade/optimize/hyperopt_auto.py b/freqtrade/optimize/hyperopt_auto.py index cf0103162..9d2f89dc7 100644 --- a/freqtrade/optimize/hyperopt_auto.py +++ b/freqtrade/optimize/hyperopt_auto.py @@ -6,7 +6,7 @@ This module implements a convenience auto-hyperopt class, which can be used toge import logging from contextlib import suppress -from typing import Callable, Dict, List +from typing import Callable from freqtrade.exceptions import OperationalException @@ -59,7 +59,7 @@ class HyperOptAuto(IHyperOpt): if attr.optimize: yield attr.get_space(attr_name) - def _get_indicator_space(self, category) -> List: + def _get_indicator_space(self, category) -> list: # TODO: is this necessary, or can we call "generate_space" directly? indicator_space = list(self._generate_indicator_space(category)) if len(indicator_space) > 0: @@ -70,32 +70,32 @@ class HyperOptAuto(IHyperOpt): ) return [] - def buy_indicator_space(self) -> List["Dimension"]: + def buy_indicator_space(self) -> list["Dimension"]: return self._get_indicator_space("buy") - def sell_indicator_space(self) -> List["Dimension"]: + def sell_indicator_space(self) -> list["Dimension"]: return self._get_indicator_space("sell") - def protection_space(self) -> List["Dimension"]: + def protection_space(self) -> list["Dimension"]: return self._get_indicator_space("protection") - def generate_roi_table(self, params: Dict) -> Dict[int, float]: + def generate_roi_table(self, params: dict) -> dict[int, float]: return self._get_func("generate_roi_table")(params) - def roi_space(self) -> List["Dimension"]: + def roi_space(self) -> list["Dimension"]: return self._get_func("roi_space")() - def stoploss_space(self) -> List["Dimension"]: + def stoploss_space(self) -> list["Dimension"]: return self._get_func("stoploss_space")() - def generate_trailing_params(self, params: Dict) -> Dict: + def generate_trailing_params(self, params: dict) -> dict: return self._get_func("generate_trailing_params")(params) - def trailing_space(self) -> List["Dimension"]: + def trailing_space(self) -> list["Dimension"]: return self._get_func("trailing_space")() - def max_open_trades_space(self) -> List["Dimension"]: + def max_open_trades_space(self) -> list["Dimension"]: return self._get_func("max_open_trades_space")() - def generate_estimator(self, dimensions: List["Dimension"], **kwargs) -> EstimatorType: + def generate_estimator(self, dimensions: list["Dimension"], **kwargs) -> EstimatorType: return self._get_func("generate_estimator")(dimensions=dimensions, **kwargs) diff --git a/freqtrade/optimize/hyperopt_epoch_filters.py b/freqtrade/optimize/hyperopt_epoch_filters.py index 0a88b9d65..5112b062d 100644 --- a/freqtrade/optimize/hyperopt_epoch_filters.py +++ b/freqtrade/optimize/hyperopt_epoch_filters.py @@ -1,5 +1,4 @@ import logging -from typing import List from freqtrade.exceptions import OperationalException @@ -7,7 +6,7 @@ from freqtrade.exceptions import OperationalException logger = logging.getLogger(__name__) -def hyperopt_filter_epochs(epochs: List, filteroptions: dict, log: bool = True) -> List: +def hyperopt_filter_epochs(epochs: list, filteroptions: dict, log: bool = True) -> list: """ Filter our items from the list of hyperopt results """ @@ -33,14 +32,14 @@ def hyperopt_filter_epochs(epochs: List, filteroptions: dict, log: bool = True) return epochs -def _hyperopt_filter_epochs_trade(epochs: List, trade_count: int): +def _hyperopt_filter_epochs_trade(epochs: list, trade_count: int): """ Filter epochs with trade-counts > trades """ return [x for x in epochs if x["results_metrics"].get("total_trades", 0) > trade_count] -def _hyperopt_filter_epochs_trade_count(epochs: List, filteroptions: dict) -> List: +def _hyperopt_filter_epochs_trade_count(epochs: list, filteroptions: dict) -> list: if filteroptions["filter_min_trades"] > 0: epochs = _hyperopt_filter_epochs_trade(epochs, filteroptions["filter_min_trades"]) @@ -53,7 +52,7 @@ def _hyperopt_filter_epochs_trade_count(epochs: List, filteroptions: dict) -> Li return epochs -def _hyperopt_filter_epochs_duration(epochs: List, filteroptions: dict) -> List: +def _hyperopt_filter_epochs_duration(epochs: list, filteroptions: dict) -> list: def get_duration_value(x): # Duration in minutes ... if "holding_avg_s" in x["results_metrics"]: @@ -74,7 +73,7 @@ def _hyperopt_filter_epochs_duration(epochs: List, filteroptions: dict) -> List: return epochs -def _hyperopt_filter_epochs_profit(epochs: List, filteroptions: dict) -> List: +def _hyperopt_filter_epochs_profit(epochs: list, filteroptions: dict) -> list: if filteroptions["filter_min_avg_profit"] is not None: epochs = _hyperopt_filter_epochs_trade(epochs, 0) epochs = [ @@ -110,7 +109,7 @@ def _hyperopt_filter_epochs_profit(epochs: List, filteroptions: dict) -> List: return epochs -def _hyperopt_filter_epochs_objective(epochs: List, filteroptions: dict) -> List: +def _hyperopt_filter_epochs_objective(epochs: list, filteroptions: dict) -> list: if filteroptions["filter_min_objective"] is not None: epochs = _hyperopt_filter_epochs_trade(epochs, 0) diff --git a/freqtrade/optimize/hyperopt_interface.py b/freqtrade/optimize/hyperopt_interface.py index 216e40753..2c0983b52 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 Dict, List, Union +from typing import Union from sklearn.base import RegressorMixin from skopt.space import Categorical, Dimension, Integer @@ -41,7 +41,7 @@ class IHyperOpt(ABC): # Assign timeframe to be used in hyperopt IHyperOpt.timeframe = str(config["timeframe"]) - def generate_estimator(self, dimensions: List[Dimension], **kwargs) -> EstimatorType: + def generate_estimator(self, dimensions: list[Dimension], **kwargs) -> EstimatorType: """ Return base_estimator. Can be any of "GP", "RF", "ET", "GBRT" or an instance of a class @@ -49,7 +49,7 @@ class IHyperOpt(ABC): """ return "ET" - def generate_roi_table(self, params: Dict) -> Dict[int, float]: + def generate_roi_table(self, params: dict) -> dict[int, float]: """ Create a ROI table. @@ -64,7 +64,7 @@ class IHyperOpt(ABC): return roi_table - def roi_space(self) -> List[Dimension]: + def roi_space(self) -> list[Dimension]: """ Create a ROI space. @@ -146,7 +146,7 @@ class IHyperOpt(ABC): ), ] - def stoploss_space(self) -> List[Dimension]: + def stoploss_space(self) -> list[Dimension]: """ Create a stoploss space. @@ -157,7 +157,7 @@ class IHyperOpt(ABC): SKDecimal(-0.35, -0.02, decimals=3, name="stoploss"), ] - def generate_trailing_params(self, params: Dict) -> Dict: + def generate_trailing_params(self, params: dict) -> dict: """ Create dict with trailing stop parameters. """ @@ -170,7 +170,7 @@ class IHyperOpt(ABC): "trailing_only_offset_is_reached": params["trailing_only_offset_is_reached"], } - def trailing_space(self) -> List[Dimension]: + def trailing_space(self) -> list[Dimension]: """ Create a trailing stoploss space. @@ -194,7 +194,7 @@ class IHyperOpt(ABC): Categorical([True, False], name="trailing_only_offset_is_reached"), ] - def max_open_trades_space(self) -> List[Dimension]: + def max_open_trades_space(self) -> list[Dimension]: """ Create a max open trades space. diff --git a/freqtrade/optimize/hyperopt_loss/hyperopt_loss_multi_metric.py b/freqtrade/optimize/hyperopt_loss/hyperopt_loss_multi_metric.py new file mode 100644 index 000000000..de8d117d6 --- /dev/null +++ b/freqtrade/optimize/hyperopt_loss/hyperopt_loss_multi_metric.py @@ -0,0 +1,108 @@ +""" +MultiMetricHyperOptLoss + +This module defines the alternative HyperOptLoss class based on: + - Profit + - Drawdown + - Profit Factor + - Expectancy Ratio + - Winrate + - Amount of trades + +Possible to change: + - `DRAWDOWN_MULT` to penalize drawdown objective for individual needs; + - `TARGET_TRADE_AMOUNT` to adjust amount of trades impact. + - `EXPECTANCY_CONST` to adjust expectancy ratio impact. + - `PF_CONST` to adjust profit factor impact. + - `WINRATE_CONST` to adjust winrate impact. + + +DRAWDOWN_MULT variable within the hyperoptloss file can be adjusted to be stricter or more + flexible on drawdown purposes. Smaller numbers penalize drawdowns more severely. +PF_CONST variable adjusts the impact of the Profit Factor on the optimization. +EXPECTANCY_CONST variable controls the influence of the Expectancy Ratio. +WINRATE_CONST variable can be adjusted to increase or decrease impact of winrate. + +PF_CONST, EXPECTANCY_CONST, WINRATE_CONST all operate in a similar manner: + a higher value means that the metric has a lesser impact on the objective, + while a lower value means that it has a greater impact. +TARGET_TRADE_AMOUNT variable sets the minimum number of trades required to avoid penalties. + If the trade amount falls below this threshold, the penalty is applied. +""" + +import numpy as np +from pandas import DataFrame + +from freqtrade.constants import Config +from freqtrade.data.metrics import calculate_expectancy, calculate_max_drawdown +from freqtrade.optimize.hyperopt import IHyperOptLoss + + +# smaller numbers penalize drawdowns more severely +DRAWDOWN_MULT = 0.055 +# A very large number to use as a replacement for infinity +LARGE_NUMBER = 1e6 +# Target trade amount, if higher that TARGET_TRADE_AMOUNT - no penalty +TARGET_TRADE_AMOUNT = 50 +# Coefficient to adjust impact of expectancy +EXPECTANCY_CONST = 2.0 +# Coefficient to adjust profit factor impact +PF_CONST = 1.0 +# Coefficient to adjust winrate impact +WINRATE_CONST = 1.2 + + +class MultiMetricHyperOptLoss(IHyperOptLoss): + @staticmethod + def hyperopt_loss_function( + results: DataFrame, + trade_count: int, + config: Config, + **kwargs, + ) -> float: + total_profit = results["profit_abs"].sum() + + # Calculate profit factor + winning_profit = results.loc[results["profit_abs"] > 0, "profit_abs"].sum() + losing_profit = results.loc[results["profit_abs"] < 0, "profit_abs"].sum() + profit_factor = winning_profit / (abs(losing_profit) + 1e-6) + log_profit_factor = np.log(profit_factor + PF_CONST) + + # Calculate expectancy + expectancy, expectancy_ratio = calculate_expectancy(results) + if expectancy_ratio > 10: + log_expectancy_ratio = np.log(1.01) + else: + log_expectancy_ratio = np.log(expectancy_ratio + EXPECTANCY_CONST) + + # Calculate winrate + winning_trades = results.loc[results["profit_abs"] > 0] + winrate = len(winning_trades) / len(results) + log_winrate_coef = np.log(WINRATE_CONST + winrate) + + # Calculate drawdown + try: + drawdown = calculate_max_drawdown( + results, starting_balance=config["dry_run_wallet"], value_col="profit_abs" + ) + relative_account_drawdown = drawdown.relative_account_drawdown + except ValueError: + relative_account_drawdown = 0 + + # Trade Count Penalty + trade_count_penalty = 1.0 # Default: no penalty + if trade_count < TARGET_TRADE_AMOUNT: + trade_count_penalty = 1 - (abs(trade_count - TARGET_TRADE_AMOUNT) / TARGET_TRADE_AMOUNT) + trade_count_penalty = max(trade_count_penalty, 0.1) + + profit_draw_function = total_profit - (relative_account_drawdown * total_profit) * ( + 1 - DRAWDOWN_MULT + ) + + return -1 * ( + profit_draw_function + * log_profit_factor + * log_expectancy_ratio + * log_winrate_coef + * trade_count_penalty + ) diff --git a/freqtrade/optimize/hyperopt_loss/hyperopt_loss_short_trade_dur.py b/freqtrade/optimize/hyperopt_loss/hyperopt_loss_short_trade_dur.py index 12565f10e..6c6e0a8cb 100644 --- a/freqtrade/optimize/hyperopt_loss/hyperopt_loss_short_trade_dur.py +++ b/freqtrade/optimize/hyperopt_loss/hyperopt_loss_short_trade_dur.py @@ -53,4 +53,5 @@ class ShortTradeDurHyperOptLoss(IHyperOptLoss): # Create an alias for This to allow the legacy Method to work as well. -DefaultHyperOptLoss = ShortTradeDurHyperOptLoss +class DefaultHyperOptLoss(ShortTradeDurHyperOptLoss): + pass diff --git a/freqtrade/optimize/hyperopt_loss_interface.py b/freqtrade/optimize/hyperopt_loss_interface.py index 39457b753..a48fee731 100644 --- a/freqtrade/optimize/hyperopt_loss_interface.py +++ b/freqtrade/optimize/hyperopt_loss_interface.py @@ -5,7 +5,7 @@ This module defines the interface for the loss-function for hyperopt from abc import ABC, abstractmethod from datetime import datetime -from typing import Any, Dict +from typing import Any from pandas import DataFrame @@ -29,8 +29,8 @@ class IHyperOptLoss(ABC): 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: """ diff --git a/freqtrade/optimize/hyperopt_output.py b/freqtrade/optimize/hyperopt_output.py index c83583d72..c30715046 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, List, Optional +from typing import Any, Optional from rich.align import Align from rich.console import Console @@ -14,7 +14,7 @@ from freqtrade.util import fmt_coin class HyperoptOutput: def __init__(self, streaming=False) -> None: - self._results: List[Any] = [] + self._results: list[Any] = [] self._streaming = streaming self.__init_table() diff --git a/freqtrade/optimize/hyperopt_tools.py b/freqtrade/optimize/hyperopt_tools.py index 975338cd5..66e194510 100644 --- a/freqtrade/optimize/hyperopt_tools.py +++ b/freqtrade/optimize/hyperopt_tools.py @@ -1,8 +1,9 @@ import logging +from collections.abc import Iterator from copy import deepcopy from datetime import datetime, timezone from pathlib import Path -from typing import Any, Dict, Iterator, List, Optional, Tuple +from typing import Any, Optional import numpy as np import rapidjson @@ -83,7 +84,7 @@ class HyperoptTools: ) @staticmethod - def load_params(filename: Path) -> Dict: + def load_params(filename: Path) -> dict: """ Load parameters from file """ @@ -92,7 +93,7 @@ class HyperoptTools: return params @staticmethod - def try_export_params(config: Config, strategy_name: str, params: Dict): + def try_export_params(config: Config, strategy_name: str, params: dict): if params.get(FTHYPT_FILEVERSION, 1) >= 2 and not config.get("disableparamexport", False): # Export parameters ... fn = HyperoptTools.get_strategy_filename(config, strategy_name) @@ -113,7 +114,7 @@ class HyperoptTools: return any(s in config["spaces"] for s in [space, "all", "default"]) @staticmethod - def _read_results(results_file: Path, batch_size: int = 10) -> Iterator[List[Any]]: + def _read_results(results_file: Path, batch_size: int = 10) -> Iterator[list[Any]]: """ Stream hyperopt results from file """ @@ -143,7 +144,7 @@ class HyperoptTools: return False @staticmethod - def load_filtered_results(results_file: Path, config: Config) -> Tuple[List, int]: + def load_filtered_results(results_file: Path, config: Config) -> tuple[list, int]: filteroptions = { "only_best": config.get("hyperopt_list_best", False), "only_profitable": config.get("hyperopt_list_profitable", False), @@ -204,7 +205,7 @@ class HyperoptTools: print(f"\n{header_str}:\n\n{explanation_str}\n") if print_json: - result_dict: Dict = {} + result_dict: dict = {} for s in [ "buy", "sell", @@ -256,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: Optional[dict] = None ) -> None: if space in params or (non_optimized and space in non_optimized): space_params = HyperoptTools._space_params(params, space, 5) @@ -298,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: Optional[int] = None) -> dict: d = params.get(space) if d: # Round floats to `r` digits after the decimal point if requested @@ -328,7 +329,7 @@ class HyperoptTools: return bool(results["loss"] < current_best_loss) @staticmethod - def format_results_explanation_string(results_metrics: Dict, stake_currency: str) -> str: + def format_results_explanation_string(results_metrics: dict, stake_currency: str) -> str: """ Return the formatted results explanation in a string """ diff --git a/freqtrade/optimize/optimize_reports/bt_output.py b/freqtrade/optimize/optimize_reports/bt_output.py index d509b75fb..a1dd73358 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, Dict, List, Literal, Union +from typing import Any, Literal, Union from freqtrade.constants import UNLIMITED_STAKE_AMOUNT, Config from freqtrade.ft_types import BacktestResultType @@ -10,7 +10,7 @@ from freqtrade.util import decimals_per_coin, fmt_coin, print_rich_table logger = logging.getLogger(__name__) -def _get_line_floatfmt(stake_currency: str) -> List[str]: +def _get_line_floatfmt(stake_currency: str) -> list[str]: """ Generate floatformat (goes in line with _generate_result_line()) """ @@ -18,8 +18,8 @@ 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" -) -> List[str]: + first_column: Union[str, list[str]], stake_currency: str, direction: str = "Trades" +) -> list[str]: """ Generate header lines (goes in line with _generate_result_line()) """ @@ -45,7 +45,7 @@ def generate_wins_draws_losses(wins, draws, losses): def text_table_bt_results( - pair_results: List[Dict[str, Any]], stake_currency: str, title: str + pair_results: list[dict[str, Any]], stake_currency: str, title: str ) -> None: """ Generates and returns a text table for the given backtest data and the results dataframe @@ -73,7 +73,7 @@ def text_table_bt_results( def text_table_tags( tag_type: Literal["enter_tag", "exit_tag", "mix_tag"], - tag_results: List[Dict[str, Any]], + tag_results: list[dict[str, Any]], stake_currency: str, ) -> None: """ @@ -123,7 +123,7 @@ def text_table_tags( def text_table_periodic_breakdown( - days_breakdown_stats: List[Dict[str, Any]], stake_currency: str, period: str + days_breakdown_stats: list[dict[str, Any]], stake_currency: str, period: str ) -> None: """ Generate small table with Backtest results by days @@ -191,7 +191,7 @@ def text_table_strategy(strategy_results, stake_currency: str, title: str): print_rich_table(output, headers, summary=title) -def text_table_add_metrics(strat_results: Dict) -> None: +def text_table_add_metrics(strat_results: dict) -> None: if len(strat_results["trades"]) > 0: best_trade = max(strat_results["trades"], key=lambda x: x["profit_ratio"]) worst_trade = min(strat_results["trades"], key=lambda x: x["profit_ratio"]) @@ -411,7 +411,7 @@ def text_table_add_metrics(strat_results: Dict) -> None: print(message) -def _show_tag_subresults(results: Dict[str, Any], stake_currency: str): +def _show_tag_subresults(results: dict[str, Any], stake_currency: str): """ Print tag subresults (enter_tag, exit_reason_summary, mix_tag_stats) """ @@ -426,7 +426,7 @@ def _show_tag_subresults(results: Dict[str, Any], stake_currency: str): def show_backtest_result( - strategy: str, results: Dict[str, Any], stake_currency: str, backtest_breakdown: List[str] + strategy: str, results: dict[str, Any], stake_currency: str, backtest_breakdown: list[str] ): """ Print results for one strategy diff --git a/freqtrade/optimize/optimize_reports/bt_storage.py b/freqtrade/optimize/optimize_reports/bt_storage.py index 9766bcd98..9633d9c18 100644 --- a/freqtrade/optimize/optimize_reports/bt_storage.py +++ b/freqtrade/optimize/optimize_reports/bt_storage.py @@ -1,6 +1,6 @@ import logging from pathlib import Path -from typing import Dict, Optional +from typing import Optional from pandas import DataFrame @@ -70,7 +70,7 @@ def store_backtest_stats( def _store_backtest_analysis_data( - recordfilename: Path, data: Dict[str, Dict], dtappendix: str, name: str + recordfilename: Path, data: dict[str, dict], dtappendix: str, name: str ) -> Path: """ Stores backtest trade candles for analysis @@ -91,9 +91,9 @@ def _store_backtest_analysis_data( def store_backtest_analysis_results( recordfilename: Path, - candles: Dict[str, Dict], - trades: Dict[str, Dict], - exited: Dict[str, Dict], + candles: dict[str, dict], + trades: dict[str, dict], + exited: dict[str, dict], dtappendix: str, ) -> None: _store_backtest_analysis_data(recordfilename, candles, dtappendix, "signals") diff --git a/freqtrade/optimize/optimize_reports/optimize_reports.py b/freqtrade/optimize/optimize_reports/optimize_reports.py index 1168f0c2f..9650bdde6 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, Dict, List, Literal, Tuple, Union +from typing import Any, Literal, Union import numpy as np from pandas import DataFrame, Series, concat, to_datetime @@ -25,8 +25,8 @@ logger = logging.getLogger(__name__) def generate_trade_signal_candles( - preprocessed_df: Dict[str, DataFrame], bt_results: Dict[str, Any], date_col: str -) -> Dict[str, DataFrame]: + preprocessed_df: dict[str, DataFrame], bt_results: dict[str, Any], date_col: str +) -> dict[str, DataFrame]: signal_candles_only = {} for pair in preprocessed_df.keys(): signal_candles_only_df = DataFrame() @@ -48,8 +48,8 @@ def generate_trade_signal_candles( def generate_rejected_signals( - preprocessed_df: Dict[str, DataFrame], rejected_dict: Dict[str, DataFrame] -) -> Dict[str, DataFrame]: + preprocessed_df: dict[str, DataFrame], rejected_dict: dict[str, DataFrame] +) -> dict[str, DataFrame]: rejected_candles_only = {} for pair, signals in rejected_dict.items(): rejected_signals_only_df = DataFrame() @@ -69,8 +69,8 @@ def generate_rejected_signals( def _generate_result_line( - result: DataFrame, starting_balance: int, first_column: Union[str, List[str]] -) -> Dict: + result: DataFrame, starting_balance: int, first_column: Union[str, list[str]] +) -> dict: """ Generate one result dict, with "first_column" as key. """ @@ -109,12 +109,12 @@ def _generate_result_line( def generate_pair_metrics( - pairlist: List[str], + pairlist: list[str], stake_currency: str, starting_balance: int, results: DataFrame, skip_nan: bool = False, -) -> List[Dict]: +) -> list[dict]: """ Generates and returns a list for the given backtest data and the results dataframe :param pairlist: Pairlist used @@ -143,11 +143,11 @@ def generate_pair_metrics( def generate_tag_metrics( - tag_type: Union[Literal["enter_tag", "exit_reason"], List[Literal["enter_tag", "exit_reason"]]], + tag_type: Union[Literal["enter_tag", "exit_reason"], list[Literal["enter_tag", "exit_reason"]]], starting_balance: int, results: DataFrame, skip_nan: bool = False, -) -> List[Dict]: +) -> list[dict]: """ Generates and returns a list of metrics for the given tag trades and the results dataframe :param starting_balance: Starting balance @@ -177,7 +177,7 @@ def generate_tag_metrics( return [] -def generate_strategy_comparison(bt_stats: Dict) -> List[Dict]: +def generate_strategy_comparison(bt_stats: dict) -> list[dict]: """ Generate summary per strategy :param bt_stats: Dict of containing results for all strategies @@ -208,8 +208,8 @@ def _get_resample_from_period(period: str) -> str: def generate_periodic_breakdown_stats( - trade_list: Union[List, DataFrame], period: str -) -> List[Dict[str, Any]]: + trade_list: Union[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: return [] @@ -237,14 +237,14 @@ def generate_periodic_breakdown_stats( return stats -def generate_all_periodic_breakdown_stats(trade_list: List) -> Dict[str, List]: +def generate_all_periodic_breakdown_stats(trade_list: list) -> dict[str, list]: result = {} for period in BACKTEST_BREAKDOWNS: result[period] = generate_periodic_breakdown_stats(trade_list, period) return result -def calc_streak(dataframe: DataFrame) -> Tuple[int, int]: +def calc_streak(dataframe: DataFrame) -> tuple[int, int]: """ Calculate consecutive win and loss streaks :param dataframe: Dataframe containing the trades dataframe, with profit_ratio column @@ -261,7 +261,7 @@ def calc_streak(dataframe: DataFrame) -> Tuple[int, int]: return cons_wins, cons_losses -def generate_trading_stats(results: DataFrame) -> Dict[str, Any]: +def generate_trading_stats(results: DataFrame) -> dict[str, Any]: """Generate overall trade statistics""" if len(results) == 0: return { @@ -313,7 +313,7 @@ def generate_trading_stats(results: DataFrame) -> Dict[str, Any]: } -def generate_daily_stats(results: DataFrame) -> Dict[str, Any]: +def generate_daily_stats(results: DataFrame) -> dict[str, Any]: """Generate daily statistics""" if len(results) == 0: return { @@ -350,14 +350,14 @@ def generate_daily_stats(results: DataFrame) -> Dict[str, Any]: def generate_strategy_stats( - pairlist: List[str], + pairlist: list[str], strategy: str, - content: Dict[str, Any], + content: dict[str, Any], min_date: datetime, max_date: datetime, market_change: float, is_hyperopt: bool = False, -) -> Dict[str, Any]: +) -> dict[str, Any]: """ :param pairlist: List of pairs to backtest :param strategy: Strategy name @@ -368,7 +368,7 @@ def generate_strategy_stats( :param market_change: float indicating the market change :return: Dictionary containing results per strategy and a strategy summary. """ - results: Dict[str, DataFrame] = content["results"] + results: dict[str, DataFrame] = content["results"] if not isinstance(results, DataFrame): return {} config = content["config"] @@ -558,8 +558,8 @@ def generate_strategy_stats( def generate_backtest_stats( - btdata: Dict[str, DataFrame], - all_results: Dict[str, Dict[str, Union[DataFrame, Dict]]], + btdata: dict[str, DataFrame], + all_results: dict[str, dict[str, Union[DataFrame, dict]]], min_date: datetime, max_date: datetime, ) -> BacktestResultType: diff --git a/freqtrade/persistence/custom_data.py b/freqtrade/persistence/custom_data.py index 5b37a50eb..3a2e700fb 100644 --- a/freqtrade/persistence/custom_data.py +++ b/freqtrade/persistence/custom_data.py @@ -1,7 +1,8 @@ import json import logging +from collections.abc import Sequence from datetime import datetime -from typing import Any, ClassVar, List, Optional, Sequence +from typing import Any, ClassVar, Optional from sqlalchemy import DateTime, ForeignKey, Integer, String, Text, UniqueConstraint, select from sqlalchemy.orm import Mapped, mapped_column, relationship @@ -85,7 +86,7 @@ class CustomDataWrapper: """ use_db = True - custom_data: List[_CustomData] = [] + custom_data: list[_CustomData] = [] unserialized_types = ["bool", "float", "int", "str"] @staticmethod @@ -116,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: Optional[str] = None) -> list[_CustomData]: if CustomDataWrapper.use_db: filters = [ _CustomData.ft_trade_id == trade_id, diff --git a/freqtrade/persistence/migrations.py b/freqtrade/persistence/migrations.py index 2150d76bc..1dd69493f 100644 --- a/freqtrade/persistence/migrations.py +++ b/freqtrade/persistence/migrations.py @@ -1,5 +1,5 @@ import logging -from typing import List, Optional +from typing import Optional from sqlalchemy import inspect, select, text, update @@ -10,19 +10,19 @@ from freqtrade.persistence.trade_model import Order, Trade logger = logging.getLogger(__name__) -def get_table_names_for_table(inspector, tabletype) -> List[str]: +def get_table_names_for_table(inspector, tabletype) -> list[str]: return [t for t in inspector.get_table_names() if t.startswith(tabletype)] -def has_column(columns: List, searchname: str) -> bool: +def has_column(columns: list, searchname: str) -> bool: return len(list(filter(lambda x: x["name"] == searchname, columns))) == 1 -def get_column_def(columns: List, column: str, default: str) -> str: +def get_column_def(columns: list, column: str, default: str) -> str: return default if not has_column(columns, column) else column -def get_backup_name(tabs: List[str], backup_prefix: str): +def get_backup_name(tabs: list[str], backup_prefix: str): table_back_name = backup_prefix for i, table_back_name in enumerate(tabs): table_back_name = f"{backup_prefix}{i}" @@ -77,9 +77,9 @@ def migrate_trades_and_orders_table( inspector, engine, trade_back_name: str, - cols: List, + cols: list, order_back_name: str, - cols_order: List, + cols_order: list, ): base_currency = get_column_def(cols, "base_currency", "null") stake_currency = get_column_def(cols, "stake_currency", "null") @@ -230,7 +230,7 @@ def drop_orders_table(engine, table_back_name: str): connection.execute(text("drop table orders")) -def migrate_orders_table(engine, table_back_name: str, cols_order: List): +def migrate_orders_table(engine, table_back_name: str, cols_order: list): ft_fee_base = get_column_def(cols_order, "ft_fee_base", "null") average = get_column_def(cols_order, "average", "null") stop_price = get_column_def(cols_order, "stop_price", "null") @@ -262,7 +262,7 @@ def migrate_orders_table(engine, table_back_name: str, cols_order: List): ) -def migrate_pairlocks_table(decl_base, inspector, engine, pairlock_back_name: str, cols: List): +def migrate_pairlocks_table(decl_base, inspector, engine, pairlock_back_name: str, cols: list): # Schema migration necessary with engine.begin() as connection: connection.execute(text(f"alter table pairlocks rename to {pairlock_back_name}")) diff --git a/freqtrade/persistence/models.py b/freqtrade/persistence/models.py index d5d5fd144..87ba3a2d2 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, Dict, Final, Optional +from typing import Any, Final, Optional from sqlalchemy import create_engine, inspect from sqlalchemy.exc import NoSuchModuleError @@ -51,7 +51,7 @@ def init_db(db_url: str) -> None: :param db_url: Database to use :return: None """ - kwargs: Dict[str, Any] = {} + kwargs: dict[str, Any] = {} if db_url == "sqlite:///": raise OperationalException( diff --git a/freqtrade/persistence/pairlock.py b/freqtrade/persistence/pairlock.py index 2ea2991c2..caec2c78a 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, Dict, Optional +from typing import Any, ClassVar, Optional from sqlalchemy import ScalarResult, String, or_, select from sqlalchemy.orm import Mapped, mapped_column @@ -64,7 +64,7 @@ class PairLock(ModelBase): def get_all_locks() -> ScalarResult["PairLock"]: return PairLock.session.scalars(select(PairLock)) - def to_json(self) -> Dict[str, Any]: + def to_json(self) -> dict[str, Any]: return { "id": self.id, "pair": self.pair, diff --git a/freqtrade/persistence/pairlock_middleware.py b/freqtrade/persistence/pairlock_middleware.py index 616906658..a30592421 100644 --- a/freqtrade/persistence/pairlock_middleware.py +++ b/freqtrade/persistence/pairlock_middleware.py @@ -1,6 +1,7 @@ import logging +from collections.abc import Sequence from datetime import datetime, timezone -from typing import List, Optional, Sequence +from typing import Optional from sqlalchemy import select @@ -19,7 +20,7 @@ class PairLocks: """ use_db = True - locks: List[PairLock] = [] + locks: list[PairLock] = [] timeframe: str = "" diff --git a/freqtrade/persistence/trade_model.py b/freqtrade/persistence/trade_model.py index 698e9721c..f765b9865 100644 --- a/freqtrade/persistence/trade_model.py +++ b/freqtrade/persistence/trade_model.py @@ -4,10 +4,11 @@ This module contains the class to persist trades into SQLite import logging from collections import defaultdict +from collections.abc import Sequence from dataclasses import dataclass from datetime import datetime, timedelta, timezone from math import isclose -from typing import Any, ClassVar, Dict, List, Optional, Sequence, cast +from typing import Any, ClassVar, Optional, cast from sqlalchemy import ( Enum, @@ -215,8 +216,8 @@ class Order(ModelBase): ) self.order_update_date = datetime.now(timezone.utc) - def to_ccxt_object(self, stopPriceName: str = "stopPrice") -> Dict[str, Any]: - order: Dict[str, Any] = { + def to_ccxt_object(self, stopPriceName: str = "stopPrice") -> dict[str, Any]: + order: dict[str, Any] = { "id": self.order_id, "symbol": self.ft_pair, "price": self.price, @@ -243,7 +244,7 @@ class Order(ModelBase): return order - def to_json(self, entry_side: str, minified: bool = False) -> Dict[str, Any]: + def to_json(self, entry_side: str, minified: bool = False) -> dict[str, Any]: """ :param minified: If True, only return a subset of the data is returned. Only used for backtesting. @@ -308,7 +309,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: dict[str, Any]): """ Get all non-closed orders - useful when trying to batch-update orders """ @@ -327,7 +328,7 @@ class Order(ModelBase): @classmethod def parse_from_ccxt_object( cls, - order: Dict[str, Any], + order: dict[str, Any], pair: str, side: str, amount: Optional[float] = None, @@ -373,17 +374,18 @@ class LocalTrade: use_db: bool = False # Trades container for backtesting - bt_trades: List["LocalTrade"] = [] - bt_trades_open: List["LocalTrade"] = [] + bt_trades: list["LocalTrade"] = [] + bt_trades_open: list["LocalTrade"] = [] # Copy of trades_open - but indexed by pair - bt_trades_open_pp: Dict[str, List["LocalTrade"]] = defaultdict(list) + 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 id: int = 0 - orders: List[Order] = [] + orders: list[Order] = [] exchange: str = "" pair: str = "" @@ -569,7 +571,7 @@ class LocalTrade: return "" @property - def open_orders(self) -> List[Order]: + def open_orders(self) -> list[Order]: """ All open orders for this trade excluding stoploss orders """ @@ -586,7 +588,7 @@ class LocalTrade: return len(open_orders_wo_sl) > 0 @property - def open_sl_orders(self) -> List[Order]: + def open_sl_orders(self) -> list[Order]: """ All open stoploss orders for this trade """ @@ -603,14 +605,14 @@ class LocalTrade: return len(open_sl_orders) > 0 @property - def sl_orders(self) -> List[Order]: + def sl_orders(self) -> list[Order]: """ All stoploss orders for this trade """ return [o for o in self.orders if o.ft_order_side in ["stoploss"]] @property - def open_orders_ids(self) -> List[str]: + def open_orders_ids(self) -> list[str]: open_orders_ids_wo_sl = [ oo.order_id for oo in self.open_orders if oo.ft_order_side not in ["stoploss"] ] @@ -637,7 +639,7 @@ class LocalTrade: f"open_rate={self.open_rate:.8f}, open_since={open_since})" ) - def to_json(self, minified: bool = False) -> Dict[str, Any]: + def to_json(self, minified: bool = False) -> dict[str, Any]: """ :param minified: If True, only return a subset of the data is returned. Only used for backtesting. @@ -746,6 +748,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: @@ -760,7 +763,7 @@ class LocalTrade: Method you should use to set self.liquidation price. Assures stop_loss is not passed the liquidation price """ - if not liquidation_price: + if liquidation_price is None: return self.liquidation_price = liquidation_price @@ -956,7 +959,7 @@ class LocalTrade: else: return False - def update_order(self, order: Dict) -> None: + def update_order(self, order: dict) -> None: Order.update_orders(self.orders, order) @property @@ -1280,7 +1283,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: Optional[str] = None) -> list["Order"]: """ Finds filled orders for this order side. Will not return open orders which already partially filled. @@ -1296,7 +1299,7 @@ class LocalTrade: and o.status in NON_OPEN_EXCHANGE_STATES ] - def select_filled_or_open_orders(self) -> List["Order"]: + def select_filled_or_open_orders(self) -> list["Order"]: """ Finds filled or open orders :param order_side: Side of the order (either 'buy', 'sell', or None) @@ -1341,7 +1344,7 @@ class LocalTrade: return data[0] return None - def get_all_custom_data(self) -> List[_CustomData]: + def get_all_custom_data(self) -> list[_CustomData]: """ Get all custom data for this trade """ @@ -1399,7 +1402,7 @@ class LocalTrade: is_open: Optional[bool] = None, open_date: Optional[datetime] = None, close_date: Optional[datetime] = None, - ) -> List["LocalTrade"]: + ) -> list["LocalTrade"]: """ Helper function to query Trades. Returns a List of trades, filtered on the parameters given. @@ -1441,6 +1444,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 @@ -1450,6 +1458,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) @@ -1458,9 +1467,12 @@ 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]: + def get_open_trades() -> list[Any]: """ Retrieve open trades """ @@ -1609,10 +1621,10 @@ class Trade(ModelBase, LocalTrade): id: Mapped[int] = mapped_column(Integer, primary_key=True) # type: ignore - orders: Mapped[List[Order]] = relationship( + orders: Mapped[list[Order]] = relationship( "Order", order_by="Order.id", cascade="all, delete-orphan", lazy="selectin", innerjoin=True ) # type: ignore - custom_data: Mapped[List[_CustomData]] = relationship( + custom_data: Mapped[list[_CustomData]] = relationship( "_CustomData", cascade="all, delete-orphan", lazy="raise" ) @@ -1759,7 +1771,7 @@ class Trade(ModelBase, LocalTrade): is_open: Optional[bool] = None, open_date: Optional[datetime] = None, close_date: Optional[datetime] = None, - ) -> List["LocalTrade"]: + ) -> list["LocalTrade"]: """ Helper function to query Trades.j Returns a List of trades, filtered on the parameters given. @@ -1778,7 +1790,7 @@ class Trade(ModelBase, LocalTrade): trade_filter.append(Trade.close_date > close_date) if is_open is not None: trade_filter.append(Trade.is_open.is_(is_open)) - return cast(List[LocalTrade], Trade.get_trades(trade_filter).all()) + return cast(list[LocalTrade], Trade.get_trades(trade_filter).all()) else: return LocalTrade.get_trades_proxy( pair=pair, is_open=is_open, open_date=open_date, close_date=close_date @@ -1886,12 +1898,12 @@ class Trade(ModelBase, LocalTrade): return total_open_stake_amount or 0 @staticmethod - def get_overall_performance(minutes=None) -> List[Dict[str, Any]]: + def get_overall_performance(minutes=None) -> list[dict[str, Any]]: """ Returns List of dicts containing all Trades, including profit and trade count NOTE: Not supported in Backtesting. """ - filters: List = [Trade.is_open.is_(False)] + filters: list = [Trade.is_open.is_(False)] if minutes: start_date = datetime.now(timezone.utc) - timedelta(minutes=minutes) filters.append(Trade.close_date >= start_date) @@ -1921,14 +1933,14 @@ class Trade(ModelBase, LocalTrade): ] @staticmethod - def get_enter_tag_performance(pair: Optional[str]) -> List[Dict[str, Any]]: + def get_enter_tag_performance(pair: Optional[str]) -> 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 NOTE: Not supported in Backtesting. """ - filters: List = [Trade.is_open.is_(False)] + filters: list = [Trade.is_open.is_(False)] if pair is not None: filters.append(Trade.pair == pair) @@ -1956,14 +1968,14 @@ class Trade(ModelBase, LocalTrade): ] @staticmethod - def get_exit_reason_performance(pair: Optional[str]) -> List[Dict[str, Any]]: + def get_exit_reason_performance(pair: Optional[str]) -> 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 NOTE: Not supported in Backtesting. """ - filters: List = [Trade.is_open.is_(False)] + filters: list = [Trade.is_open.is_(False)] if pair is not None: filters.append(Trade.pair == pair) sell_tag_perf = Trade.session.execute( @@ -1990,14 +2002,14 @@ class Trade(ModelBase, LocalTrade): ] @staticmethod - def get_mix_tag_performance(pair: Optional[str]) -> List[Dict[str, Any]]: + def get_mix_tag_performance(pair: Optional[str]) -> 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 NOTE: Not supported in Backtesting. """ - filters: List = [Trade.is_open.is_(False)] + filters: list = [Trade.is_open.is_(False)] if pair is not None: filters.append(Trade.pair == pair) mix_tag_perf = Trade.session.execute( @@ -2014,7 +2026,7 @@ class Trade(ModelBase, LocalTrade): .order_by(desc("profit_sum_abs")) ).all() - resp: List[Dict] = [] + resp: list[dict] = [] for _, enter_tag, exit_reason, profit, profit_abs, count in mix_tag_perf: enter_tag = enter_tag if enter_tag is not None else "Other" exit_reason = exit_reason if exit_reason is not None else "Other" @@ -2053,7 +2065,7 @@ class Trade(ModelBase, LocalTrade): NOTE: Not supported in Backtesting. :returns: Tuple containing (pair, profit_sum) """ - filters: List = [Trade.is_open.is_(False)] + filters: list = [Trade.is_open.is_(False)] if start_date: filters.append(Trade.close_date >= start_date) diff --git a/freqtrade/plot/plotting.py b/freqtrade/plot/plotting.py index de0910732..8ce851476 100644 --- a/freqtrade/plot/plotting.py +++ b/freqtrade/plot/plotting.py @@ -1,7 +1,7 @@ import logging from datetime import datetime, timezone from pathlib import Path -from typing import Dict, List, Optional +from typing import Optional import pandas as pd @@ -43,7 +43,7 @@ except ImportError: exit(1) -def init_plotscript(config, markets: List, startup_candles: int = 0): +def init_plotscript(config, markets: list, startup_candles: int = 0): """ Initialize objects needed for plotting :return: Dict with candle (OHLCV) data, trades and pairs @@ -103,7 +103,7 @@ def init_plotscript(config, markets: List, startup_candles: int = 0): } -def add_indicators(fig, row, indicators: Dict[str, Dict], data: pd.DataFrame) -> make_subplots: +def add_indicators(fig, row, indicators: dict[str, dict], data: pd.DataFrame) -> make_subplots: """ Generate all the indicators selected by the user for a specific row, based on the configuration :param fig: Plot figure to append to @@ -301,8 +301,8 @@ def plot_trades(fig, trades: pd.DataFrame) -> make_subplots: def create_plotconfig( - indicators1: List[str], indicators2: List[str], plot_config: Dict[str, Dict] -) -> Dict[str, Dict]: + indicators1: list[str], indicators2: list[str], plot_config: dict[str, dict] +) -> dict[str, dict]: """ Combines indicators 1 and indicators 2 into plot_config if necessary :param indicators1: List containing Main plot indicators @@ -434,9 +434,9 @@ def generate_candlestick_graph( data: pd.DataFrame, trades: Optional[pd.DataFrame] = None, *, - indicators1: Optional[List[str]] = None, - indicators2: Optional[List[str]] = None, - plot_config: Optional[Dict[str, Dict]] = None, + indicators1: Optional[list[str]] = None, + indicators2: Optional[list[str]] = None, + plot_config: Optional[dict[str, dict]] = None, ) -> go.Figure: """ Generate the graph from the data generated by Backtesting or from DB @@ -521,7 +521,7 @@ def generate_candlestick_graph( def generate_profit_graph( pairs: str, - data: Dict[str, pd.DataFrame], + data: dict[str, pd.DataFrame], trades: pd.DataFrame, timeframe: str, stake_currency: str, diff --git a/freqtrade/plugins/pairlist/AgeFilter.py b/freqtrade/plugins/pairlist/AgeFilter.py index 0c691346a..cc89e3421 100644 --- a/freqtrade/plugins/pairlist/AgeFilter.py +++ b/freqtrade/plugins/pairlist/AgeFilter.py @@ -5,7 +5,7 @@ Minimum age (days listed) pair list filter import logging from copy import deepcopy from datetime import timedelta -from typing import Dict, List, Optional +from typing import Optional from pandas import DataFrame @@ -27,7 +27,7 @@ class AgeFilter(IPairList): super().__init__(*args, **kwargs) # Checked symbols cache (dictionary of ticker symbol => timestamp) - self._symbolsChecked: Dict[str, int] = {} + self._symbolsChecked: dict[str, int] = {} self._symbolsCheckFailed = PeriodicCache(maxsize=1000, ttl=86_400) self._min_days_listed = self._pairlistconfig.get("min_days_listed", 10) @@ -78,7 +78,7 @@ class AgeFilter(IPairList): return "Filter pairs by age (days listed)." @staticmethod - def available_parameters() -> Dict[str, PairlistParameter]: + def available_parameters() -> dict[str, PairlistParameter]: return { "min_days_listed": { "type": "number", @@ -94,7 +94,7 @@ class AgeFilter(IPairList): }, } - def filter_pairlist(self, pairlist: List[str], tickers: Tickers) -> List[str]: + def filter_pairlist(self, pairlist: list[str], tickers: Tickers) -> list[str]: """ :param pairlist: pairlist to filter or sort :param tickers: Tickers (from exchange.get_tickers). May be cached. diff --git a/freqtrade/plugins/pairlist/FullTradesFilter.py b/freqtrade/plugins/pairlist/FullTradesFilter.py index ba7590ae1..c9eecdb49 100644 --- a/freqtrade/plugins/pairlist/FullTradesFilter.py +++ b/freqtrade/plugins/pairlist/FullTradesFilter.py @@ -3,7 +3,6 @@ Full trade slots pair list filter """ import logging -from typing import List from freqtrade.exchange.exchange_types import Tickers from freqtrade.persistence import Trade @@ -35,7 +34,7 @@ class FullTradesFilter(IPairList): def description() -> str: return "Shrink whitelist when trade slots are full." - def filter_pairlist(self, pairlist: List[str], tickers: Tickers) -> List[str]: + def filter_pairlist(self, pairlist: list[str], tickers: Tickers) -> list[str]: """ Filters and sorts pairlist and returns the allowlist again. Called on each bot iteration - please use internal caching if necessary diff --git a/freqtrade/plugins/pairlist/IPairList.py b/freqtrade/plugins/pairlist/IPairList.py index 6a4ad32fb..a1ce4dc36 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, Dict, List, Literal, Optional, TypedDict, Union +from typing import Any, Literal, Optional, TypedDict, Union from freqtrade.constants import Config from freqtrade.exceptions import OperationalException @@ -36,12 +36,12 @@ class __StringPairlistParameter(__PairlistParameterBase): class __OptionPairlistParameter(__PairlistParameterBase): type: Literal["option"] default: Union[str, None] - options: List[str] + options: list[str] class __ListPairListParamenter(__PairlistParameterBase): type: Literal["list"] - default: Union[List[str], None] + default: Union[list[str], None] class __BoolPairlistParameter(__PairlistParameterBase): @@ -78,7 +78,7 @@ class IPairList(LoggingMixin, ABC): exchange: Exchange, pairlistmanager, config: Config, - pairlistconfig: Dict[str, Any], + pairlistconfig: dict[str, Any], pairlist_pos: int, ) -> None: """ @@ -126,7 +126,7 @@ class IPairList(LoggingMixin, ABC): return "" @staticmethod - def available_parameters() -> Dict[str, PairlistParameter]: + def available_parameters() -> dict[str, PairlistParameter]: """ Return parameters used by this Pairlist Handler, and their type contains a dictionary with the parameter name as key, and a dictionary @@ -136,7 +136,7 @@ class IPairList(LoggingMixin, ABC): return {} @staticmethod - def refresh_period_parameter() -> Dict[str, PairlistParameter]: + def refresh_period_parameter() -> dict[str, PairlistParameter]: return { "refresh_period": { "type": "number", @@ -166,7 +166,7 @@ class IPairList(LoggingMixin, ABC): """ raise NotImplementedError() - def gen_pairlist(self, tickers: Tickers) -> List[str]: + def gen_pairlist(self, tickers: Tickers) -> list[str]: """ Generate the pairlist. @@ -185,7 +185,7 @@ class IPairList(LoggingMixin, ABC): "at the first position in the list of Pairlist Handlers." ) - def filter_pairlist(self, pairlist: List[str], tickers: Tickers) -> List[str]: + def filter_pairlist(self, pairlist: list[str], tickers: Tickers) -> list[str]: """ Filters and sorts pairlist and returns the whitelist again. @@ -209,7 +209,7 @@ class IPairList(LoggingMixin, ABC): return pairlist - def verify_blacklist(self, pairlist: List[str], logmethod) -> List[str]: + def verify_blacklist(self, pairlist: list[str], logmethod) -> list[str]: """ Proxy method to verify_blacklist for easy access for child classes. :param pairlist: Pairlist to validate @@ -219,8 +219,8 @@ class IPairList(LoggingMixin, ABC): return self._pairlistmanager.verify_blacklist(pairlist, logmethod) def verify_whitelist( - self, pairlist: List[str], logmethod, keep_invalid: bool = False - ) -> List[str]: + self, pairlist: list[str], logmethod, keep_invalid: bool = False + ) -> list[str]: """ Proxy method to verify_whitelist for easy access for child classes. :param pairlist: Pairlist to validate @@ -230,7 +230,7 @@ class IPairList(LoggingMixin, ABC): """ return self._pairlistmanager.verify_whitelist(pairlist, logmethod, keep_invalid) - def _whitelist_for_active_markets(self, pairlist: List[str]) -> List[str]: + def _whitelist_for_active_markets(self, pairlist: list[str]) -> list[str]: """ Check available markets and remove pair from whitelist if necessary :param pairlist: the sorted list of pairs the user might want to trade @@ -243,7 +243,7 @@ class IPairList(LoggingMixin, ABC): "Markets not loaded. Make sure that exchange is initialized correctly." ) - sanitized_whitelist: List[str] = [] + sanitized_whitelist: list[str] = [] for pair in pairlist: # pair is not in the generated dynamic market or has the wrong stake currency if pair not in markets: diff --git a/freqtrade/plugins/pairlist/MarketCapPairList.py b/freqtrade/plugins/pairlist/MarketCapPairList.py index 3ca31fbf2..2394e910e 100644 --- a/freqtrade/plugins/pairlist/MarketCapPairList.py +++ b/freqtrade/plugins/pairlist/MarketCapPairList.py @@ -5,7 +5,6 @@ Provides dynamic pair list based on Market Cap """ import logging -from typing import Dict, List from cachetools import TTLCache @@ -83,7 +82,7 @@ class MarketCapPairList(IPairList): return "Provides pair list based on CoinGecko's market cap rank." @staticmethod - def available_parameters() -> Dict[str, PairlistParameter]: + def available_parameters() -> dict[str, PairlistParameter]: return { "number_assets": { "type": "number", @@ -114,7 +113,7 @@ class MarketCapPairList(IPairList): }, } - def gen_pairlist(self, tickers: Tickers) -> List[str]: + def gen_pairlist(self, tickers: Tickers) -> list[str]: """ Generate the pairlist :param tickers: Tickers (from exchange.get_tickers). May be cached. @@ -143,7 +142,7 @@ class MarketCapPairList(IPairList): return pairlist - def filter_pairlist(self, pairlist: List[str], tickers: Dict) -> List[str]: + def filter_pairlist(self, pairlist: list[str], tickers: dict) -> list[str]: """ Filters and sorts pairlist and returns the whitelist again. Called on each bot iteration - please use internal caching if necessary diff --git a/freqtrade/plugins/pairlist/OffsetFilter.py b/freqtrade/plugins/pairlist/OffsetFilter.py index f06ec411d..ca508ce87 100644 --- a/freqtrade/plugins/pairlist/OffsetFilter.py +++ b/freqtrade/plugins/pairlist/OffsetFilter.py @@ -3,7 +3,6 @@ Offset pair list filter """ import logging -from typing import Dict, List from freqtrade.exceptions import OperationalException from freqtrade.exchange.exchange_types import Tickers @@ -47,7 +46,7 @@ class OffsetFilter(IPairList): return "Offset pair list filter." @staticmethod - def available_parameters() -> Dict[str, PairlistParameter]: + def available_parameters() -> dict[str, PairlistParameter]: return { "offset": { "type": "number", @@ -63,7 +62,7 @@ class OffsetFilter(IPairList): }, } - def filter_pairlist(self, pairlist: List[str], tickers: Tickers) -> List[str]: + def filter_pairlist(self, pairlist: list[str], tickers: Tickers) -> list[str]: """ Filters and sorts pairlist and returns the whitelist again. Called on each bot iteration - please use internal caching if necessary diff --git a/freqtrade/plugins/pairlist/PercentChangePairList.py b/freqtrade/plugins/pairlist/PercentChangePairList.py index 411edbf26..b428da75a 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, Dict, List, Optional +from typing import Any, Optional from cachetools import TTLCache from pandas import DataFrame @@ -115,7 +115,7 @@ class PercentChangePairList(IPairList): return "Provides dynamic pair list based on percentage change." @staticmethod - def available_parameters() -> Dict[str, PairlistParameter]: + def available_parameters() -> dict[str, PairlistParameter]: return { "number_assets": { "type": "number", @@ -163,7 +163,7 @@ class PercentChangePairList(IPairList): }, } - def gen_pairlist(self, tickers: Tickers) -> List[str]: + def gen_pairlist(self, tickers: Tickers) -> list[str]: """ Generate the pairlist :param tickers: Tickers (from exchange.get_tickers). May be cached. @@ -204,7 +204,7 @@ class PercentChangePairList(IPairList): return pairlist - def filter_pairlist(self, pairlist: List[str], tickers: Dict) -> List[str]: + def filter_pairlist(self, pairlist: list[str], tickers: dict) -> list[str]: """ Filters and sorts pairlist and returns the whitelist again. Called on each bot iteration - please use internal caching if necessary @@ -212,7 +212,7 @@ class PercentChangePairList(IPairList): :param tickers: Tickers (from exchange.get_tickers). May be cached. :return: new whitelist """ - filtered_tickers: List[Dict[str, Any]] = [{"symbol": k} for k in pairlist] + filtered_tickers: list[dict[str, Any]] = [{"symbol": k} for k in pairlist] if self._use_range: # calculating using lookback_period self.fetch_percent_change_from_lookback_period(filtered_tickers) @@ -240,8 +240,8 @@ class PercentChangePairList(IPairList): return pairs def fetch_candles_for_lookback_period( - self, filtered_tickers: List[Dict[str, str]] - ) -> Dict[PairWithTimeframe, DataFrame]: + self, filtered_tickers: list[dict[str, str]] + ) -> dict[PairWithTimeframe, DataFrame]: since_ms = ( int( timeframe_to_prev_date( @@ -277,7 +277,7 @@ class PercentChangePairList(IPairList): candles = self._exchange.refresh_ohlcv_with_cache(needed_pairs, since_ms) return candles - def fetch_percent_change_from_lookback_period(self, filtered_tickers: List[Dict[str, Any]]): + def fetch_percent_change_from_lookback_period(self, filtered_tickers: list[dict[str, Any]]): # get lookback period in ms, for exchange ohlcv fetch candles = self.fetch_candles_for_lookback_period(filtered_tickers) @@ -301,7 +301,7 @@ class PercentChangePairList(IPairList): else: filtered_tickers[i]["percentage"] = 0 - def fetch_percent_change_from_tickers(self, filtered_tickers: List[Dict[str, Any]], tickers): + def fetch_percent_change_from_tickers(self, filtered_tickers: list[dict[str, Any]], tickers): for i, p in enumerate(filtered_tickers): # Filter out assets if not self._validate_pair( diff --git a/freqtrade/plugins/pairlist/PerformanceFilter.py b/freqtrade/plugins/pairlist/PerformanceFilter.py index 64f9529ed..06ab11c54 100644 --- a/freqtrade/plugins/pairlist/PerformanceFilter.py +++ b/freqtrade/plugins/pairlist/PerformanceFilter.py @@ -3,7 +3,6 @@ Performance pair list filter """ import logging -from typing import Dict, List import pandas as pd @@ -44,7 +43,7 @@ class PerformanceFilter(IPairList): return "Filter pairs by performance." @staticmethod - def available_parameters() -> Dict[str, PairlistParameter]: + def available_parameters() -> dict[str, PairlistParameter]: return { "minutes": { "type": "number", @@ -60,7 +59,7 @@ class PerformanceFilter(IPairList): }, } - def filter_pairlist(self, pairlist: List[str], tickers: Tickers) -> List[str]: + def filter_pairlist(self, pairlist: list[str], tickers: Tickers) -> list[str]: """ Filters and sorts pairlist and returns the allowlist again. Called on each bot iteration - please use internal caching if necessary diff --git a/freqtrade/plugins/pairlist/PriceFilter.py b/freqtrade/plugins/pairlist/PriceFilter.py index efea28683..f963b1247 100644 --- a/freqtrade/plugins/pairlist/PriceFilter.py +++ b/freqtrade/plugins/pairlist/PriceFilter.py @@ -3,7 +3,7 @@ Price pair list filter """ import logging -from typing import Dict, Optional +from typing import Optional from freqtrade.exceptions import OperationalException from freqtrade.exchange.exchange_types import Ticker @@ -71,7 +71,7 @@ class PriceFilter(IPairList): return "Filter pairs by price." @staticmethod - def available_parameters() -> Dict[str, PairlistParameter]: + def available_parameters() -> dict[str, PairlistParameter]: return { "low_price_ratio": { "type": "number", diff --git a/freqtrade/plugins/pairlist/ProducerPairList.py b/freqtrade/plugins/pairlist/ProducerPairList.py index b52dd46b9..e286b3560 100644 --- a/freqtrade/plugins/pairlist/ProducerPairList.py +++ b/freqtrade/plugins/pairlist/ProducerPairList.py @@ -5,7 +5,7 @@ Provides pair list from Leader data """ import logging -from typing import Dict, List, Optional +from typing import Optional from freqtrade.exceptions import OperationalException from freqtrade.exchange.exchange_types import Tickers @@ -64,7 +64,7 @@ class ProducerPairList(IPairList): return "Get a pairlist from an upstream bot." @staticmethod - def available_parameters() -> Dict[str, PairlistParameter]: + def available_parameters() -> dict[str, PairlistParameter]: return { "number_assets": { "type": "number", @@ -83,7 +83,7 @@ class ProducerPairList(IPairList): }, } - def _filter_pairlist(self, pairlist: Optional[List[str]]): + def _filter_pairlist(self, pairlist: Optional[list[str]]): upstream_pairlist = self._pairlistmanager._dataprovider.get_producer_pairs( self._producer_name ) @@ -97,7 +97,7 @@ class ProducerPairList(IPairList): return pairs - def gen_pairlist(self, tickers: Tickers) -> List[str]: + def gen_pairlist(self, tickers: Tickers) -> list[str]: """ Generate the pairlist :param tickers: Tickers (from exchange.get_tickers). May be cached. @@ -108,7 +108,7 @@ class ProducerPairList(IPairList): pairs = self._whitelist_for_active_markets(self.verify_whitelist(pairs, logger.info)) return pairs - def filter_pairlist(self, pairlist: List[str], tickers: Tickers) -> List[str]: + def filter_pairlist(self, pairlist: list[str], tickers: Tickers) -> list[str]: """ Filters and sorts pairlist and returns the whitelist again. Called on each bot iteration - please use internal caching if necessary diff --git a/freqtrade/plugins/pairlist/RemotePairList.py b/freqtrade/plugins/pairlist/RemotePairList.py index 8a28af123..a0817998f 100644 --- a/freqtrade/plugins/pairlist/RemotePairList.py +++ b/freqtrade/plugins/pairlist/RemotePairList.py @@ -6,7 +6,7 @@ Provides pair list fetched from a remote source import logging from pathlib import Path -from typing import Any, Dict, List, Tuple +from typing import Any import rapidjson import requests @@ -54,7 +54,7 @@ class RemotePairList(IPairList): self._bearer_token = self._pairlistconfig.get("bearer_token", "") self._init_done = False self._save_to_file = self._pairlistconfig.get("save_to_file", None) - self._last_pairlist: List[Any] = list() + self._last_pairlist: list[Any] = list() if self._mode not in ["whitelist", "blacklist"]: raise OperationalException( @@ -93,7 +93,7 @@ class RemotePairList(IPairList): return "Retrieve pairs from a remote API or local file." @staticmethod - def available_parameters() -> Dict[str, PairlistParameter]: + def available_parameters() -> dict[str, PairlistParameter]: return { "pairlist_url": { "type": "string", @@ -148,7 +148,7 @@ class RemotePairList(IPairList): }, } - def process_json(self, jsonparse) -> List[str]: + def process_json(self, jsonparse) -> list[str]: pairlist = jsonparse.get("pairs", []) remote_refresh_period = int(jsonparse.get("refresh_period", self._refresh_period)) @@ -166,7 +166,7 @@ class RemotePairList(IPairList): return pairlist - def return_last_pairlist(self) -> List[str]: + def return_last_pairlist(self) -> list[str]: if self._keep_pairlist_on_failure: pairlist = self._last_pairlist self.log_once("Keeping last fetched pairlist", logger.info) @@ -175,7 +175,7 @@ class RemotePairList(IPairList): return pairlist - def fetch_pairlist(self) -> Tuple[List[str], float]: + def fetch_pairlist(self) -> tuple[list[str], float]: headers = {"User-Agent": "Freqtrade/" + __version__ + " Remotepairlist"} if self._bearer_token: @@ -207,14 +207,14 @@ class RemotePairList(IPairList): return pairlist, time_elapsed - def _handle_error(self, error: str) -> List[str]: + def _handle_error(self, error: str) -> list[str]: if self._init_done: self.log_once("Error: " + error, logger.info) return self.return_last_pairlist() else: raise OperationalException(error) - def gen_pairlist(self, tickers: Tickers) -> List[str]: + def gen_pairlist(self, tickers: Tickers) -> list[str]: """ Generate the pairlist :param tickers: Tickers (from exchange.get_tickers). May be cached. @@ -278,7 +278,7 @@ class RemotePairList(IPairList): return pairlist - def save_pairlist(self, pairlist: List[str], filename: str) -> None: + def save_pairlist(self, pairlist: list[str], filename: str) -> None: pairlist_data = {"pairs": pairlist} try: file_path = Path(filename) @@ -288,7 +288,7 @@ class RemotePairList(IPairList): except Exception as e: logger.error(f"Error saving processed pairlist to {filename}: {e}") - def filter_pairlist(self, pairlist: List[str], tickers: Dict) -> List[str]: + def filter_pairlist(self, pairlist: list[str], tickers: dict) -> list[str]: """ Filters and sorts pairlist and returns the whitelist again. Called on each bot iteration - please use internal caching if necessary diff --git a/freqtrade/plugins/pairlist/ShuffleFilter.py b/freqtrade/plugins/pairlist/ShuffleFilter.py index bad2602d2..be536e705 100644 --- a/freqtrade/plugins/pairlist/ShuffleFilter.py +++ b/freqtrade/plugins/pairlist/ShuffleFilter.py @@ -4,7 +4,7 @@ Shuffle pair list filter import logging import random -from typing import Dict, List, Literal +from typing import Literal from freqtrade.enums import RunMode from freqtrade.exchange import timeframe_to_seconds @@ -61,7 +61,7 @@ class ShuffleFilter(IPairList): return "Randomize pairlist order." @staticmethod - def available_parameters() -> Dict[str, PairlistParameter]: + def available_parameters() -> dict[str, PairlistParameter]: return { "shuffle_frequency": { "type": "option", @@ -78,7 +78,7 @@ class ShuffleFilter(IPairList): }, } - def filter_pairlist(self, pairlist: List[str], tickers: Tickers) -> List[str]: + def filter_pairlist(self, pairlist: list[str], tickers: Tickers) -> list[str]: """ Filters and sorts pairlist and returns the whitelist again. Called on each bot iteration - please use internal caching if necessary diff --git a/freqtrade/plugins/pairlist/SpreadFilter.py b/freqtrade/plugins/pairlist/SpreadFilter.py index 5e4e9de94..0ecd1909d 100644 --- a/freqtrade/plugins/pairlist/SpreadFilter.py +++ b/freqtrade/plugins/pairlist/SpreadFilter.py @@ -3,7 +3,7 @@ Spread pair list filter """ import logging -from typing import Dict, Optional +from typing import Optional from freqtrade.exceptions import OperationalException from freqtrade.exchange.exchange_types import Ticker @@ -51,7 +51,7 @@ class SpreadFilter(IPairList): return "Filter by bid/ask difference." @staticmethod - def available_parameters() -> Dict[str, PairlistParameter]: + def available_parameters() -> dict[str, PairlistParameter]: return { "max_spread_ratio": { "type": "number", diff --git a/freqtrade/plugins/pairlist/StaticPairList.py b/freqtrade/plugins/pairlist/StaticPairList.py index 6a493a5c5..bff2105b3 100644 --- a/freqtrade/plugins/pairlist/StaticPairList.py +++ b/freqtrade/plugins/pairlist/StaticPairList.py @@ -6,7 +6,6 @@ Provides pair white list as it configured in config import logging from copy import deepcopy -from typing import Dict, List from freqtrade.exchange.exchange_types import Tickers from freqtrade.plugins.pairlist.IPairList import IPairList, PairlistParameter, SupportsBacktesting @@ -45,7 +44,7 @@ class StaticPairList(IPairList): return "Use pairlist as configured in config." @staticmethod - def available_parameters() -> Dict[str, PairlistParameter]: + def available_parameters() -> dict[str, PairlistParameter]: return { "allow_inactive": { "type": "boolean", @@ -55,7 +54,7 @@ class StaticPairList(IPairList): }, } - def gen_pairlist(self, tickers: Tickers) -> List[str]: + def gen_pairlist(self, tickers: Tickers) -> list[str]: """ Generate the pairlist :param tickers: Tickers (from exchange.get_tickers). May be cached. @@ -71,7 +70,7 @@ class StaticPairList(IPairList): # proper warnings in the log return self._whitelist_for_active_markets(wl) - def filter_pairlist(self, pairlist: List[str], tickers: Tickers) -> List[str]: + def filter_pairlist(self, pairlist: list[str], tickers: Tickers) -> list[str]: """ Filters and sorts pairlist and returns the whitelist again. Called on each bot iteration - please use internal caching if necessary diff --git a/freqtrade/plugins/pairlist/VolatilityFilter.py b/freqtrade/plugins/pairlist/VolatilityFilter.py index a2808ddfe..76e93a1f3 100644 --- a/freqtrade/plugins/pairlist/VolatilityFilter.py +++ b/freqtrade/plugins/pairlist/VolatilityFilter.py @@ -5,7 +5,7 @@ Volatility pairlist filter import logging import sys from datetime import timedelta -from typing import Dict, List, Optional +from typing import Optional import numpy as np from cachetools import TTLCache @@ -79,7 +79,7 @@ class VolatilityFilter(IPairList): return "Filter pairs by their recent volatility." @staticmethod - def available_parameters() -> Dict[str, PairlistParameter]: + def available_parameters() -> dict[str, PairlistParameter]: return { "lookback_days": { "type": "number", @@ -109,7 +109,7 @@ class VolatilityFilter(IPairList): **IPairList.refresh_period_parameter(), } - def filter_pairlist(self, pairlist: List[str], tickers: Tickers) -> List[str]: + def filter_pairlist(self, pairlist: list[str], tickers: Tickers) -> list[str]: """ Validate trading range :param pairlist: pairlist to filter or sort @@ -123,8 +123,8 @@ class VolatilityFilter(IPairList): since_ms = dt_ts(dt_floor_day(dt_now()) - timedelta(days=self._days)) candles = self._exchange.refresh_ohlcv_with_cache(needed_pairs, since_ms=since_ms) - resulting_pairlist: List[str] = [] - volatilitys: Dict[str, float] = {} + resulting_pairlist: list[str] = [] + volatilitys: dict[str, float] = {} for p in pairlist: daily_candles = candles.get((p, "1d", self._def_candletype), None) diff --git a/freqtrade/plugins/pairlist/VolumePairList.py b/freqtrade/plugins/pairlist/VolumePairList.py index 4b56e0c7f..b8f806858 100644 --- a/freqtrade/plugins/pairlist/VolumePairList.py +++ b/freqtrade/plugins/pairlist/VolumePairList.py @@ -6,7 +6,7 @@ Provides dynamic pair list based on trade volumes import logging from datetime import timedelta -from typing import Any, Dict, List, Literal +from typing import Any, Literal from cachetools import TTLCache @@ -122,7 +122,7 @@ class VolumePairList(IPairList): return "Provides dynamic pair list based on trade volumes." @staticmethod - def available_parameters() -> Dict[str, PairlistParameter]: + def available_parameters() -> dict[str, PairlistParameter]: return { "number_assets": { "type": "number", @@ -170,7 +170,7 @@ class VolumePairList(IPairList): }, } - def gen_pairlist(self, tickers: Tickers) -> List[str]: + def gen_pairlist(self, tickers: Tickers) -> list[str]: """ Generate the pairlist :param tickers: Tickers (from exchange.get_tickers). May be cached. @@ -212,7 +212,7 @@ class VolumePairList(IPairList): return pairlist - def filter_pairlist(self, pairlist: List[str], tickers: Dict) -> List[str]: + def filter_pairlist(self, pairlist: list[str], tickers: dict) -> list[str]: """ Filters and sorts pairlist and returns the whitelist again. Called on each bot iteration - please use internal caching if necessary @@ -222,7 +222,7 @@ class VolumePairList(IPairList): """ if self._use_range: # Create bare minimum from tickers structure. - filtered_tickers: List[Dict[str, Any]] = [{"symbol": k} for k in pairlist] + filtered_tickers: list[dict[str, Any]] = [{"symbol": k} for k in pairlist] # get lookback period in ms, for exchange ohlcv fetch since_ms = ( diff --git a/freqtrade/plugins/pairlist/pairlist_helpers.py b/freqtrade/plugins/pairlist/pairlist_helpers.py index cbe79c5f5..b3138f8f2 100644 --- a/freqtrade/plugins/pairlist/pairlist_helpers.py +++ b/freqtrade/plugins/pairlist/pairlist_helpers.py @@ -1,12 +1,11 @@ import re -from typing import List from freqtrade.constants import Config def expand_pairlist( - wildcardpl: List[str], available_pairs: List[str], keep_invalid: bool = False -) -> List[str]: + wildcardpl: list[str], available_pairs: list[str], keep_invalid: bool = False +) -> list[str]: """ Expand pairlist potentially containing wildcards based on available markets. This will implicitly filter all pairs in the wildcard-list which are not in available_pairs. @@ -41,7 +40,7 @@ def expand_pairlist( return result -def dynamic_expand_pairlist(config: Config, markets: List[str]) -> List[str]: +def dynamic_expand_pairlist(config: Config, markets: list[str]) -> list[str]: expanded_pairs = expand_pairlist(config["pairs"], markets) if config.get("freqai", {}).get("enabled", False): corr_pairlist = config["freqai"]["feature_parameters"]["include_corr_pairlist"] diff --git a/freqtrade/plugins/pairlist/rangestabilityfilter.py b/freqtrade/plugins/pairlist/rangestabilityfilter.py index 25cc6e423..cff958865 100644 --- a/freqtrade/plugins/pairlist/rangestabilityfilter.py +++ b/freqtrade/plugins/pairlist/rangestabilityfilter.py @@ -4,7 +4,7 @@ Rate of change pairlist filter import logging from datetime import timedelta -from typing import Dict, List, Optional +from typing import Optional from cachetools import TTLCache from pandas import DataFrame @@ -76,7 +76,7 @@ class RangeStabilityFilter(IPairList): return "Filters pairs by their rate of change." @staticmethod - def available_parameters() -> Dict[str, PairlistParameter]: + def available_parameters() -> dict[str, PairlistParameter]: return { "lookback_days": { "type": "number", @@ -106,7 +106,7 @@ class RangeStabilityFilter(IPairList): **IPairList.refresh_period_parameter(), } - def filter_pairlist(self, pairlist: List[str], tickers: Tickers) -> List[str]: + def filter_pairlist(self, pairlist: list[str], tickers: Tickers) -> list[str]: """ Validate trading range :param pairlist: pairlist to filter or sort @@ -120,8 +120,8 @@ class RangeStabilityFilter(IPairList): since_ms = dt_ts(dt_floor_day(dt_now()) - timedelta(days=self._days + 1)) candles = self._exchange.refresh_ohlcv_with_cache(needed_pairs, since_ms=since_ms) - resulting_pairlist: List[str] = [] - pct_changes: Dict[str, float] = {} + resulting_pairlist: list[str] = [] + pct_changes: dict[str, float] = {} for p in pairlist: daily_candles = candles.get((p, "1d", self._def_candletype), None) diff --git a/freqtrade/plugins/pairlistmanager.py b/freqtrade/plugins/pairlistmanager.py index ba80d09da..2171629d3 100644 --- a/freqtrade/plugins/pairlistmanager.py +++ b/freqtrade/plugins/pairlistmanager.py @@ -4,7 +4,7 @@ PairList manager class import logging from functools import partial -from typing import Dict, List, Optional +from typing import Optional from cachetools import TTLCache, cached @@ -31,7 +31,7 @@ class PairListManager(LoggingMixin): 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._pairlist_handlers: list[IPairList] = [] self._tickers_needed = False self._dataprovider: Optional[DataProvider] = dataprovider for pairlist_handler_config in self._config.get("pairlists", []): @@ -67,9 +67,9 @@ class PairListManager(LoggingMixin): if self._config["runmode"] not in (RunMode.BACKTEST, RunMode.EDGE, RunMode.HYPEROPT): return - pairlist_errors: List[str] = [] - noaction_pairlists: List[str] = [] - biased_pairlists: List[str] = [] + pairlist_errors: list[str] = [] + noaction_pairlists: list[str] = [] + biased_pairlists: list[str] = [] for pairlist_handler in self._pairlist_handlers: if pairlist_handler.supports_backtesting == SupportsBacktesting.NO: pairlist_errors.append(pairlist_handler.name) @@ -97,12 +97,12 @@ class PairListManager(LoggingMixin): ) @property - def whitelist(self) -> List[str]: + def whitelist(self) -> list[str]: """The current whitelist""" return self._whitelist @property - def blacklist(self) -> List[str]: + def blacklist(self) -> list[str]: """ The current blacklist -> no need to overwrite in subclasses @@ -110,16 +110,16 @@ class PairListManager(LoggingMixin): return self._blacklist @property - def expanded_blacklist(self) -> List[str]: + def expanded_blacklist(self) -> list[str]: """The expanded blacklist (including wildcard expansion)""" return expand_pairlist(self._blacklist, self._exchange.get_markets().keys()) @property - def name_list(self) -> List[str]: + def name_list(self) -> list[str]: """Get list of loaded Pairlist Handler names""" return [p.name for p in self._pairlist_handlers] - def short_desc(self) -> List[Dict]: + def short_desc(self) -> list[dict]: """List of short_desc for each Pairlist Handler""" return [{p.name: p.short_desc()} for p in self._pairlist_handlers] @@ -130,7 +130,7 @@ class PairListManager(LoggingMixin): def refresh_pairlist(self) -> None: """Run pairlist through all configured Pairlist Handlers.""" # Tickers should be cached to avoid calling the exchange on each call. - tickers: Dict = {} + tickers: dict = {} if self._tickers_needed: tickers = self._get_cached_tickers() @@ -150,7 +150,7 @@ class PairListManager(LoggingMixin): self._whitelist = pairlist - def verify_blacklist(self, pairlist: List[str], logmethod) -> List[str]: + def verify_blacklist(self, pairlist: list[str], logmethod) -> list[str]: """ Verify and remove items from pairlist - returning a filtered pairlist. Logs a warning or info depending on `aswarning`. @@ -173,8 +173,8 @@ class PairListManager(LoggingMixin): return pairlist def verify_whitelist( - self, pairlist: List[str], logmethod, keep_invalid: bool = False - ) -> List[str]: + self, pairlist: list[str], logmethod, keep_invalid: bool = False + ) -> list[str]: """ Verify and remove items from pairlist - returning a filtered pairlist. Logs a warning or info depending on `aswarning`. @@ -193,7 +193,7 @@ class PairListManager(LoggingMixin): return whitelist def create_pair_list( - self, pairs: List[str], timeframe: Optional[str] = None + self, pairs: list[str], timeframe: Optional[str] = None ) -> ListPairsWithTimeframes: """ Create list of pair tuples with (pair, timeframe) diff --git a/freqtrade/plugins/protectionmanager.py b/freqtrade/plugins/protectionmanager.py index 4f60ae0e0..648cd6bbb 100644 --- a/freqtrade/plugins/protectionmanager.py +++ b/freqtrade/plugins/protectionmanager.py @@ -4,9 +4,10 @@ Protection manager class import logging from datetime import datetime, timezone -from typing import Dict, List, Optional +from typing import Any, Optional from freqtrade.constants import Config, LongShort +from freqtrade.exceptions import ConfigurationError from freqtrade.persistence import PairLocks from freqtrade.persistence.models import PairLock from freqtrade.plugins.protections import IProtection @@ -17,10 +18,11 @@ logger = logging.getLogger(__name__) class ProtectionManager: - def __init__(self, config: Config, protections: List) -> None: + def __init__(self, config: Config, protections: list) -> None: self._config = config - self._protection_handlers: List[IProtection] = [] + self._protection_handlers: list[IProtection] = [] + self.validate_protections(protections) for protection_handler_config in protections: protection_handler = ProtectionResolver.load_protection( protection_handler_config["method"], @@ -33,13 +35,13 @@ class ProtectionManager: logger.info("No protection Handlers defined.") @property - def name_list(self) -> List[str]: + def name_list(self) -> list[str]: """ Get list of loaded Protection Handler names """ return [p.name for p in self._protection_handlers] - def short_desc(self) -> List[Dict]: + def short_desc(self) -> list[dict]: """ List of short_desc for each Pairlist Handler """ @@ -76,3 +78,40 @@ class ProtectionManager: pair, lock.until, lock.reason, now=now, side=lock.lock_side ) return result + + @staticmethod + def validate_protections(protections: list[dict[str, Any]]) -> None: + """ + Validate protection setup validity + """ + + for prot in protections: + parsed_unlock_at = None + if (config_unlock_at := prot.get("unlock_at")) is not None: + try: + parsed_unlock_at = datetime.strptime(config_unlock_at, "%H:%M") + except ValueError: + raise ConfigurationError( + f"Invalid date format for unlock_at: {config_unlock_at}." + ) + + if "stop_duration" in prot and "stop_duration_candles" in prot: + raise ConfigurationError( + "Protections must specify either `stop_duration` or `stop_duration_candles`.\n" + f"Please fix the protection {prot.get('method')}." + ) + + if "lookback_period" in prot and "lookback_period_candles" in prot: + raise ConfigurationError( + "Protections must specify either `lookback_period` or " + f"`lookback_period_candles`.\n Please fix the protection {prot.get('method')}." + ) + + if parsed_unlock_at is not None and ( + "stop_duration" in prot or "stop_duration_candles" in prot + ): + raise ConfigurationError( + "Protections must specify either `unlock_at`, `stop_duration` or " + "`stop_duration_candles`.\n" + f"Please fix the protection {prot.get('method')}." + ) diff --git a/freqtrade/plugins/protections/iprotection.py b/freqtrade/plugins/protections/iprotection.py index c4b039161..a2264675e 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, Dict, List, Optional +from typing import Any, Optional from freqtrade.constants import Config, LongShort from freqtrade.exchange import timeframe_to_minutes @@ -28,7 +28,7 @@ class IProtection(LoggingMixin, ABC): # Can stop trading for one pair has_local_stop: bool = False - def __init__(self, config: Config, protection_config: Dict[str, Any]) -> None: + 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 @@ -119,7 +119,7 @@ class IProtection(LoggingMixin, ABC): If true, this pair will be locked with until """ - def calculate_lock_end(self, trades: List[LocalTrade]) -> datetime: + def calculate_lock_end(self, trades: list[LocalTrade]) -> datetime: """ Get lock end time Implicitly uses `self._stop_duration` or `self._unlock_at` depending on the configuration. diff --git a/freqtrade/plugins/protections/low_profit_pairs.py b/freqtrade/plugins/protections/low_profit_pairs.py index f0023646a..e6ee0db64 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, Dict, Optional +from typing import Any, Optional from freqtrade.constants import Config, LongShort from freqtrade.persistence import Trade @@ -14,7 +14,7 @@ class LowProfitPairs(IProtection): has_global_stop: bool = False has_local_stop: bool = True - def __init__(self, config: Config, protection_config: Dict[str, Any]) -> None: + def __init__(self, config: Config, protection_config: dict[str, Any]) -> None: super().__init__(config, protection_config) self._trade_limit = protection_config.get("trade_limit", 1) diff --git a/freqtrade/plugins/protections/max_drawdown_protection.py b/freqtrade/plugins/protections/max_drawdown_protection.py index 5939ee9f0..903f9fbbf 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, Dict, Optional +from typing import Any, Optional import pandas as pd @@ -17,7 +17,7 @@ class MaxDrawdown(IProtection): has_global_stop: bool = True has_local_stop: bool = False - def __init__(self, config: Config, protection_config: Dict[str, Any]) -> None: + def __init__(self, config: Config, protection_config: dict[str, Any]) -> None: super().__init__(config, protection_config) self._trade_limit = protection_config.get("trade_limit", 1) diff --git a/freqtrade/plugins/protections/stoploss_guard.py b/freqtrade/plugins/protections/stoploss_guard.py index da7437178..47ebc3696 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, Dict, Optional +from typing import Any, Optional from freqtrade.constants import Config, LongShort from freqtrade.enums import ExitType @@ -15,7 +15,7 @@ class StoplossGuard(IProtection): has_global_stop: bool = True has_local_stop: bool = True - def __init__(self, config: Config, protection_config: Dict[str, Any]) -> None: + def __init__(self, config: Config, protection_config: dict[str, Any]) -> None: super().__init__(config, protection_config) self._trade_limit = protection_config.get("trade_limit", 10) diff --git a/freqtrade/resolvers/exchange_resolver.py b/freqtrade/resolvers/exchange_resolver.py index c0c3c906b..9d5a0d557 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, Dict, List, Optional +from typing import Any, Optional import freqtrade.exchange as exchanges from freqtrade.constants import Config, ExchangeConfig @@ -90,7 +90,7 @@ class ExchangeResolver(IResolver): @classmethod def search_all_objects( cls, config: Config, enum_failed: bool, recursive: bool = False - ) -> List[Dict[str, Any]]: + ) -> list[dict[str, Any]]: """ Searches for valid objects :param config: Config object diff --git a/freqtrade/resolvers/iresolver.py b/freqtrade/resolvers/iresolver.py index bac5c08a7..9aa103a7e 100644 --- a/freqtrade/resolvers/iresolver.py +++ b/freqtrade/resolvers/iresolver.py @@ -8,8 +8,9 @@ import importlib.util import inspect import logging import sys +from collections.abc import Iterator from pathlib import Path -from typing import Any, Dict, Iterator, List, Optional, Tuple, Type, Union +from typing import Any, Optional, Union from freqtrade.constants import Config from freqtrade.exceptions import OperationalException @@ -40,7 +41,7 @@ class IResolver: """ # Childclasses need to override this - object_type: Type[Any] + object_type: type[Any] object_type_str: str user_subdir: Optional[str] = None initial_search_path: Optional[Path] = None @@ -52,9 +53,9 @@ class IResolver: cls, config: Config, user_subdir: Optional[str] = None, - extra_dirs: Optional[List[str]] = None, - ) -> List[Path]: - abs_paths: List[Path] = [] + extra_dirs: Optional[list[str]] = None, + ) -> list[Path]: + abs_paths: list[Path] = [] if cls.initial_search_path: abs_paths.append(cls.initial_search_path) @@ -108,15 +109,21 @@ class IResolver: if enum_failed: return iter([None]) + def is_valid_class(obj): + try: + return ( + inspect.isclass(obj) + and issubclass(obj, cls.object_type) + and obj is not cls.object_type + and obj.__module__ == module_name + ) + except TypeError: + return False + valid_objects_gen = ( (obj, inspect.getsource(module)) - for name, obj in inspect.getmembers(module, inspect.isclass) - if ( - (object_name is None or object_name == name) - and issubclass(obj, cls.object_type) - and obj is not cls.object_type - and obj.__module__ == module_name - ) + for name, obj in inspect.getmembers(module, is_valid_class) + if (object_name is None or object_name == name) ) # The __module__ check ensures we only use strategies that are defined in this folder. return valid_objects_gen @@ -124,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]]: + ) -> Union[tuple[Any, Path], tuple[None, None]]: """ Search for the objectname in the given directory :param directory: relative or absolute directory path @@ -153,7 +160,7 @@ class IResolver: @classmethod def _load_object( - cls, paths: List[Path], *, object_name: str, add_source: bool = False, kwargs: Dict + cls, paths: list[Path], *, object_name: str, add_source: bool = False, kwargs: dict ) -> Optional[Any]: """ Try to load object from path list. @@ -188,7 +195,7 @@ class IResolver: :return: Object instance or None """ - extra_dirs: List[str] = [] + extra_dirs: list[str] = [] if extra_dir: extra_dirs.append(extra_dir) @@ -207,7 +214,7 @@ class IResolver: @classmethod def search_all_objects( cls, config: Config, enum_failed: bool, recursive: bool = False - ) -> List[Dict[str, Any]]: + ) -> list[dict[str, Any]]: """ Searches for valid objects :param config: Config object @@ -239,7 +246,7 @@ class IResolver: enum_failed: bool, recursive: bool = False, basedir: Optional[Path] = None, - ) -> List[Dict[str, Any]]: + ) -> list[dict[str, Any]]: """ Searches a directory for valid objects :param directory: Path to search @@ -249,7 +256,7 @@ class IResolver: :return: List of dicts containing 'name', 'class' and 'location' entries """ logger.debug(f"Searching for {cls.object_type.__name__} '{directory}'") - objects: List[Dict[str, Any]] = [] + objects: list[dict[str, Any]] = [] if not directory.is_dir(): logger.info(f"'{directory}' is not a directory, skipping.") return objects diff --git a/freqtrade/resolvers/protection_resolver.py b/freqtrade/resolvers/protection_resolver.py index 67b68f050..661791ace 100644 --- a/freqtrade/resolvers/protection_resolver.py +++ b/freqtrade/resolvers/protection_resolver.py @@ -4,7 +4,6 @@ This module load custom pairlists import logging from pathlib import Path -from typing import Dict from freqtrade.constants import Config from freqtrade.plugins.protections import IProtection @@ -26,7 +25,7 @@ class ProtectionResolver(IResolver): @staticmethod def load_protection( - protection_name: str, config: Config, protection_config: Dict + protection_name: str, config: Config, protection_config: dict ) -> IProtection: """ Load the protection with protection_name diff --git a/freqtrade/resolvers/strategy_resolver.py b/freqtrade/resolvers/strategy_resolver.py index d234a680f..5ac1cb6f9 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, List, Optional +from typing import Any, Optional from freqtrade.configuration.config_validation import validate_migrated_strategy_settings from freqtrade.constants import REQUIRED_ORDERTIF, REQUIRED_ORDERTYPES, USERPATH_STRATEGIES, Config @@ -69,7 +69,6 @@ class StrategyResolver(IResolver): ("order_time_in_force", None), ("stake_currency", None), ("stake_amount", None), - ("protections", None), ("startup_candle_count", None), ("unfilledtimeout", None), ("use_exit_signal", True), @@ -257,7 +256,7 @@ class StrategyResolver(IResolver): :return: Strategy instance or None """ if config.get("recursive_strategy_search", False): - extra_dirs: List[str] = [ + extra_dirs: list[str] = [ path[0] for path in walk(f"{config['user_data_dir']}/{USERPATH_STRATEGIES}") ] # sub-directories else: diff --git a/freqtrade/rpc/api_server/api_auth.py b/freqtrade/rpc/api_server/api_auth.py index 24e04a905..d9b04ab81 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, Dict, Union +from typing import Any, Union import jwt from fastapi import APIRouter, Depends, HTTPException, Query, WebSocket, status @@ -56,7 +56,7 @@ def get_user_from_token(token, secret_key: str, token_type: str = "access") -> s async def validate_ws_token( ws: WebSocket, ws_token: Union[str, None] = Query(default=None, alias="token"), - api_config: Dict[str, Any] = Depends(get_api_config), + api_config: dict[str, Any] = Depends(get_api_config), ): secret_ws_token = api_config.get("ws_token", None) secret_jwt_key = api_config.get("jwt_secret_key", "super-secret") diff --git a/freqtrade/rpc/api_server/api_background_tasks.py b/freqtrade/rpc/api_server/api_background_tasks.py index 6df0411c8..3ca1d7311 100644 --- a/freqtrade/rpc/api_server/api_background_tasks.py +++ b/freqtrade/rpc/api_server/api_background_tasks.py @@ -1,23 +1,9 @@ import logging -from copy import deepcopy -from typing import List -from fastapi import APIRouter, BackgroundTasks, Depends +from fastapi import APIRouter from fastapi.exceptions import HTTPException -from freqtrade.constants import Config -from freqtrade.enums import CandleType -from freqtrade.exceptions import OperationalException -from freqtrade.persistence import FtNoDBContext -from freqtrade.rpc.api_server.api_schemas import ( - BackgroundTaskStatus, - BgJobStarted, - ExchangeModePayloadMixin, - PairListsPayload, - PairListsResponse, - WhitelistEvaluateResponse, -) -from freqtrade.rpc.api_server.deps import get_config, get_exchange +from freqtrade.rpc.api_server.api_schemas import BackgroundTaskStatus from freqtrade.rpc.api_server.webserver_bgwork import ApiBG @@ -27,7 +13,7 @@ logger = logging.getLogger(__name__) router = APIRouter() -@router.get("/background", response_model=List[BackgroundTaskStatus], tags=["webserver"]) +@router.get("/background", response_model=list[BackgroundTaskStatus], tags=["webserver"]) def background_job_list(): return [ { @@ -55,123 +41,3 @@ def background_job(jobid: str): "progress": job.get("progress"), "error": job.get("error", None), } - - -@router.get( - "/pairlists/available", response_model=PairListsResponse, tags=["pairlists", "webserver"] -) -def list_pairlists(config=Depends(get_config)): - from freqtrade.resolvers import PairListResolver - - pairlists = PairListResolver.search_all_objects(config, False) - pairlists = sorted(pairlists, key=lambda x: x["name"]) - - return { - "pairlists": [ - { - "name": x["name"], - "is_pairlist_generator": x["class"].is_pairlist_generator, - "params": x["class"].available_parameters(), - "description": x["class"].description(), - } - for x in pairlists - ] - } - - -def __run_pairlist(job_id: str, config_loc: Config): - try: - ApiBG.jobs[job_id]["is_running"] = True - from freqtrade.plugins.pairlistmanager import PairListManager - - with FtNoDBContext(): - exchange = get_exchange(config_loc) - pairlists = PairListManager(exchange, config_loc) - pairlists.refresh_pairlist() - ApiBG.jobs[job_id]["result"] = { - "method": pairlists.name_list, - "length": len(pairlists.whitelist), - "whitelist": pairlists.whitelist, - } - ApiBG.jobs[job_id]["status"] = "success" - except (OperationalException, Exception) as e: - logger.exception(e) - ApiBG.jobs[job_id]["error"] = str(e) - ApiBG.jobs[job_id]["status"] = "failed" - finally: - ApiBG.jobs[job_id]["is_running"] = False - ApiBG.pairlist_running = False - - -@router.post("/pairlists/evaluate", response_model=BgJobStarted, tags=["pairlists", "webserver"]) -def pairlists_evaluate( - payload: PairListsPayload, background_tasks: BackgroundTasks, config=Depends(get_config) -): - if ApiBG.pairlist_running: - raise HTTPException(status_code=400, detail="Pairlist evaluation is already running.") - - config_loc = deepcopy(config) - config_loc["stake_currency"] = payload.stake_currency - config_loc["pairlists"] = payload.pairlists - handleExchangePayload(payload, config_loc) - # TODO: overwrite blacklist? make it optional and fall back to the one in config? - # Outcome depends on the UI approach. - config_loc["exchange"]["pair_blacklist"] = payload.blacklist - # Random job id - job_id = ApiBG.get_job_id() - - ApiBG.jobs[job_id] = { - "category": "pairlist", - "status": "pending", - "progress": None, - "is_running": False, - "result": {}, - "error": None, - } - background_tasks.add_task(__run_pairlist, job_id, config_loc) - ApiBG.pairlist_running = True - - return { - "status": "Pairlist evaluation started in background.", - "job_id": job_id, - } - - -def handleExchangePayload(payload: ExchangeModePayloadMixin, config_loc: Config): - """ - Handle exchange and trading mode payload. - Updates the configuration with the payload values. - """ - if payload.exchange: - config_loc["exchange"]["name"] = payload.exchange - if payload.trading_mode: - config_loc["trading_mode"] = payload.trading_mode - config_loc["candle_type_def"] = CandleType.get_default( - config_loc.get("trading_mode", "spot") or "spot" - ) - if payload.margin_mode: - config_loc["margin_mode"] = payload.margin_mode - - -@router.get( - "/pairlists/evaluate/{jobid}", - response_model=WhitelistEvaluateResponse, - tags=["pairlists", "webserver"], -) -def pairlists_evaluate_get(jobid: str): - if not (job := ApiBG.jobs.get(jobid)): - raise HTTPException(status_code=404, detail="Job not found.") - - if job["is_running"]: - raise HTTPException(status_code=400, detail="Job not finished yet.") - - if error := job["error"]: - return { - "status": "failed", - "error": error, - } - - return { - "status": "success", - "result": job["result"], - } diff --git a/freqtrade/rpc/api_server/api_backtest.py b/freqtrade/rpc/api_server/api_backtest.py index e4b598807..1386109b8 100644 --- a/freqtrade/rpc/api_server/api_backtest.py +++ b/freqtrade/rpc/api_server/api_backtest.py @@ -3,7 +3,7 @@ import logging from copy import deepcopy from datetime import datetime from pathlib import Path -from typing import Any, Dict, List +from typing import Any from fastapi import APIRouter, BackgroundTasks, Depends from fastapi.exceptions import HTTPException @@ -77,7 +77,6 @@ def __run_backtest_bg(btconfig: Config): lastconfig["timerange"] = btconfig["timerange"] lastconfig["timeframe"] = strat.timeframe - lastconfig["protections"] = btconfig.get("protections", []) lastconfig["enable_protections"] = btconfig.get("enable_protections") lastconfig["dry_run_wallet"] = btconfig.get("dry_run_wallet") @@ -261,7 +260,7 @@ def api_backtest_abort(): @router.get( - "/backtest/history", response_model=List[BacktestHistoryEntry], tags=["webserver", "backtest"] + "/backtest/history", response_model=list[BacktestHistoryEntry], tags=["webserver", "backtest"] ) def api_backtest_history(config=Depends(get_config)): # Get backtest result history, read from metadata files @@ -276,7 +275,7 @@ def api_backtest_history_result(filename: str, strategy: str, config=Depends(get bt_results_base: Path = config["user_data_dir"] / "backtest_results" fn = (bt_results_base / filename).with_suffix(".json") - results: Dict[str, Any] = { + results: dict[str, Any] = { "metadata": {}, "strategy": {}, "strategy_comparison": [], @@ -296,7 +295,7 @@ def api_backtest_history_result(filename: str, strategy: str, config=Depends(get @router.delete( "/backtest/history/{file}", - response_model=List[BacktestHistoryEntry], + response_model=list[BacktestHistoryEntry], tags=["webserver", "backtest"], ) def api_delete_backtest_history_entry(file: str, config=Depends(get_config)): @@ -313,7 +312,7 @@ def api_delete_backtest_history_entry(file: str, config=Depends(get_config)): @router.patch( "/backtest/history/{file}", - response_model=List[BacktestHistoryEntry], + response_model=list[BacktestHistoryEntry], tags=["webserver", "backtest"], ) def api_update_backtest_history_entry( diff --git a/freqtrade/rpc/api_server/api_pairlists.py b/freqtrade/rpc/api_server/api_pairlists.py new file mode 100644 index 000000000..75467c28b --- /dev/null +++ b/freqtrade/rpc/api_server/api_pairlists.py @@ -0,0 +1,145 @@ +import logging +from copy import deepcopy + +from fastapi import APIRouter, BackgroundTasks, Depends +from fastapi.exceptions import HTTPException + +from freqtrade.constants import Config +from freqtrade.enums import CandleType +from freqtrade.exceptions import OperationalException +from freqtrade.persistence import FtNoDBContext +from freqtrade.rpc.api_server.api_schemas import ( + BgJobStarted, + ExchangeModePayloadMixin, + PairListsPayload, + PairListsResponse, + WhitelistEvaluateResponse, +) +from freqtrade.rpc.api_server.deps import get_config, get_exchange +from freqtrade.rpc.api_server.webserver_bgwork import ApiBG + + +logger = logging.getLogger(__name__) + +# Private API, protected by authentication and webserver_mode dependency +router = APIRouter() + + +@router.get( + "/pairlists/available", response_model=PairListsResponse, tags=["pairlists", "webserver"] +) +def list_pairlists(config=Depends(get_config)): + from freqtrade.resolvers import PairListResolver + + pairlists = PairListResolver.search_all_objects(config, False) + pairlists = sorted(pairlists, key=lambda x: x["name"]) + + return { + "pairlists": [ + { + "name": x["name"], + "is_pairlist_generator": x["class"].is_pairlist_generator, + "params": x["class"].available_parameters(), + "description": x["class"].description(), + } + for x in pairlists + ] + } + + +def __run_pairlist(job_id: str, config_loc: Config): + try: + ApiBG.jobs[job_id]["is_running"] = True + from freqtrade.plugins.pairlistmanager import PairListManager + + with FtNoDBContext(): + exchange = get_exchange(config_loc) + pairlists = PairListManager(exchange, config_loc) + pairlists.refresh_pairlist() + ApiBG.jobs[job_id]["result"] = { + "method": pairlists.name_list, + "length": len(pairlists.whitelist), + "whitelist": pairlists.whitelist, + } + ApiBG.jobs[job_id]["status"] = "success" + except (OperationalException, Exception) as e: + logger.exception(e) + ApiBG.jobs[job_id]["error"] = str(e) + ApiBG.jobs[job_id]["status"] = "failed" + finally: + ApiBG.jobs[job_id]["is_running"] = False + ApiBG.pairlist_running = False + + +@router.post("/pairlists/evaluate", response_model=BgJobStarted, tags=["pairlists", "webserver"]) +def pairlists_evaluate( + payload: PairListsPayload, background_tasks: BackgroundTasks, config=Depends(get_config) +): + if ApiBG.pairlist_running: + raise HTTPException(status_code=400, detail="Pairlist evaluation is already running.") + + config_loc = deepcopy(config) + config_loc["stake_currency"] = payload.stake_currency + config_loc["pairlists"] = payload.pairlists + handleExchangePayload(payload, config_loc) + # TODO: overwrite blacklist? make it optional and fall back to the one in config? + # Outcome depends on the UI approach. + config_loc["exchange"]["pair_blacklist"] = payload.blacklist + # Random job id + job_id = ApiBG.get_job_id() + + ApiBG.jobs[job_id] = { + "category": "pairlist", + "status": "pending", + "progress": None, + "is_running": False, + "result": {}, + "error": None, + } + background_tasks.add_task(__run_pairlist, job_id, config_loc) + ApiBG.pairlist_running = True + + return { + "status": "Pairlist evaluation started in background.", + "job_id": job_id, + } + + +def handleExchangePayload(payload: ExchangeModePayloadMixin, config_loc: Config): + """ + Handle exchange and trading mode payload. + Updates the configuration with the payload values. + """ + if payload.exchange: + config_loc["exchange"]["name"] = payload.exchange + if payload.trading_mode: + config_loc["trading_mode"] = payload.trading_mode + config_loc["candle_type_def"] = CandleType.get_default( + config_loc.get("trading_mode", "spot") or "spot" + ) + if payload.margin_mode: + config_loc["margin_mode"] = payload.margin_mode + + +@router.get( + "/pairlists/evaluate/{jobid}", + response_model=WhitelistEvaluateResponse, + tags=["pairlists", "webserver"], +) +def pairlists_evaluate_get(jobid: str): + if not (job := ApiBG.jobs.get(jobid)): + raise HTTPException(status_code=404, detail="Job not found.") + + if job["is_running"]: + raise HTTPException(status_code=400, detail="Job not finished yet.") + + if error := job["error"]: + return { + "status": "failed", + "error": error, + } + + return { + "status": "success", + "result": job["result"], + } diff --git a/freqtrade/rpc/api_server/api_schemas.py b/freqtrade/rpc/api_server/api_schemas.py index e3e23d211..3c0fd70b7 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, Dict, List, Optional, Union +from typing import Any, Optional, Union from pydantic import AwareDatetime, BaseModel, RootModel, SerializeAsAny @@ -73,7 +73,7 @@ class Balance(BaseModel): class Balances(BaseModel): - currencies: List[Balance] + currencies: list[Balance] total: float total_bot: float symbol: str @@ -172,8 +172,8 @@ class SellReason(BaseModel): class Stats(BaseModel): - exit_reasons: Dict[str, SellReason] - durations: Dict[str, Optional[float]] + exit_reasons: dict[str, SellReason] + durations: dict[str, Optional[float]] class DailyWeeklyMonthlyRecord(BaseModel): @@ -186,7 +186,7 @@ class DailyWeeklyMonthlyRecord(BaseModel): class DailyWeeklyMonthly(BaseModel): - data: List[DailyWeeklyMonthlyRecord] + data: list[DailyWeeklyMonthlyRecord] fiat_display_currency: str stake_currency: str @@ -221,7 +221,7 @@ class ShowConfig(BaseModel): available_capital: Optional[float] = None stake_currency_decimals: int max_open_trades: IntOrInf - minimal_roi: Dict[str, Any] + minimal_roi: dict[str, Any] stoploss: Optional[float] = None stoploss_on_exchange: bool trailing_stop: Optional[bool] = None @@ -237,8 +237,8 @@ class ShowConfig(BaseModel): exchange: str strategy: Optional[str] = None force_entry_enable: bool - exit_pricing: Dict[str, Any] - entry_pricing: Dict[str, Any] + exit_pricing: dict[str, Any] + entry_pricing: dict[str, Any] bot_name: str state: str runmode: str @@ -326,7 +326,7 @@ class TradeSchema(BaseModel): min_rate: Optional[float] = None max_rate: Optional[float] = None has_open_orders: bool - orders: List[OrderSchema] + orders: list[OrderSchema] leverage: Optional[float] = None interest_rate: Optional[float] = None @@ -352,7 +352,7 @@ class OpenTradeSchema(TradeSchema): class TradeResponse(BaseModel): - trades: List[TradeSchema] + trades: list[TradeSchema] trades_count: int offset: int total_trades: int @@ -375,7 +375,7 @@ class LockModel(BaseModel): class Locks(BaseModel): lock_count: int - locks: List[LockModel] + locks: list[LockModel] class LocksPayload(BaseModel): @@ -392,7 +392,7 @@ class DeleteLockRequest(BaseModel): class Logs(BaseModel): log_count: int - logs: List[List] + logs: list[list] class ForceEnterPayload(BaseModel): @@ -412,21 +412,21 @@ class ForceExitPayload(BaseModel): class BlacklistPayload(BaseModel): - blacklist: List[str] + blacklist: list[str] class BlacklistResponse(BaseModel): - blacklist: List[str] - blacklist_expanded: List[str] - errors: Dict + blacklist: list[str] + blacklist_expanded: list[str] + errors: dict length: int - method: List[str] + method: list[str] class WhitelistResponse(BaseModel): - whitelist: List[str] + whitelist: list[str] length: int - method: List[str] + method: list[str] class WhitelistEvaluateResponse(BackgroundTaskResult): @@ -441,40 +441,49 @@ class DeleteTrade(BaseModel): class PlotConfig_(BaseModel): - main_plot: Dict[str, Any] - subplots: Dict[str, Any] + main_plot: dict[str, Any] + subplots: dict[str, Any] -PlotConfig = RootModel[Union[PlotConfig_, Dict]] +PlotConfig = RootModel[Union[PlotConfig_, dict]] class StrategyListResponse(BaseModel): - strategies: List[str] + strategies: list[str] class ExchangeListResponse(BaseModel): - exchanges: List[ValidExchangesType] + exchanges: list[ValidExchangesType] + + +class HyperoptLoss(BaseModel): + name: str + description: str + + +class HyperoptLossListResponse(BaseModel): + loss_functions: list[HyperoptLoss] class PairListResponse(BaseModel): name: str description: str is_pairlist_generator: bool - params: Dict[str, Any] + params: dict[str, Any] class PairListsResponse(BaseModel): - pairlists: List[PairListResponse] + pairlists: list[PairListResponse] class PairListsPayload(ExchangeModePayloadMixin, BaseModel): - pairlists: List[Dict[str, Any]] - blacklist: List[str] + pairlists: list[dict[str, Any]] + blacklist: list[str] stake_currency: str class FreqAIModelListResponse(BaseModel): - freqaimodels: List[str] + freqaimodels: list[str] class StrategyResponse(BaseModel): @@ -485,15 +494,15 @@ class StrategyResponse(BaseModel): class AvailablePairs(BaseModel): length: int - pairs: List[str] - pair_interval: List[List[str]] + pairs: list[str] + pair_interval: list[list[str]] class PairCandlesRequest(BaseModel): pair: str timeframe: str limit: Optional[int] = None - columns: Optional[List[str]] = None + columns: Optional[list[str]] = None class PairHistoryRequest(PairCandlesRequest): @@ -507,9 +516,9 @@ class PairHistory(BaseModel): pair: str timeframe: str timeframe_ms: int - columns: List[str] - all_columns: List[str] = [] - data: SerializeAsAny[List[Any]] + columns: list[str] + all_columns: list[str] = [] + data: SerializeAsAny[list[Any]] length: int buy_signals: int sell_signals: int @@ -551,7 +560,7 @@ class BacktestResponse(BaseModel): progress: float trade_count: Optional[float] = None # TODO: Properly type backtestresult... - backtest_result: Optional[Dict[str, Any]] = None + backtest_result: Optional[dict[str, Any]] = None # TODO: This is a copy of BacktestHistoryEntryType @@ -573,13 +582,13 @@ class BacktestMetadataUpdate(BaseModel): class BacktestMarketChange(BaseModel): - columns: List[str] + columns: list[str] length: int - data: List[List[Any]] + data: list[list[Any]] class SysInfo(BaseModel): - cpu_pct: List[float] + cpu_pct: list[float] ram_pct: float diff --git a/freqtrade/rpc/api_server/api_v1.py b/freqtrade/rpc/api_server/api_v1.py index f3ffd3a9c..936022109 100644 --- a/freqtrade/rpc/api_server/api_v1.py +++ b/freqtrade/rpc/api_server/api_v1.py @@ -1,6 +1,6 @@ import logging from copy import deepcopy -from typing import List, Optional +from typing import Optional from fastapi import APIRouter, Depends, Query from fastapi.exceptions import HTTPException @@ -27,6 +27,7 @@ from freqtrade.rpc.api_server.api_schemas import ( ForceExitPayload, FreqAIModelListResponse, Health, + HyperoptLossListResponse, Locks, LocksPayload, Logs, @@ -82,7 +83,8 @@ logger = logging.getLogger(__name__) # 2.33: Additional weekly/monthly metrics # 2.34: new entries/exits/mix_tags endpoints # 2.35: pair_candles and pair_history endpoints as Post variant -API_VERSION = 2.35 +# 2.40: Add hyperopt-loss endpoint +API_VERSION = 2.40 # Public API, requires no auth. router_public = APIRouter() @@ -116,22 +118,22 @@ def count(rpc: RPC = Depends(get_rpc)): return rpc._rpc_count() -@router.get("/entries", response_model=List[Entry], tags=["info"]) +@router.get("/entries", response_model=list[Entry], tags=["info"]) def entries(pair: Optional[str] = None, rpc: RPC = Depends(get_rpc)): return rpc._rpc_enter_tag_performance(pair) -@router.get("/exits", response_model=List[Exit], tags=["info"]) +@router.get("/exits", response_model=list[Exit], tags=["info"]) def exits(pair: Optional[str] = None, rpc: RPC = Depends(get_rpc)): return rpc._rpc_exit_reason_performance(pair) -@router.get("/mix_tags", response_model=List[MixTag], tags=["info"]) +@router.get("/mix_tags", response_model=list[MixTag], tags=["info"]) def mix_tags(pair: Optional[str] = None, rpc: RPC = Depends(get_rpc)): return rpc._rpc_mix_tag_performance(pair) -@router.get("/performance", response_model=List[PerformanceEntry], tags=["info"]) +@router.get("/performance", response_model=list[PerformanceEntry], tags=["info"]) def performance(rpc: RPC = Depends(get_rpc)): return rpc._rpc_performance() @@ -167,7 +169,7 @@ def monthly(timescale: int = 3, rpc: RPC = Depends(get_rpc), config=Depends(get_ ) -@router.get("/status", response_model=List[OpenTradeSchema], tags=["info"]) +@router.get("/status", response_model=list[OpenTradeSchema], tags=["info"]) def status(rpc: RPC = Depends(get_rpc)): try: return rpc._rpc_trade_status() @@ -268,7 +270,7 @@ def blacklist_post(payload: BlacklistPayload, rpc: RPC = Depends(get_rpc)): @router.delete("/blacklist", response_model=BlacklistResponse, tags=["info", "pairlist"]) -def blacklist_delete(pairs_to_delete: List[str] = Query([]), rpc: RPC = Depends(get_rpc)): +def blacklist_delete(pairs_to_delete: list[str] = Query([]), rpc: RPC = Depends(get_rpc)): """Provide a list of pairs to delete from the blacklist""" return rpc._rpc_blacklist_delete(pairs_to_delete) @@ -295,7 +297,7 @@ def delete_lock_pair(payload: DeleteLockRequest, rpc: RPC = Depends(get_rpc)): @router.post("/locks", response_model=Locks, tags=["info", "locks"]) -def add_locks(payload: List[LocksPayload], rpc: RPC = Depends(get_rpc)): +def add_locks(payload: list[LocksPayload], rpc: RPC = Depends(get_rpc)): for lock in payload: rpc._rpc_add_lock(lock.pair, lock.until, lock.reason, lock.side) return rpc._rpc_locks() @@ -456,6 +458,30 @@ def list_exchanges(config=Depends(get_config)): } +@router.get( + "/hyperoptloss", response_model=HyperoptLossListResponse, tags=["hyperopt", "webserver"] +) +def list_hyperoptloss( + config=Depends(get_config), +): + import textwrap + + from freqtrade.resolvers.hyperopt_resolver import HyperOptLossResolver + + loss_functions = HyperOptLossResolver.search_all_objects(config, False) + loss_functions = sorted(loss_functions, key=lambda x: x["name"]) + + return { + "loss_functions": [ + { + "name": x["name"], + "description": textwrap.dedent((x["class"].__doc__ or "").strip()), + } + for x in loss_functions + ] + } + + @router.get("/freqaimodels", response_model=FreqAIModelListResponse, tags=["freqai"]) def list_freqaimodels(config=Depends(get_config)): from freqtrade.resolvers.freqaimodel_resolver import FreqaiModelResolver diff --git a/freqtrade/rpc/api_server/api_ws.py b/freqtrade/rpc/api_server/api_ws.py index ed458165e..b0e197b19 100644 --- a/freqtrade/rpc/api_server/api_ws.py +++ b/freqtrade/rpc/api_server/api_ws.py @@ -1,6 +1,6 @@ import logging import time -from typing import Any, Dict +from typing import Any from fastapi import APIRouter, Depends from fastapi.websockets import WebSocket @@ -61,7 +61,7 @@ async def channel_broadcaster(channel: WebSocketChannel, message_stream: Message await channel.send(message, use_timeout=True) -async def _process_consumer_request(request: Dict[str, Any], channel: WebSocketChannel, rpc: RPC): +async def _process_consumer_request(request: dict[str, Any], channel: WebSocketChannel, rpc: RPC): """ Validate and handle a request from a websocket consumer """ diff --git a/freqtrade/rpc/api_server/deps.py b/freqtrade/rpc/api_server/deps.py index 766673be7..997e8487b 100644 --- a/freqtrade/rpc/api_server/deps.py +++ b/freqtrade/rpc/api_server/deps.py @@ -1,4 +1,5 @@ -from typing import Any, AsyncIterator, Dict, Optional +from collections.abc import AsyncIterator +from typing import Any, Optional from uuid import uuid4 from fastapi import Depends, HTTPException @@ -35,11 +36,11 @@ async def get_rpc() -> Optional[AsyncIterator[RPC]]: raise RPCException("Bot is not in the correct state") -def get_config() -> Dict[str, Any]: +def get_config() -> dict[str, Any]: return ApiServer._config -def get_api_config() -> Dict[str, Any]: +def get_api_config() -> dict[str, Any]: return ApiServer._config["api_server"] diff --git a/freqtrade/rpc/api_server/web_ui.py b/freqtrade/rpc/api_server/web_ui.py index 6d37ec308..bf37c6fa7 100644 --- a/freqtrade/rpc/api_server/web_ui.py +++ b/freqtrade/rpc/api_server/web_ui.py @@ -21,7 +21,7 @@ async def fallback(): @router_ui.get("/ui_version", include_in_schema=False) async def ui_version(): - from freqtrade.commands.deploy_commands import read_ui_version + from freqtrade.commands.deploy_ui import read_ui_version uibase = Path(__file__).parent / "ui/installed/" version = read_ui_version(uibase) @@ -31,16 +31,6 @@ async def ui_version(): } -def is_relative_to(path: Path, base: Path) -> bool: - # Helper function simulating behaviour of is_relative_to, which was only added in python 3.9 - try: - path.relative_to(base) - return True - except ValueError: - pass - return False - - @router_ui.get("/{rest_of_path:path}", include_in_schema=False) async def index_html(rest_of_path: str): """ @@ -56,7 +46,7 @@ async def index_html(rest_of_path: str): if filename.suffix == ".js": # Force text/javascript for .js files - Circumvent faulty system configuration media_type = "application/javascript" - if filename.is_file() and is_relative_to(filename, uibase): + if filename.is_file() and filename.is_relative_to(uibase): return FileResponse(str(filename), media_type=media_type) index_file = uibase / "index.html" diff --git a/freqtrade/rpc/api_server/webserver.py b/freqtrade/rpc/api_server/webserver.py index 57f321739..c96db9981 100644 --- a/freqtrade/rpc/api_server/webserver.py +++ b/freqtrade/rpc/api_server/webserver.py @@ -116,6 +116,7 @@ class ApiServer(RPCHandler): from freqtrade.rpc.api_server.api_auth import http_basic_or_jwt_token, router_login from freqtrade.rpc.api_server.api_background_tasks import router as api_bg_tasks from freqtrade.rpc.api_server.api_backtest import router as api_backtest + from freqtrade.rpc.api_server.api_pairlists import router as api_pairlists from freqtrade.rpc.api_server.api_v1 import router as api_v1 from freqtrade.rpc.api_server.api_v1 import router_public as api_v1_public from freqtrade.rpc.api_server.api_ws import router as ws_router @@ -140,6 +141,11 @@ class ApiServer(RPCHandler): prefix="/api/v1", dependencies=[Depends(http_basic_or_jwt_token), Depends(is_webserver_mode)], ) + app.include_router( + api_pairlists, + prefix="/api/v1", + dependencies=[Depends(http_basic_or_jwt_token), Depends(is_webserver_mode)], + ) app.include_router(ws_router, prefix="/api/v1") # UI Router MUST be last! app.include_router(router_ui, prefix="") diff --git a/freqtrade/rpc/api_server/webserver_bgwork.py b/freqtrade/rpc/api_server/webserver_bgwork.py index d3cf4d2ea..dc8222490 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, Dict, Literal, Optional, TypedDict +from typing import Any, Literal, Optional, TypedDict from uuid import uuid4 from freqtrade.exchange.exchange import Exchange @@ -15,7 +15,7 @@ class JobsContainer(TypedDict): class ApiBG: # Backtesting type: Backtesting - bt: Dict[str, Any] = { + bt: dict[str, Any] = { "bt": None, "data": None, "timerange": None, @@ -24,12 +24,12 @@ class ApiBG: } bgtask_running: bool = False # Exchange - only available in webserver mode. - exchanges: Dict[str, Exchange] = {} + exchanges: dict[str, Exchange] = {} # Generic background jobs # TODO: Change this to TTLCache - jobs: Dict[str, JobsContainer] = {} + jobs: dict[str, JobsContainer] = {} # Pairlist evaluate things pairlist_running: bool = False diff --git a/freqtrade/rpc/api_server/ws/channel.py b/freqtrade/rpc/api_server/ws/channel.py index 3c1e0ce2d..5acbebe9f 100644 --- a/freqtrade/rpc/api_server/ws/channel.py +++ b/freqtrade/rpc/api_server/ws/channel.py @@ -2,8 +2,9 @@ import asyncio import logging import time from collections import deque +from collections.abc import AsyncIterator from contextlib import asynccontextmanager -from typing import Any, AsyncIterator, Deque, Dict, List, Optional, Type, Union +from typing import Any, Optional, Union from uuid import uuid4 from fastapi import WebSocketDisconnect @@ -30,7 +31,7 @@ class WebSocketChannel: self, websocket: WebSocketType, channel_id: Optional[str] = None, - serializer_cls: Type[WebSocketSerializer] = HybridJSONWebSocketSerializer, + serializer_cls: type[WebSocketSerializer] = HybridJSONWebSocketSerializer, send_throttle: float = 0.01, ): self.channel_id = channel_id if channel_id else uuid4().hex[:8] @@ -39,16 +40,16 @@ class WebSocketChannel: # Internal event to signify a closed websocket self._closed = asyncio.Event() # The async tasks created for the channel - self._channel_tasks: List[asyncio.Task] = [] + self._channel_tasks: list[asyncio.Task] = [] # Deque for average send times - self._send_times: Deque[float] = deque([], maxlen=10) + self._send_times: deque[float] = deque([], maxlen=10) # High limit defaults to 3 to start self._send_high_limit = 3 self._send_throttle = send_throttle # The subscribed message types - self._subscriptions: List[str] = [] + self._subscriptions: list[str] = [] # Wrap the WebSocket in the Serializing class self._wrapped_ws = serializer_cls(self._websocket) @@ -80,7 +81,7 @@ class WebSocketChannel: 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 + self, message: Union[WSMessageSchemaType, dict[str, Any]], use_timeout: bool = False ): """ Send a message on the wrapped websocket. If the sending @@ -153,7 +154,7 @@ class WebSocketChannel: """ return self._closed.is_set() - def set_subscriptions(self, subscriptions: List[str]) -> None: + def set_subscriptions(self, subscriptions: list[str]) -> None: """ Set which subscriptions this channel is subscribed to diff --git a/freqtrade/rpc/api_server/ws/proxy.py b/freqtrade/rpc/api_server/ws/proxy.py index ae123dd2d..a2c2cbafc 100644 --- a/freqtrade/rpc/api_server/ws/proxy.py +++ b/freqtrade/rpc/api_server/ws/proxy.py @@ -1,4 +1,4 @@ -from typing import Any, Tuple, Union +from typing import Any, Union from fastapi import WebSocket as FastAPIWebSocket from websockets.client import WebSocketClientProtocol as WebSocket @@ -20,7 +20,7 @@ class WebSocketProxy: return self._websocket @property - def remote_addr(self) -> Tuple[Any, ...]: + def remote_addr(self) -> tuple[Any, ...]: if isinstance(self._websocket, WebSocket): return self._websocket.remote_address elif isinstance(self._websocket, FastAPIWebSocket): diff --git a/freqtrade/rpc/api_server/ws/serializer.py b/freqtrade/rpc/api_server/ws/serializer.py index c07c6295f..a93a776cc 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, Dict, Union +from typing import Any, Union 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: Union[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 9855f9e06..8f7dad33b 100644 --- a/freqtrade/rpc/api_server/ws/types.py +++ b/freqtrade/rpc/api_server/ws/types.py @@ -1,8 +1,8 @@ -from typing import Any, Dict, TypeVar +from typing import Any, TypeVar from fastapi import WebSocket as FastAPIWebSocket from websockets.client import WebSocketClientProtocol as WebSocket WebSocketType = TypeVar("WebSocketType", FastAPIWebSocket, WebSocket) -MessageType = Dict[str, Any] +MessageType = dict[str, Any] diff --git a/freqtrade/rpc/api_server/ws_schemas.py b/freqtrade/rpc/api_server/ws_schemas.py index 70b12af8d..5eb8f1812 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, Dict, List, Optional, TypedDict +from typing import Any, Optional, TypedDict from pandas import DataFrame from pydantic import BaseModel, ConfigDict @@ -20,7 +20,7 @@ class WSRequestSchema(BaseArbitraryModel): class WSMessageSchemaType(TypedDict): # Type for typing to avoid doing pydantic typechecks. type: RPCMessageType - data: Optional[Dict[str, Any]] + data: Optional[dict[str, Any]] class WSMessageSchema(BaseArbitraryModel): @@ -34,7 +34,7 @@ class WSMessageSchema(BaseArbitraryModel): class WSSubscribeRequest(WSRequestSchema): type: RPCRequestType = RPCRequestType.SUBSCRIBE - data: List[RPCMessageType] + data: list[RPCMessageType] class WSWhitelistRequest(WSRequestSchema): @@ -44,7 +44,7 @@ class WSWhitelistRequest(WSRequestSchema): class WSAnalyzedDFRequest(WSRequestSchema): type: RPCRequestType = RPCRequestType.ANALYZED_DF - data: Dict[str, Any] = {"limit": 1500, "pair": None} + data: dict[str, Any] = {"limit": 1500, "pair": None} # ------------------------------ MESSAGE SCHEMAS ---------------------------- @@ -52,7 +52,7 @@ class WSAnalyzedDFRequest(WSRequestSchema): class WSWhitelistMessage(WSMessageSchema): type: RPCMessageType = RPCMessageType.WHITELIST - data: List[str] + data: list[str] class WSAnalyzedDFMessage(WSMessageSchema): diff --git a/freqtrade/rpc/external_message_consumer.py b/freqtrade/rpc/external_message_consumer.py index 7d33efea6..e08553fab 100644 --- a/freqtrade/rpc/external_message_consumer.py +++ b/freqtrade/rpc/external_message_consumer.py @@ -9,7 +9,7 @@ import asyncio import logging import socket from threading import Thread -from typing import TYPE_CHECKING, Any, Callable, Dict, List, TypedDict, Union +from typing import Any, Callable, TypedDict, Union import websockets from pydantic import ValidationError @@ -31,10 +31,6 @@ from freqtrade.rpc.api_server.ws_schemas import ( ) -if TYPE_CHECKING: - import websockets.connect - - class Producer(TypedDict): name: str host: str @@ -56,7 +52,7 @@ class ExternalMessageConsumer: other freqtrade bot's """ - def __init__(self, config: Dict[str, Any], dataprovider: DataProvider): + def __init__(self, config: dict[str, Any], dataprovider: DataProvider): self._config = config self._dp = dataprovider @@ -69,7 +65,7 @@ class ExternalMessageConsumer: self._emc_config = self._config.get("external_message_consumer", {}) self.enabled = self._emc_config.get("enabled", False) - self.producers: List[Producer] = self._emc_config.get("producers", []) + self.producers: list[Producer] = self._emc_config.get("producers", []) self.wait_timeout = self._emc_config.get("wait_timeout", 30) # in seconds self.ping_timeout = self._emc_config.get("ping_timeout", 10) # in seconds @@ -88,19 +84,19 @@ class ExternalMessageConsumer: self.topics = [RPCMessageType.WHITELIST, RPCMessageType.ANALYZED_DF] # Allow setting data for each initial request - self._initial_requests: List[WSRequestSchema] = [ + self._initial_requests: list[WSRequestSchema] = [ WSSubscribeRequest(data=self.topics), WSWhitelistRequest(), WSAnalyzedDFRequest(), ] # Specify which function to use for which RPCMessageType - self._message_handlers: Dict[str, Callable[[str, WSMessageSchema], None]] = { + self._message_handlers: dict[str, Callable[[str, WSMessageSchema], None]] = { RPCMessageType.WHITELIST: self._consume_whitelist_message, RPCMessageType.ANALYZED_DF: self._consume_analyzed_df_message, } - self._channel_streams: Dict[str, MessageStream] = {} + self._channel_streams: dict[str, MessageStream] = {} self.start() @@ -287,7 +283,7 @@ class ExternalMessageConsumer: raise def send_producer_request( - self, producer_name: str, request: Union[WSRequestSchema, Dict[str, Any]] + self, producer_name: str, request: Union[WSRequestSchema, dict[str, Any]] ): """ Publish a message to the producer's message stream to be @@ -302,7 +298,7 @@ class ExternalMessageConsumer: if channel_stream := self._channel_streams.get(producer_name): channel_stream.publish(request) - def handle_producer_message(self, producer: Producer, message: Dict[str, Any]): + def handle_producer_message(self, producer: Producer, message: dict[str, Any]): """ Handles external messages from a Producer """ diff --git a/freqtrade/rpc/fiat_convert.py b/freqtrade/rpc/fiat_convert.py index 20f8df468..f4589a7da 100644 --- a/freqtrade/rpc/fiat_convert.py +++ b/freqtrade/rpc/fiat_convert.py @@ -5,7 +5,7 @@ e.g BTC to USD import logging from datetime import datetime -from typing import Any, Dict, List +from typing import Any from cachetools import TTLCache from requests.exceptions import RequestException @@ -41,7 +41,7 @@ class CryptoToFiatConverter(LoggingMixin): __instance = None - _coinlistings: List[Dict] = [] + _coinlistings: list[dict] = [] _backoff: float = 0.0 def __new__(cls, *args: Any, **kwargs: Any) -> Any: diff --git a/freqtrade/rpc/rpc.py b/freqtrade/rpc/rpc.py index 99fcaf7d7..c3bcfd9f1 100644 --- a/freqtrade/rpc/rpc.py +++ b/freqtrade/rpc/rpc.py @@ -4,9 +4,10 @@ This module contains class to define a RPC communications import logging 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, Dict, Generator, List, Optional, Sequence, Tuple, Union +from typing import Any, Optional, Union import psutil from dateutil.relativedelta import relativedelta @@ -112,7 +113,7 @@ class RPC: @staticmethod def _rpc_show_config( config, botstate: Union[State, str], strategy_version: Optional[str] = None - ) -> Dict[str, Any]: + ) -> dict[str, Any]: """ Return a dict of config options. Explicitly does NOT return the full config to avoid leakage of sensitive @@ -167,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: Optional[list[int]] = 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 @@ -270,8 +271,8 @@ class RPC: def _rpc_status_table( self, stake_currency: str, fiat_display_currency: str - ) -> Tuple[List, List, float]: - trades: List[Trade] = Trade.get_open_trades() + ) -> tuple[list, list, float]: + trades: list[Trade] = Trade.get_open_trades() nonspot = self._config.get("trading_mode", TradingMode.SPOT) != TradingMode.SPOT if not trades: raise RPCException("no active trade") @@ -354,7 +355,7 @@ class RPC: stake_currency: str, fiat_display_currency: str, timeunit: str = "days", - ) -> Dict[str, Any]: + ) -> dict[str, Any]: """ :param timeunit: Valid entries are 'days', 'weeks', 'months' """ @@ -373,7 +374,7 @@ class RPC: if not (isinstance(timescale, int) and timescale > 0): raise RPCException("timescale must be an integer greater than 0") - profit_units: Dict[date, Dict] = {} + profit_units: dict[date, dict] = {} daily_stake = self._freqtrade.wallets.get_total_stake_amount() for day in range(0, timescale): @@ -424,7 +425,7 @@ class RPC: "data": data, } - def _rpc_trade_history(self, limit: int, offset: int = 0, order_by_id: bool = False) -> Dict: + def _rpc_trade_history(self, limit: int, offset: int = 0, order_by_id: bool = False) -> dict: """Returns the X last trades""" order_by: Any = Trade.id if order_by_id else Trade.close_date.desc() if limit: @@ -451,7 +452,7 @@ class RPC: "total_trades": total_trades, } - def _rpc_stats(self) -> Dict[str, Any]: + def _rpc_stats(self) -> dict[str, Any]: """ Generate generic stats for trades in database """ @@ -466,7 +467,7 @@ class RPC: trades = Trade.get_trades([Trade.is_open.is_(False)], include_orders=False) # Duration - dur: Dict[str, List[float]] = {"wins": [], "draws": [], "losses": []} + dur: dict[str, list[float]] = {"wins": [], "draws": [], "losses": []} # Exit reason exit_reasons = {} for trade in trades: @@ -487,7 +488,7 @@ class RPC: def _rpc_trade_statistics( self, stake_currency: str, fiat_display_currency: str, start_date: Optional[datetime] = None - ) -> Dict[str, Any]: + ) -> dict[str, Any]: """Returns cumulative profit statistics""" start_date = datetime.fromtimestamp(0) if start_date is None else start_date @@ -670,7 +671,7 @@ class RPC: def __balance_get_est_stake( self, coin: str, stake_currency: str, amount: float, balance: Wallet, tickers - ) -> Tuple[float, float]: + ) -> tuple[float, float]: est_stake = 0.0 est_bot_stake = 0.0 if coin == stake_currency: @@ -690,9 +691,9 @@ class RPC: return est_stake, est_bot_stake - def _rpc_balance(self, stake_currency: str, fiat_display_currency: str) -> Dict: + def _rpc_balance(self, stake_currency: str, fiat_display_currency: str) -> dict: """Returns current account balance per crypto""" - currencies: List[Dict] = [] + currencies: list[dict] = [] total = 0.0 total_bot = 0.0 try: @@ -700,8 +701,8 @@ class RPC: except ExchangeError: raise RPCException("Error getting current tickers.") - open_trades: List[Trade] = Trade.get_open_trades() - open_assets: Dict[str, Trade] = {t.safe_base_currency: t for t in open_trades} + open_trades: list[Trade] = Trade.get_open_trades() + open_assets: dict[str, Trade] = {t.safe_base_currency: t for t in open_trades} self._freqtrade.wallets.update(require_update=False) starting_capital = self._freqtrade.wallets.get_starting_balance() starting_cap_fiat = ( @@ -805,7 +806,7 @@ class RPC: "note": "Simulated balances" if self._freqtrade.config["dry_run"] else "", } - def _rpc_start(self) -> Dict[str, str]: + def _rpc_start(self) -> dict[str, str]: """Handler for start""" if self._freqtrade.state == State.RUNNING: return {"status": "already running"} @@ -813,7 +814,7 @@ class RPC: self._freqtrade.state = State.RUNNING return {"status": "starting trader ..."} - def _rpc_stop(self) -> Dict[str, str]: + def _rpc_stop(self) -> dict[str, str]: """Handler for stop""" if self._freqtrade.state == State.RUNNING: self._freqtrade.state = State.STOPPED @@ -821,12 +822,12 @@ class RPC: return {"status": "already stopped"} - def _rpc_reload_config(self) -> Dict[str, str]: + def _rpc_reload_config(self) -> dict[str, str]: """Handler for reload_config.""" self._freqtrade.state = State.RELOAD_CONFIG return {"status": "Reloading config ..."} - def _rpc_stopentry(self) -> Dict[str, str]: + def _rpc_stopentry(self) -> dict[str, str]: """ Handler to stop buying, but handle open trades gracefully. """ @@ -837,7 +838,7 @@ class RPC: return {"status": "No more entries will occur from now. Run /reload_config to reset."} - def _rpc_reload_trade_from_exchange(self, trade_id: int) -> Dict[str, str]: + def _rpc_reload_trade_from_exchange(self, trade_id: int) -> dict[str, str]: """ Handler for reload_trade_from_exchange. Reloads a trade from it's orders, should manual interaction have happened. @@ -901,7 +902,7 @@ class RPC: def _rpc_force_exit( self, trade_id: str, ordertype: Optional[str] = None, *, amount: Optional[float] = None - ) -> Dict[str, str]: + ) -> dict[str, str]: """ Handler for forceexit . Sells the given trade at current price @@ -1051,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, Union[str, int]]: """ Handler for delete . Delete the given trade and close eventually existing open orders. @@ -1092,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: Optional[str]) -> list[dict[str, Any]]: # Query for trade trade = Trade.get_trades(trade_filter=[Trade.id == trade_id]).first() if trade is None: @@ -1118,7 +1119,7 @@ class RPC: for data_entry in custom_data ] - def _rpc_performance(self) -> List[Dict[str, Any]]: + def _rpc_performance(self) -> list[dict[str, Any]]: """ Handler for performance. Shows a performance statistic from finished trades @@ -1127,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: Optional[str]) -> 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: Optional[str]) -> 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: Optional[str]) -> list[dict[str, Any]]: """ Handler for mix tag (enter_tag + exit_reason) performance. Shows a performance statistic from finished trades @@ -1150,7 +1151,7 @@ class RPC: return mix_tags - def _rpc_count(self) -> Dict[str, float]: + def _rpc_count(self) -> dict[str, float]: """Returns the number of trades running""" if self._freqtrade.state != State.RUNNING: raise RPCException("trader is not running") @@ -1166,7 +1167,7 @@ class RPC: "total_stake": sum((trade.open_rate * trade.amount) for trade in trades), } - def _rpc_locks(self) -> Dict[str, Any]: + def _rpc_locks(self) -> dict[str, Any]: """Returns the current locks""" locks = PairLocks.get_pair_locks(None) @@ -1174,7 +1175,7 @@ class RPC: def _rpc_delete_lock( self, lockid: Optional[int] = None, pair: Optional[str] = None - ) -> Dict[str, Any]: + ) -> dict[str, Any]: """Delete specific lock(s)""" locks: Sequence[PairLock] = [] @@ -1202,7 +1203,7 @@ class RPC: ) return lock - def _rpc_whitelist(self) -> Dict: + def _rpc_whitelist(self) -> dict: """Returns the currently active whitelist""" res = { "method": self._freqtrade.pairlists.name_list, @@ -1211,7 +1212,7 @@ class RPC: } return res - def _rpc_blacklist_delete(self, delete: List[str]) -> Dict: + def _rpc_blacklist_delete(self, delete: list[str]) -> dict: """Removes pairs from currently active blacklist""" errors = {} for pair in delete: @@ -1223,7 +1224,7 @@ class RPC: resp["errors"] = errors return resp - def _rpc_blacklist(self, add: Optional[List[str]] = None) -> Dict: + def _rpc_blacklist(self, add: Optional[list[str]] = None) -> dict: """Returns the currently active blacklist""" errors = {} if add: @@ -1248,7 +1249,7 @@ class RPC: return res @staticmethod - def _rpc_get_logs(limit: Optional[int]) -> Dict[str, Any]: + def _rpc_get_logs(limit: Optional[int]) -> dict[str, Any]: """Returns the last X logs""" if limit: buffer = bufferHandler.buffer[-limit:] @@ -1272,7 +1273,7 @@ class RPC: return {"log_count": len(records), "logs": records} - def _rpc_edge(self) -> List[Dict[str, Any]]: + def _rpc_edge(self) -> list[dict[str, Any]]: """Returns information related to Edge""" if not self._freqtrade.edge: raise RPCException("Edge is not enabled.") @@ -1285,8 +1286,8 @@ class RPC: timeframe: str, dataframe: DataFrame, last_analyzed: datetime, - selected_cols: Optional[List[str]], - ) -> Dict[str, Any]: + selected_cols: Optional[list[str]], + ) -> dict[str, Any]: has_content = len(dataframe) != 0 dataframe_columns = list(dataframe.columns) signals = { @@ -1354,8 +1355,8 @@ class RPC: return res def _rpc_analysed_dataframe( - self, pair: str, timeframe: str, limit: Optional[int], selected_cols: Optional[List[str]] - ) -> Dict[str, Any]: + self, pair: str, timeframe: str, limit: Optional[int], selected_cols: Optional[list[str]] + ) -> dict[str, Any]: """Analyzed dataframe in Dict form""" _data, last_analyzed = self.__rpc_analysed_dataframe_raw(pair, timeframe, limit) @@ -1365,7 +1366,7 @@ class RPC: def __rpc_analysed_dataframe_raw( self, pair: str, timeframe: str, limit: Optional[int] - ) -> Tuple[DataFrame, datetime]: + ) -> tuple[DataFrame, datetime]: """ Get the dataframe and last analyze from the dataprovider @@ -1382,8 +1383,8 @@ class RPC: return _data, last_analyzed def _ws_all_analysed_dataframes( - self, pairlist: List[str], limit: Optional[int] - ) -> Generator[Dict[str, Any], None, None]: + self, pairlist: list[str], limit: Optional[int] + ) -> Generator[dict[str, Any], None, None]: """ Get the analysed dataframes of each pair in the pairlist. If specified, only return the most recent `limit` candles for @@ -1414,8 +1415,8 @@ class RPC: @staticmethod def _rpc_analysed_history_full( - config: Config, pair: str, timeframe: str, exchange, selected_cols: Optional[List[str]] - ) -> Dict[str, Any]: + config: Config, pair: str, timeframe: str, exchange, selected_cols: Optional[list[str]] + ) -> dict[str, Any]: timerange_parsed = TimeRange.parse_timerange(config.get("timerange")) from freqtrade.data.converter import trim_dataframe @@ -1454,7 +1455,7 @@ class RPC: selected_cols, ) - def _rpc_plot_config(self) -> Dict[str, Any]: + def _rpc_plot_config(self) -> dict[str, Any]: if ( self._freqtrade.strategy.plot_config and "subplots" not in self._freqtrade.strategy.plot_config @@ -1463,7 +1464,7 @@ class RPC: return self._freqtrade.strategy.plot_config @staticmethod - def _rpc_plot_config_with_strategy(config: Config) -> Dict[str, Any]: + def _rpc_plot_config_with_strategy(config: Config) -> dict[str, Any]: from freqtrade.resolvers.strategy_resolver import StrategyResolver strategy = StrategyResolver.load_strategy(config) @@ -1475,15 +1476,15 @@ class RPC: return strategy.plot_config @staticmethod - def _rpc_sysinfo() -> Dict[str, Any]: + def _rpc_sysinfo() -> dict[str, Any]: return { "cpu_pct": psutil.cpu_percent(interval=1, percpu=True), "ram_pct": psutil.virtual_memory().percent, } - def health(self) -> Dict[str, Optional[Union[str, int]]]: + def health(self) -> dict[str, Optional[Union[str, int]]]: last_p = self._freqtrade.last_process - res: Dict[str, Union[None, str, int]] = { + res: dict[str, Union[None, str, int]] = { "last_process": None, "last_process_loc": None, "last_process_ts": None, diff --git a/freqtrade/rpc/rpc_manager.py b/freqtrade/rpc/rpc_manager.py index f62feea3e..0a4f48a35 100644 --- a/freqtrade/rpc/rpc_manager.py +++ b/freqtrade/rpc/rpc_manager.py @@ -4,7 +4,6 @@ This module contains class to manage RPC communications (Telegram, API, ...) import logging from collections import deque -from typing import List from freqtrade.constants import Config from freqtrade.enums import NO_ECHO_MESSAGES, RPCMessageType @@ -22,7 +21,7 @@ class RPCManager: def __init__(self, freqtrade) -> None: """Initializes all enabled rpc modules""" - self.registered_modules: List[RPCHandler] = [] + self.registered_modules: list[RPCHandler] = [] self._rpc = RPC(freqtrade) config = freqtrade.config # Enable telegram diff --git a/freqtrade/rpc/rpc_types.py b/freqtrade/rpc/rpc_types.py index e5f4f93c9..0fa4cea75 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, List, Literal, Optional, TypedDict, Union +from typing import Any, Literal, Optional, TypedDict, Union from freqtrade.constants import PairWithTimeframe from freqtrade.enums import RPCMessageType @@ -43,7 +43,7 @@ class RPCProtectionMsg(RPCSendMsgBase): class RPCWhitelistMsg(RPCSendMsgBase): type: Literal[RPCMessageType.WHITELIST] - data: List[str] + data: list[str] class __RPCEntryExitMsgBase(RPCSendMsgBase): diff --git a/freqtrade/rpc/telegram.py b/freqtrade/rpc/telegram.py index 46939daed..2de4499f1 100644 --- a/freqtrade/rpc/telegram.py +++ b/freqtrade/rpc/telegram.py @@ -8,6 +8,7 @@ import asyncio import json import logging import re +from collections.abc import Coroutine from copy import deepcopy from dataclasses import dataclass from datetime import date, datetime, timedelta @@ -16,7 +17,7 @@ from html import escape from itertools import chain from math import isnan from threading import Thread -from typing import Any, Callable, Coroutine, Dict, List, Literal, Optional, Union +from typing import Any, Callable, Literal, Optional, Union from tabulate import tabulate from telegram import ( @@ -145,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[Union[str, KeyboardButton]]] = [ ["/daily", "/profit", "/balance"], ["/status", "/status table", "/performance"], ["/count", "/start", "/stop", "/help"], @@ -154,7 +155,7 @@ class Telegram(RPCHandler): # TODO: DRY! - its not good to list all valid cmds here. But otherwise # this needs refactoring of the whole telegram module (same # problem in _help()). - valid_keys: List[str] = [ + valid_keys: list[str] = [ r"/start$", r"/stop$", r"/status$", @@ -594,16 +595,16 @@ class Telegram(RPCHandler): else: return "\N{CROSS MARK}" - def _prepare_order_details(self, filled_orders: List, quote_currency: str, is_open: bool): + def _prepare_order_details(self, filled_orders: list, quote_currency: str, is_open: bool): """ Prepare details of trade with entry adjustment enabled """ - lines_detail: List[str] = [] + lines_detail: list[str] = [] if len(filled_orders) > 0: first_avg = filled_orders[0]["safe_price"] order_nr = 0 for order in filled_orders: - lines: List[str] = [] + lines: list[str] = [] if order["is_open"] is True: continue order_nr += 1 @@ -662,7 +663,7 @@ class Telegram(RPCHandler): lines.extend(lines_detail if lines_detail else "") await self.__send_order_msg(lines, r) - async def __send_order_msg(self, lines: List[str], r: Dict[str, Any]) -> None: + async def __send_order_msg(self, lines: list[str], r: dict[str, Any]) -> None: """ Send status message. """ @@ -805,7 +806,7 @@ class Telegram(RPCHandler): await self.__send_status_msg(lines, r) - async def __send_status_msg(self, lines: List[str], r: Dict[str, Any]) -> None: + async def __send_status_msg(self, lines: list[str], r: dict[str, Any]) -> None: """ Send status message. """ @@ -1344,8 +1345,8 @@ class Telegram(RPCHandler): @staticmethod def _layout_inline_keyboard( - buttons: List[InlineKeyboardButton], cols=3 - ) -> List[List[InlineKeyboardButton]]: + buttons: list[InlineKeyboardButton], cols=3 + ) -> list[list[InlineKeyboardButton]]: return [buttons[i : i + cols] for i in range(0, len(buttons), cols)] @authorized_only @@ -1689,7 +1690,7 @@ class Telegram(RPCHandler): """ await self.send_blacklist_msg(self._rpc._rpc_blacklist(context.args)) - async def send_blacklist_msg(self, blacklist: Dict): + async def send_blacklist_msg(self, blacklist: dict): errmsgs = [] for _, error in blacklist["errors"].items(): errmsgs.append(f"Error: {error['error_msg']}") @@ -1998,7 +1999,7 @@ class Telegram(RPCHandler): msg: str, parse_mode: str = ParseMode.MARKDOWN, disable_notification: bool = False, - keyboard: Optional[List[List[InlineKeyboardButton]]] = None, + keyboard: Optional[list[list[InlineKeyboardButton]]] = None, callback_path: str = "", reload_able: bool = False, query: Optional[CallbackQuery] = None, diff --git a/freqtrade/rpc/webhook.py b/freqtrade/rpc/webhook.py index d67d654f0..640d5ea63 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, Dict, Optional +from typing import Any, Optional 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) -> Optional[dict[str, Any]]: 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 8362b754a..ffc4abb50 100644 --- a/freqtrade/strategy/hyper.py +++ b/freqtrade/strategy/hyper.py @@ -4,8 +4,9 @@ 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, Dict, Iterator, List, Optional, Tuple, Type, Union +from typing import Any, Optional, Union from freqtrade.constants import Config from freqtrade.exceptions import OperationalException @@ -28,9 +29,9 @@ class HyperStrategyMixin: Initialize hyperoptable strategy mixin. """ self.config = config - self.ft_buy_params: List[BaseParameter] = [] - self.ft_sell_params: List[BaseParameter] = [] - self.ft_protection_params: List[BaseParameter] = [] + self.ft_buy_params: list[BaseParameter] = [] + self.ft_sell_params: list[BaseParameter] = [] + self.ft_protection_params: list[BaseParameter] = [] params = self.load_params_from_file() params = params.get("params", {}) @@ -39,7 +40,7 @@ class HyperStrategyMixin: def enumerate_parameters( self, category: Optional[str] = None - ) -> Iterator[Tuple[str, BaseParameter]]: + ) -> Iterator[tuple[str, BaseParameter]]: """ Find all optimizable parameters and return (name, attr) iterator. :param category: @@ -59,9 +60,9 @@ class HyperStrategyMixin: yield par.name, par @classmethod - def detect_all_parameters(cls) -> Dict: + def detect_all_parameters(cls) -> dict: """Detect all parameters and return them as a list""" - params: Dict[str, Any] = { + params: dict[str, Any] = { "buy": list(detect_parameters(cls, "buy")), "sell": list(detect_parameters(cls, "sell")), "protection": list(detect_parameters(cls, "protection")), @@ -124,7 +125,7 @@ class HyperStrategyMixin: self._ft_load_params(sell_params, "sell", hyperopt) self._ft_load_params(protection_params, "protection", hyperopt) - def load_params_from_file(self) -> Dict: + def load_params_from_file(self) -> dict: filename_str = getattr(self, "__file__", "") if not filename_str: return {} @@ -144,14 +145,14 @@ class HyperStrategyMixin: return {} - def _ft_load_params(self, params: Dict, space: str, hyperopt: bool = False) -> None: + def _ft_load_params(self, params: dict, space: str, hyperopt: bool = False) -> None: """ Set optimizable parameter values. :param params: Dictionary with new parameter values. """ if not params: logger.info(f"No params for {space} found, using default values.") - param_container: List[BaseParameter] = getattr(self, f"ft_{space}_params") + param_container: list[BaseParameter] = getattr(self, f"ft_{space}_params") for attr_name, attr in detect_parameters(self, space): attr.name = attr_name @@ -173,11 +174,11 @@ class HyperStrategyMixin: else: logger.info(f"Strategy Parameter(default): {attr_name} = {attr.value}") - def get_no_optimize_params(self) -> Dict[str, Dict]: + def get_no_optimize_params(self) -> dict[str, dict]: """ Returns list of Parameters that are not part of the current optimize job """ - params: Dict[str, Dict] = { + params: dict[str, dict] = { "buy": {}, "sell": {}, "protection": {}, @@ -189,8 +190,8 @@ class HyperStrategyMixin: def detect_parameters( - obj: Union[HyperStrategyMixin, Type[HyperStrategyMixin]], category: str -) -> Iterator[Tuple[str, BaseParameter]]: + obj: Union[HyperStrategyMixin, type[HyperStrategyMixin]], category: str +) -> Iterator[tuple[str, BaseParameter]]: """ Detect all parameters for 'category' for "obj" :param obj: Strategy object or class diff --git a/freqtrade/strategy/informative_decorator.py b/freqtrade/strategy/informative_decorator.py index 12f4281d2..cdb840512 100644 --- a/freqtrade/strategy/informative_decorator.py +++ b/freqtrade/strategy/informative_decorator.py @@ -1,5 +1,5 @@ from dataclasses import dataclass -from typing import Any, Callable, Dict, Optional, Union +from typing import Any, Callable, Optional, Union from pandas import DataFrame @@ -73,7 +73,7 @@ def informative( return decorator -def __get_pair_formats(market: Optional[Dict[str, Any]]) -> Dict[str, str]: +def __get_pair_formats(market: Optional[dict[str, Any]]) -> dict[str, str]: if not market: return {} base = market["base"] @@ -86,7 +86,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: Optional[dict[str, Any]] = 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 3893ee42b..a4bb007f1 100644 --- a/freqtrade/strategy/interface.py +++ b/freqtrade/strategy/interface.py @@ -8,7 +8,7 @@ from abc import ABC, abstractmethod from collections import OrderedDict from datetime import datetime, timedelta, timezone from math import isinf, isnan -from typing import Dict, List, Optional, Tuple, Union +from typing import Optional, Union from pandas import DataFrame @@ -63,9 +63,9 @@ class IStrategy(ABC, HyperStrategyMixin): # Version 3 - First version with short and leverage support INTERFACE_VERSION: int = 3 - _ft_params_from_file: Dict + _ft_params_from_file: dict # associated minimal roi - minimal_roi: Dict = {} + minimal_roi: dict = {} # associated stoploss stoploss: float @@ -87,7 +87,7 @@ class IStrategy(ABC, HyperStrategyMixin): timeframe: str # Optional order types - order_types: Dict = { + order_types: dict = { "entry": "limit", "exit": "limit", "stoploss": "limit", @@ -96,7 +96,7 @@ class IStrategy(ABC, HyperStrategyMixin): } # Optional time in force - order_time_in_force: Dict = { + order_time_in_force: dict = { "entry": "GTC", "exit": "GTC", } @@ -123,7 +123,7 @@ class IStrategy(ABC, HyperStrategyMixin): startup_candle_count: int = 0 # Protections - protections: List = [] + protections: list = [] # Class level variables (intentional) containing # the dataprovider (dp) (access to other candles, historic data, ...) @@ -136,24 +136,24 @@ class IStrategy(ABC, HyperStrategyMixin): __source__: str = "" # Definition of plot_config. See plotting documentation for more details. - plot_config: Dict = {} + plot_config: dict = {} # A self set parameter that represents the market direction. filled from configuration market_direction: MarketDirection = MarketDirection.NONE # Global cache dictionary - _cached_grouped_trades_per_pair: Dict[ - str, OrderedDict[Tuple[datetime, datetime], DataFrame] + _cached_grouped_trades_per_pair: dict[ + str, OrderedDict[tuple[datetime, datetime], DataFrame] ] = {} def __init__(self, config: Config) -> None: self.config = config # Dict to determine if analysis is necessary - self._last_candle_seen_per_pair: Dict[str, datetime] = {} + self._last_candle_seen_per_pair: dict[str, datetime] = {} super().__init__(config) # Gather informative pairs from @informative-decorated methods. - self._ft_informative: List[Tuple[InformativeData, PopulateIndicators]] = [] + self._ft_informative: list[tuple[InformativeData, PopulateIndicators]] = [] for attr_name in dir(self.__class__): cls_method = getattr(self.__class__, attr_name) if not callable(cls_method): @@ -627,7 +627,7 @@ class IStrategy(ABC, HyperStrategyMixin): 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. @@ -761,7 +761,7 @@ class IStrategy(ABC, HyperStrategyMixin): return df def feature_engineering_expand_all( - self, dataframe: DataFrame, period: int, metadata: Dict, **kwargs + self, dataframe: DataFrame, period: int, metadata: dict, **kwargs ) -> DataFrame: """ *Only functional with FreqAI enabled strategies* @@ -789,7 +789,7 @@ class IStrategy(ABC, HyperStrategyMixin): return dataframe def feature_engineering_expand_basic( - self, dataframe: DataFrame, metadata: Dict, **kwargs + self, dataframe: DataFrame, metadata: dict, **kwargs ) -> DataFrame: """ *Only functional with FreqAI enabled strategies* @@ -820,7 +820,7 @@ class IStrategy(ABC, HyperStrategyMixin): return dataframe def feature_engineering_standard( - self, dataframe: DataFrame, metadata: Dict, **kwargs + self, dataframe: DataFrame, metadata: dict, **kwargs ) -> DataFrame: """ *Only functional with FreqAI enabled strategies* @@ -845,7 +845,7 @@ class IStrategy(ABC, HyperStrategyMixin): """ return dataframe - def set_freqai_targets(self, dataframe: DataFrame, metadata: Dict, **kwargs) -> DataFrame: + def set_freqai_targets(self, dataframe: DataFrame, metadata: dict, **kwargs) -> DataFrame: """ *Only functional with FreqAI enabled strategies* Required function to set the targets for the model. @@ -880,7 +880,7 @@ class IStrategy(ABC, HyperStrategyMixin): current_entry_profit: float, current_exit_profit: float, **kwargs, - ) -> Tuple[Optional[float], str]: + ) -> tuple[Optional[float], str]: """ wrapper around adjust_trade_position to handle the return value """ @@ -1112,7 +1112,7 @@ class IStrategy(ABC, HyperStrategyMixin): logger.warning("Empty dataframe for pair %s", pair) return - def analyze(self, pairs: List[str]) -> None: + def analyze(self, pairs: list[str]) -> None: """ Analyze all pairs using analyze_pair(). :param pairs: List of pairs to analyze @@ -1121,7 +1121,7 @@ class IStrategy(ABC, HyperStrategyMixin): self.analyze_pair(pair) @staticmethod - def preserve_df(dataframe: DataFrame) -> Tuple[int, float, datetime]: + def preserve_df(dataframe: DataFrame) -> tuple[int, float, datetime]: """keep some data for dataframes""" return len(dataframe), dataframe["close"].iloc[-1], dataframe["date"].iloc[-1] @@ -1133,8 +1133,6 @@ class IStrategy(ABC, HyperStrategyMixin): message = "" if dataframe is None: message = "No dataframe returned (return statement missing?)." - elif "enter_long" not in dataframe: - message = "enter_long/buy column not set." elif df_len != len(dataframe): message = message_template.format("length") elif df_close != dataframe["close"].iloc[-1]: @@ -1152,7 +1150,7 @@ class IStrategy(ABC, HyperStrategyMixin): pair: str, timeframe: str, dataframe: DataFrame, - ) -> Tuple[Optional[DataFrame], Optional[datetime]]: + ) -> tuple[Optional[DataFrame], Optional[datetime]]: """ Calculates current signal based based on the entry order or exit order columns of the dataframe. @@ -1185,7 +1183,7 @@ class IStrategy(ABC, HyperStrategyMixin): def get_exit_signal( self, pair: str, timeframe: str, dataframe: DataFrame, is_short: Optional[bool] = None - ) -> Tuple[bool, bool, Optional[str]]: + ) -> tuple[bool, bool, Optional[str]]: """ Calculates current exit signal based based on the dataframe columns of the dataframe. @@ -1206,7 +1204,7 @@ class IStrategy(ABC, HyperStrategyMixin): exit_ = latest.get(SignalType.EXIT_SHORT.value, 0) == 1 else: - enter = latest[SignalType.ENTER_LONG.value] == 1 + enter = latest.get(SignalType.ENTER_LONG.value, 0) == 1 exit_ = latest.get(SignalType.EXIT_LONG.value, 0) == 1 exit_tag = latest.get(SignalTagType.EXIT_TAG.value, None) # Tags can be None, which does not resolve to False. @@ -1221,7 +1219,7 @@ class IStrategy(ABC, HyperStrategyMixin): pair: str, timeframe: str, dataframe: DataFrame, - ) -> Tuple[Optional[SignalDirection], Optional[str]]: + ) -> tuple[Optional[SignalDirection], Optional[str]]: """ Calculates current entry signal based based on the dataframe signals columns of the dataframe. @@ -1235,7 +1233,7 @@ class IStrategy(ABC, HyperStrategyMixin): if latest is None or latest_date is None: return None, None - enter_long = latest[SignalType.ENTER_LONG.value] == 1 + enter_long = latest.get(SignalType.ENTER_LONG.value, 0) == 1 exit_long = latest.get(SignalType.EXIT_LONG.value, 0) == 1 enter_short = latest.get(SignalType.ENTER_SHORT.value, 0) == 1 exit_short = latest.get(SignalType.EXIT_SHORT.value, 0) == 1 @@ -1292,7 +1290,7 @@ class IStrategy(ABC, HyperStrategyMixin): low: Optional[float] = None, high: Optional[float] = None, force_stoploss: float = 0, - ) -> List[ExitCheckTuple]: + ) -> list[ExitCheckTuple]: """ This function evaluates if one of the conditions required to trigger an exit order has been reached, which can either be a stop-loss, ROI or exit-signal. @@ -1301,7 +1299,7 @@ class IStrategy(ABC, HyperStrategyMixin): :param force_stoploss: Externally provided stoploss :return: List of exit reasons - or empty list. """ - exits: List[ExitCheckTuple] = [] + exits: list[ExitCheckTuple] = [] current_rate = rate current_profit = trade.calc_profit_ratio(current_rate) current_profit_best = current_profit @@ -1520,7 +1518,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[Optional[int], Optional[float]]: """ Based on trade duration defines the ROI entry that may have been reached. :param trade_dur: trade duration in minutes @@ -1573,7 +1571,7 @@ class IStrategy(ABC, HyperStrategyMixin): pair=trade.pair, trade=trade, order=order, current_time=current_time ) - def advise_all_indicators(self, data: Dict[str, DataFrame]) -> Dict[str, DataFrame]: + def advise_all_indicators(self, data: dict[str, DataFrame]) -> dict[str, DataFrame]: """ Populates indicators for given candle (OHLCV) data (for multiple pairs) Does not run advise_entry or advise_exit! @@ -1611,7 +1609,7 @@ class IStrategy(ABC, HyperStrategyMixin): config["timeframe"] = self.timeframe pair = metadata["pair"] # TODO: slice trades to size of dataframe for faster backtesting - cached_grouped_trades: OrderedDict[Tuple[datetime, datetime], DataFrame] = ( + cached_grouped_trades: OrderedDict[tuple[datetime, datetime], DataFrame] = ( self._cached_grouped_trades_per_pair.get(pair, OrderedDict()) ) dataframe, cached_grouped_trades = populate_dataframe_with_trades( diff --git a/freqtrade/strategy/parameters.py b/freqtrade/strategy/parameters.py index 79091e2d6..82e930667 100644 --- a/freqtrade/strategy/parameters.py +++ b/freqtrade/strategy/parameters.py @@ -5,8 +5,9 @@ This module defines a base class for auto-hyperoptable strategies. import logging from abc import ABC, abstractmethod +from collections.abc import Sequence from contextlib import suppress -from typing import Any, Optional, Sequence, Union +from typing import Any, Optional, Union from freqtrade.enums import HyperoptState from freqtrade.optimize.hyperopt_tools import HyperoptStateContainer diff --git a/freqtrade/system/__init__.py b/freqtrade/system/__init__.py new file mode 100644 index 000000000..10a23aa4b --- /dev/null +++ b/freqtrade/system/__init__.py @@ -0,0 +1,7 @@ +"""system specific and performance tuning""" + +from freqtrade.system.asyncio_config import asyncio_setup +from freqtrade.system.gc_setup import gc_set_threshold + + +__all__ = ["asyncio_setup", "gc_set_threshold"] diff --git a/freqtrade/configuration/asyncio_config.py b/freqtrade/system/asyncio_config.py similarity index 100% rename from freqtrade/configuration/asyncio_config.py rename to freqtrade/system/asyncio_config.py diff --git a/freqtrade/util/gc_setup.py b/freqtrade/system/gc_setup.py similarity index 100% rename from freqtrade/util/gc_setup.py rename to freqtrade/system/gc_setup.py diff --git a/freqtrade/templates/FreqaiExampleHybridStrategy.py b/freqtrade/templates/FreqaiExampleHybridStrategy.py index e41fbac56..908c4b174 100644 --- a/freqtrade/templates/FreqaiExampleHybridStrategy.py +++ b/freqtrade/templates/FreqaiExampleHybridStrategy.py @@ -1,5 +1,4 @@ import logging -from typing import Dict import numpy as np # noqa import pandas as pd # noqa @@ -98,7 +97,7 @@ class FreqaiExampleHybridStrategy(IStrategy): exit_short_rsi = IntParameter(low=1, high=50, default=30, space="buy", optimize=True, load=True) def feature_engineering_expand_all( - self, dataframe: DataFrame, period: int, metadata: Dict, **kwargs + self, dataframe: DataFrame, period: int, metadata: dict, **kwargs ) -> DataFrame: """ *Only functional with FreqAI enabled strategies* @@ -151,7 +150,7 @@ class FreqaiExampleHybridStrategy(IStrategy): return dataframe def feature_engineering_expand_basic( - self, dataframe: DataFrame, metadata: Dict, **kwargs + self, dataframe: DataFrame, metadata: dict, **kwargs ) -> DataFrame: """ *Only functional with FreqAI enabled strategies* @@ -185,7 +184,7 @@ class FreqaiExampleHybridStrategy(IStrategy): return dataframe def feature_engineering_standard( - self, dataframe: DataFrame, metadata: Dict, **kwargs + self, dataframe: DataFrame, metadata: dict, **kwargs ) -> DataFrame: """ *Only functional with FreqAI enabled strategies* @@ -212,7 +211,7 @@ class FreqaiExampleHybridStrategy(IStrategy): dataframe["%-hour_of_day"] = dataframe["date"].dt.hour return dataframe - def set_freqai_targets(self, dataframe: DataFrame, metadata: Dict, **kwargs) -> DataFrame: + def set_freqai_targets(self, dataframe: DataFrame, metadata: dict, **kwargs) -> DataFrame: """ *Only functional with FreqAI enabled strategies* Required function to set the targets for the model. diff --git a/freqtrade/templates/FreqaiExampleStrategy.py b/freqtrade/templates/FreqaiExampleStrategy.py index a16775163..b07487496 100644 --- a/freqtrade/templates/FreqaiExampleStrategy.py +++ b/freqtrade/templates/FreqaiExampleStrategy.py @@ -1,6 +1,5 @@ import logging from functools import reduce -from typing import Dict import talib.abstract as ta from pandas import DataFrame @@ -46,7 +45,7 @@ class FreqaiExampleStrategy(IStrategy): can_short = True def feature_engineering_expand_all( - self, dataframe: DataFrame, period: int, metadata: Dict, **kwargs + self, dataframe: DataFrame, period: int, metadata: dict, **kwargs ) -> DataFrame: """ *Only functional with FreqAI enabled strategies* @@ -103,7 +102,7 @@ class FreqaiExampleStrategy(IStrategy): return dataframe def feature_engineering_expand_basic( - self, dataframe: DataFrame, metadata: Dict, **kwargs + self, dataframe: DataFrame, metadata: dict, **kwargs ) -> DataFrame: """ *Only functional with FreqAI enabled strategies* @@ -141,7 +140,7 @@ class FreqaiExampleStrategy(IStrategy): return dataframe def feature_engineering_standard( - self, dataframe: DataFrame, metadata: Dict, **kwargs + self, dataframe: DataFrame, metadata: dict, **kwargs ) -> DataFrame: """ *Only functional with FreqAI enabled strategies* @@ -172,7 +171,7 @@ class FreqaiExampleStrategy(IStrategy): dataframe["%-hour_of_day"] = dataframe["date"].dt.hour return dataframe - def set_freqai_targets(self, dataframe: DataFrame, metadata: Dict, **kwargs) -> DataFrame: + def set_freqai_targets(self, dataframe: DataFrame, metadata: dict, **kwargs) -> DataFrame: """ *Only functional with FreqAI enabled strategies* Required function to set the targets for the model. diff --git a/freqtrade/templates/sample_hyperopt_loss.py b/freqtrade/templates/sample_hyperopt_loss.py index 4e4afed24..20a348f2b 100644 --- a/freqtrade/templates/sample_hyperopt_loss.py +++ b/freqtrade/templates/sample_hyperopt_loss.py @@ -1,6 +1,5 @@ from datetime import datetime from math import exp -from typing import Dict from pandas import DataFrame @@ -41,7 +40,7 @@ class SampleHyperOptLoss(IHyperOptLoss): min_date: datetime, max_date: datetime, config: Config, - processed: Dict[str, DataFrame], + processed: dict[str, DataFrame], *args, **kwargs, ) -> float: diff --git a/freqtrade/templates/sample_strategy.py b/freqtrade/templates/sample_strategy.py index 835e6fa91..75a0fe385 100644 --- a/freqtrade/templates/sample_strategy.py +++ b/freqtrade/templates/sample_strategy.py @@ -6,7 +6,7 @@ import numpy as np import pandas as pd from datetime import datetime, timedelta, timezone from pandas import DataFrame -from typing import Dict, Optional, Union, Tuple +from typing import Optional, Union from freqtrade.strategy import ( IStrategy, diff --git a/freqtrade/util/migrations/funding_rate_mig.py b/freqtrade/util/migrations/funding_rate_mig.py index 16ca60732..e53ec339e 100644 --- a/freqtrade/util/migrations/funding_rate_mig.py +++ b/freqtrade/util/migrations/funding_rate_mig.py @@ -2,7 +2,6 @@ import logging from typing import Optional from freqtrade.constants import Config -from freqtrade.data.history import get_datahandler from freqtrade.enums import TradingMode from freqtrade.exchange import Exchange @@ -11,6 +10,8 @@ logger = logging.getLogger(__name__) def migrate_funding_fee_timeframe(config: Config, exchange: Optional[Exchange]): + from freqtrade.data.history import get_datahandler + if config.get("trading_mode", TradingMode.SPOT) != TradingMode.FUTURES: # only act on futures return diff --git a/freqtrade/util/rich_progress.py b/freqtrade/util/rich_progress.py index b7d8f4c3d..ef06e7f46 100644 --- a/freqtrade/util/rich_progress.py +++ b/freqtrade/util/rich_progress.py @@ -1,4 +1,4 @@ -from typing import Callable, List, Optional, Union +from typing import Callable, Optional, Union 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: Optional[list[ConsoleRenderable]] = None, + cust_callables: Optional[list[Callable[[], ConsoleRenderable]]] = None, **kwargs, ) -> None: self._cust_objs = cust_objs or [] diff --git a/freqtrade/util/rich_tables.py b/freqtrade/util/rich_tables.py index cfab5cd74..54f941c58 100644 --- a/freqtrade/util/rich_tables.py +++ b/freqtrade/util/rich_tables.py @@ -1,5 +1,6 @@ import sys -from typing import Any, Dict, List, Optional, Sequence, Union +from collections.abc import Sequence +from typing import Any, Optional, Union from pandas import DataFrame from rich.console import Console @@ -11,12 +12,12 @@ TextOrString = Union[str, Text] def print_rich_table( - tabular_data: Sequence[Union[Dict[str, Any], Sequence[TextOrString]]], + tabular_data: Sequence[Union[dict[str, Any], Sequence[TextOrString]]], headers: Sequence[str], summary: Optional[str] = None, *, justify="right", - table_kwargs: Optional[Dict[str, Any]] = None, + table_kwargs: Optional[dict[str, Any]] = None, ) -> None: table = Table( *[c if isinstance(c, Column) else Column(c, justify=justify) for c in headers], @@ -34,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[Union[str, Text]] = [r if isinstance(r, Text) else str(r) for r in row] table.add_row(*row_to_add) width = None @@ -58,7 +59,7 @@ def print_df_rich_table( *, show_index=False, index_name: Optional[str] = None, - table_kwargs: Optional[Dict[str, Any]] = None, + table_kwargs: Optional[dict[str, Any]] = None, ) -> None: table = Table(title=summary, **(table_kwargs or {})) diff --git a/freqtrade/util/template_renderer.py b/freqtrade/util/template_renderer.py index 2ea3525aa..b2c96fdc5 100644 --- a/freqtrade/util/template_renderer.py +++ b/freqtrade/util/template_renderer.py @@ -2,10 +2,10 @@ Jinja2 rendering utils, used to generate new strategy and configurations. """ -from typing import Dict, Optional +from typing import Optional -def render_template(templatefile: str, arguments: Dict) -> str: +def render_template(templatefile: str, arguments: dict) -> str: from jinja2 import Environment, PackageLoader, select_autoescape env = Environment( @@ -17,7 +17,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: Optional[dict] = None ) -> str: """ Use templatefile if possible, otherwise fall back to templatefallbackfile diff --git a/freqtrade/wallets.py b/freqtrade/wallets.py index e67bdd79e..e688b307a 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 Dict, NamedTuple, Optional +from typing import NamedTuple, Optional from freqtrade.constants import UNLIMITED_STAKE_AMOUNT, Config, IntOrInf from freqtrade.enums import RunMode, TradingMode @@ -39,8 +39,8 @@ class Wallets: self._config = config self._is_backtest = is_backtest self._exchange = exchange - self._wallets: Dict[str, Wallet] = {} - self._positions: Dict[str, PositionWallet] = {} + 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.update() @@ -199,10 +199,10 @@ class Wallets: logger.info("Wallets synced.") self._last_wallet_refresh = dt_now() - def get_all_balances(self) -> Dict[str, Wallet]: + def get_all_balances(self) -> dict[str, Wallet]: return self._wallets - def get_all_positions(self) -> Dict[str, PositionWallet]: + def get_all_positions(self) -> dict[str, PositionWallet]: return self._positions def _check_exit_amount(self, trade: Trade) -> bool: diff --git a/freqtrade/worker.py b/freqtrade/worker.py index 4c8fee356..d618b3dec 100644 --- a/freqtrade/worker.py +++ b/freqtrade/worker.py @@ -6,7 +6,7 @@ import logging import time import traceback from os import getpid -from typing import Any, Callable, Dict, Optional +from typing import Any, Callable, Optional import sdnotify @@ -27,7 +27,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: Optional[Config] = None) -> None: """ Init all variables and objects the bot needs to work """ diff --git a/ft_client/freqtrade_client/__init__.py b/ft_client/freqtrade_client/__init__.py index ca1fd67bc..935689b2d 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.9" +__version__ = "2024.10" if "dev" in __version__: from pathlib import Path diff --git a/ft_client/freqtrade_client/ft_client.py b/ft_client/freqtrade_client/ft_client.py index 3c450d1df..13bcbc2b9 100644 --- a/ft_client/freqtrade_client/ft_client.py +++ b/ft_client/freqtrade_client/ft_client.py @@ -5,7 +5,7 @@ import logging import re import sys from pathlib import Path -from typing import Any, Dict +from typing import Any import rapidjson @@ -81,7 +81,7 @@ def print_commands(): print(f"{x}\n\t{doc}\n") -def main_exec(parsed: Dict[str, Any]): +def main_exec(parsed: dict[str, Any]): if parsed.get("show"): print_commands() sys.exit() diff --git a/ft_client/freqtrade_client/ft_rest_client.py b/ft_client/freqtrade_client/ft_rest_client.py index f7c895901..577001836 100755 --- a/ft_client/freqtrade_client/ft_rest_client.py +++ b/ft_client/freqtrade_client/ft_rest_client.py @@ -7,17 +7,18 @@ so it can be used as a standalone script, and can be installed independently. import json import logging -from typing import Any, Dict, List, Optional, Union +from typing import Any, Optional, Union from urllib.parse import urlencode, urlparse, urlunparse import requests +from requests.adapters import HTTPAdapter 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 = Optional[dict[str, Any]] +PostDataT = Optional[Union[dict[str, Any], list[dict[str, Any]]]] class FtRestClient: @@ -28,12 +29,11 @@ class FtRestClient: self._session = requests.Session() # allow configuration of pool - adapter = requests.adapters.HTTPAdapter( - pool_connections=pool_connections, pool_maxsize=pool_maxsize - ) + adapter = HTTPAdapter(pool_connections=pool_connections, pool_maxsize=pool_maxsize) self._session.mount("http://", adapter) - self._session.auth = (username, password) + if username and password: + self._session.auth = (username, password) def _call(self, method, apipath, params: Optional[dict] = None, data=None, files=None): if str(method).upper() not in ("GET", "POST", "PUT", "DELETE"): @@ -243,7 +243,7 @@ class FtRestClient: :param limit: Limits log messages to the last logs. No limit to get the entire log. :return: json object """ - return self._get("logs", params={"limit": limit} if limit else 0) + return self._get("logs", params={"limit": limit} if limit else {}) def trades(self, limit=None, offset=None): """Return trades history, sorted by id diff --git a/ft_client/pyproject.toml b/ft_client/pyproject.toml index 919e524f8..a29b3d08c 100644 --- a/ft_client/pyproject.toml +++ b/ft_client/pyproject.toml @@ -13,14 +13,13 @@ authors = [ description = "Freqtrade - Client scripts" readme = "README.md" -requires-python = ">=3.9" +requires-python = ">=3.10" license = {text = "GPLv3"} # license = "GPLv3" classifiers = [ "Environment :: Console", "Intended Audience :: Science/Research", "License :: OSI Approved :: GNU General Public License v3 (GPLv3)", - "Programming Language :: Python :: 3.9", "Programming Language :: Python :: 3.10", "Programming Language :: Python :: 3.11", "Programming Language :: Python :: 3.12", diff --git a/mkdocs.yml b/mkdocs.yml index 6d51e136b..550d3e7d5 100644 --- a/mkdocs.yml +++ b/mkdocs.yml @@ -12,6 +12,7 @@ nav: - Windows: windows_installation.md - Freqtrade Basics: bot-basics.md - Configuration: configuration.md + - Strategy Quickstart: strategy-101.md - Strategy Customization: strategy-customization.md - Strategy Callbacks: strategy-callbacks.md - Stoploss: stoploss.md @@ -124,3 +125,4 @@ plugins: enabled: true - mike: deploy_prefix: 'en' + canonical_version: 'stable' diff --git a/pyproject.toml b/pyproject.toml index bd36d15c6..1db932cdc 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -4,7 +4,7 @@ build-backend = "setuptools.build_meta" [project] name = "freqtrade" -dynamic = ["version", "dependencies", "optional-dependencies"] +dynamic = ["version"] authors = [ {name = "Freqtrade Team"}, @@ -13,14 +13,12 @@ authors = [ description = "Freqtrade - Crypto Trading Bot" readme = "README.md" -requires-python = ">=3.9" +requires-python = ">=3.10" license = {text = "GPLv3"} -# license = "GPLv3" classifiers = [ "Environment :: Console", "Intended Audience :: Science/Research", "License :: OSI Approved :: GNU General Public License v3 (GPLv3)", - "Programming Language :: Python :: 3.9", "Programming Language :: Python :: 3.10", "Programming Language :: Python :: 3.11", "Programming Language :: Python :: 3.12", @@ -29,6 +27,111 @@ classifiers = [ "Topic :: Office/Business :: Financial :: Investment", ] +dependencies = [ + # from requirements.txt + "ccxt>=4.3.24", + "SQLAlchemy>=2.0.6", + "python-telegram-bot>=20.1", + "humanize>=4.0.0", + "cachetools", + "requests", + "httpx>=0.24.1", + "urllib3", + "jsonschema", + "numpy<2.0", + "pandas>=2.2.0,<3.0", + "TA-Lib", + "pandas-ta", + "technical", + "tabulate", + "pycoingecko", + "py_find_1st", + "python-rapidjson", + "orjson", + "jinja2", + "questionary", + "prompt-toolkit", + "joblib>=1.2.0", + "rich", + 'pyarrow; platform_machine != "armv7l"', + "fastapi", + "pydantic>=2.2.0", + "pyjwt", + "websockets", + "uvicorn", + "psutil", + "schedule", + "janus", + "ast-comments", + "aiofiles", + "aiohttp", + "cryptography", + "sdnotify", + "python-dateutil", + "pytz", + "packaging", + "freqtrade-client", +] + +[project.optional-dependencies] +# Requirements used for submodules +plot = ["plotly>=4.0"] +hyperopt = [ + "scipy", + "scikit-learn", + "ft-scikit-optimize>=0.9.2", + "filelock", +] +freqai = [ + "scikit-learn", + "joblib", + 'catboost; platform_machine != "aarch64"', + "lightgbm", + "xgboost", + "tensorboard", + "datasieve>=0.1.5", +] +freqai_rl = [ + "torch", + "gymnasium", + "stable-baselines3", + "sb3-contrib", + "tqdm", +] +hdf5 = [ + "tables", + "blosc", +] +develop = [ + "coveralls", + "isort", + "mypy", + "pre-commit", + "pytest-asyncio", + "pytest-cov", + "pytest-mock", + "pytest-random-order", + "pytest", + "ruff", + "time-machine", + "types-cachetools", + "types-filelock", + "types-python-dateutil", + "types-requests", + "types-tabulate", +] +jupyter = [ + "jupyter", + "nbstripout", + "ipykernel", + "nbconvert", +] +all = [ + "freqtrade[plot,hyperopt,freqai,freqai_rl,hdf5,jupyter]", +] +dev = [ + "freqtrade[all,develop]", +] [project.urls] Homepage = "https://github.com/freqtrade/freqtrade" @@ -105,27 +208,47 @@ plugins = [ module = "tests.*" ignore_errors = true -[[tool.mypy.overrides]] -# Telegram does not use implicit_optional = false in the current version. -module = "telegram.*" -implicit_optional = true - [tool.pyright] -include = ["freqtrade"] +include = ["freqtrade", "ft_client"] exclude = [ "**/__pycache__", "build_helpers/*.py", + "ft_client/build/*", + "build/*", + "tests/*", ] ignore = ["freqtrade/vendor/**"] +pythonPlatform = "All" +pythonVersion = "3.9" + +typeCheckingMode = "off" +# analyzeUnannotatedFunctions = false + +reportArgumentType = false # 155 +reportAssignmentType = false # 12 +reportAttributeAccessIssue = false # 255 +reportCallIssue = false # 23 +reportGeneralTypeIssues = false # 48 +reportIncompatibleMethodOverride = false # 15 +reportIncompatibleVariableOverride = false # 5 +reportIndexIssue = false # 22 +reportMissingImports = false # 5 +reportOperatorIssue = false # 7 +reportOptionalMemberAccess = false # 35 +reportOptionalOperand = false # 7 +reportPossiblyUnboundVariable = false # 36 +reportPrivateImportUsage = false # 5 +reportRedeclaration = false # 1 +reportReturnType = false # 28 +reportTypedDictNotRequiredAccess = false # 27 [tool.ruff] line-length = 100 extend-exclude = [".env", ".venv"] -target-version = "py38" +target-version = "py39" [tool.ruff.lint] -# Exclude UP036 as it's causing the "exit if < 3.9" to fail. extend-select = [ "C90", # mccabe "B", # bugbear diff --git a/requirements-dev.txt b/requirements-dev.txt index 0036f6b68..13fc77c39 100644 --- a/requirements-dev.txt +++ b/requirements-dev.txt @@ -7,9 +7,9 @@ -r docs/requirements-docs.txt coveralls==4.0.1 -ruff==0.6.7 -mypy==1.11.2 -pre-commit==3.8.0 +ruff==0.7.1 +mypy==1.13.0 +pre-commit==4.0.1 pytest==8.3.3 pytest-asyncio==0.24.0 pytest-cov==5.0.0 @@ -19,7 +19,7 @@ pytest-timeout==2.3.1 pytest-xdist==3.6.1 isort==5.13.2 # For datetime mocking -time-machine==2.15.0 +time-machine==2.16.0 # Convert jupyter notebooks to markdown documents nbconvert==7.16.4 @@ -27,6 +27,6 @@ nbconvert==7.16.4 # mypy types types-cachetools==5.5.0.20240820 types-filelock==3.2.7 -types-requests==2.32.0.20240914 +types-requests==2.32.0.20241016 types-tabulate==0.9.0.20240106 -types-python-dateutil==2.9.0.20240906 +types-python-dateutil==2.9.0.20241003 diff --git a/requirements-freqai-rl.txt b/requirements-freqai-rl.txt index c9db23d96..14388df86 100644 --- a/requirements-freqai-rl.txt +++ b/requirements-freqai-rl.txt @@ -3,7 +3,7 @@ # Required for freqai-rl torch==2.2.2; sys_platform == 'darwin' and platform_machine == 'x86_64' -torch==2.4.1; sys_platform != 'darwin' or platform_machine != 'x86_64' +torch==2.5.0; sys_platform != 'darwin' or platform_machine != 'x86_64' gymnasium==0.29.1 stable_baselines3==2.3.2 sb3_contrib>=2.2.1 diff --git a/requirements-freqai.txt b/requirements-freqai.txt index e71dfc129..a09eadbb3 100644 --- a/requirements-freqai.txt +++ b/requirements-freqai.txt @@ -11,5 +11,5 @@ catboost==1.2.7; 'arm' not in platform_machine matplotlib==3.9.2 lightgbm==4.5.0 xgboost==2.0.3 -tensorboard==2.17.1 +tensorboard==2.18.0 datasieve==0.1.7 diff --git a/requirements-hyperopt.txt b/requirements-hyperopt.txt index 41afe6d58..7f60c8299 100644 --- a/requirements-hyperopt.txt +++ b/requirements-hyperopt.txt @@ -2,8 +2,7 @@ -r requirements.txt # Required for hyperopt -scipy==1.14.1; python_version >= "3.10" -scipy==1.13.1; python_version < "3.10" +scipy==1.14.1 scikit-learn==1.5.2 ft-scikit-optimize==0.9.2 filelock==3.16.1 diff --git a/requirements.txt b/requirements.txt index 04469b882..a77cad1d9 100644 --- a/requirements.txt +++ b/requirements.txt @@ -1,18 +1,18 @@ numpy==1.26.4 pandas==2.2.3 -bottleneck==1.4.0 +bottleneck==1.4.2 numexpr==2.10.1 pandas-ta==0.3.14b -ccxt==4.4.6 +ccxt==4.4.24 cryptography==42.0.8; platform_machine == 'armv7l' -cryptography==43.0.1; platform_machine != 'armv7l' -aiohttp==3.10.5 -SQLAlchemy==2.0.35 +cryptography==43.0.3; platform_machine != 'armv7l' +aiohttp==3.10.10 +SQLAlchemy==2.0.36 python-telegram-bot==21.6 # can't be hard-pinned due to telegram-bot pinning httpx with ~ httpx>=0.24.1 -humanize==4.10.0 +humanize==4.11.0 cachetools==5.5.0 requests==2.32.3 urllib3==2.2.3 @@ -22,11 +22,9 @@ technical==1.4.4 tabulate==0.9.0 pycoingecko==3.1.0 jinja2==3.1.4 -# Tables 3.10 dropped support for Python 3.9 -tables==3.9.1; python_version < "3.10" -tables==3.10.1; python_version >= "3.10" +tables==3.10.1 joblib==1.4.2 -rich==13.8.1 +rich==13.9.3 pyarrow==17.0.0; platform_machine != 'armv7l' # find first, C search in arrays @@ -35,18 +33,18 @@ py_find_1st==1.1.6 # Load ticker files 30% faster python-rapidjson==1.20 # Properly format api responses -orjson==3.10.7 +orjson==3.10.10 # Notify systemd sdnotify==0.3.2 # API Server -fastapi==0.115.0 +fastapi==0.115.4 pydantic==2.9.2 -uvicorn==0.30.6 +uvicorn==0.32.0 pyjwt==2.9.0 aiofiles==24.1.0 -psutil==6.0.0 +psutil==6.1.0 # Building config files interactively questionary==2.0.1 diff --git a/setup.ps1 b/setup.ps1 index 8647bea94..116ed0a2b 100644 --- a/setup.ps1 +++ b/setup.ps1 @@ -153,16 +153,13 @@ function Find-PythonExecutable { "python3.12", "python3.11", "python3.10", - "python3.9", "python3", "C:\Users\$env:USERNAME\AppData\Local\Programs\Python\Python312\python.exe", "C:\Users\$env:USERNAME\AppData\Local\Programs\Python\Python311\python.exe", "C:\Users\$env:USERNAME\AppData\Local\Programs\Python\Python310\python.exe", - "C:\Users\$env:USERNAME\AppData\Local\Programs\Python\Python39\python.exe", "C:\Python312\python.exe", "C:\Python311\python.exe", - "C:\Python310\python.exe", - "C:\Python39\python.exe" + "C:\Python310\python.exe" ) @@ -178,10 +175,10 @@ function Main { "Starting the operations..." | Out-File $LogFilePath -Append "Current directory: $(Get-Location)" | Out-File $LogFilePath -Append - # Exit on lower versions than Python 3.9 or when Python executable not found + # Exit on lower versions than Python 3.10 or when Python executable not found $PythonExecutable = Find-PythonExecutable if ($null -eq $PythonExecutable) { - Write-Log "No suitable Python executable found. Please ensure that Python 3.9 or higher is installed and available in the system PATH." -Level 'ERROR' + Write-Log "No suitable Python executable found. Please ensure that Python 3.10 or higher is installed and available in the system PATH." -Level 'ERROR' Exit 1 } diff --git a/setup.py b/setup.py deleted file mode 100644 index 6963862e0..000000000 --- a/setup.py +++ /dev/null @@ -1,126 +0,0 @@ -from setuptools import setup - - -# Requirements used for submodules -plot = ["plotly>=4.0"] -hyperopt = [ - "scipy", - "scikit-learn", - "ft-scikit-optimize>=0.9.2", - "filelock", -] - -freqai = [ - "scikit-learn", - "joblib", - 'catboost; platform_machine != "aarch64"', - "lightgbm", - "xgboost", - "tensorboard", - "datasieve>=0.1.5", -] - -freqai_rl = [ - "torch", - "gymnasium", - "stable-baselines3", - "sb3-contrib", - "tqdm", -] - -hdf5 = [ - "tables", - "blosc", -] - -develop = [ - "coveralls", - "isort", - "mypy", - "pre-commit", - "pytest-asyncio", - "pytest-cov", - "pytest-mock", - "pytest-random-order", - "pytest", - "ruff", - "time-machine", - "types-cachetools", - "types-filelock", - "types-python-dateutil" "types-requests", - "types-tabulate", -] - -jupyter = [ - "jupyter", - "nbstripout", - "ipykernel", - "nbconvert", -] - -all_extra = plot + develop + jupyter + hyperopt + hdf5 + freqai + freqai_rl - -setup( - tests_require=[ - "pytest", - "pytest-asyncio", - "pytest-cov", - "pytest-mock", - ], - install_requires=[ - # from requirements.txt - "ccxt>=4.3.24", - "SQLAlchemy>=2.0.6", - "python-telegram-bot>=20.1", - "humanize>=4.0.0", - "cachetools", - "requests", - "httpx>=0.24.1", - "urllib3", - "jsonschema", - "numpy<2.0", - "pandas>=2.2.0,<3.0", - "TA-Lib", - "pandas-ta", - "technical", - "tabulate", - "pycoingecko", - "py_find_1st", - "python-rapidjson", - "orjson", - "jinja2", - "questionary", - "prompt-toolkit", - "joblib>=1.2.0", - "rich", - 'pyarrow; platform_machine != "armv7l"', - "fastapi", - "pydantic>=2.2.0", - "pyjwt", - "websockets", - "uvicorn", - "psutil", - "schedule", - "janus", - "ast-comments", - "aiofiles", - "aiohttp", - "cryptography", - "sdnotify", - "python-dateutil", - "pytz", - "packaging", - "freqtrade-client", - ], - extras_require={ - "dev": all_extra, - "plot": plot, - "jupyter": jupyter, - "hyperopt": hyperopt, - "hdf5": hdf5, - "freqai": freqai, - "freqai_rl": freqai_rl, - "all": all_extra, - }, - url="https://github.com/freqtrade/freqtrade", -) diff --git a/setup.sh b/setup.sh index 18f7682d8..a9e684d62 100755 --- a/setup.sh +++ b/setup.sh @@ -25,7 +25,7 @@ function check_installed_python() { exit 2 fi - for v in 12 11 10 9 + for v in 12 11 10 do PYTHON="python3.${v}" which $PYTHON @@ -36,7 +36,7 @@ function check_installed_python() { fi done - echo "No usable python found. Please make sure to have python3.9 or newer installed." + echo "No usable python found. Please make sure to have python3.10 or newer installed." exit 1 } @@ -166,7 +166,7 @@ function install_macos() { #Gets number after decimal in python version version=$(egrep -o 3.\[0-9\]+ <<< $PYTHON | sed 's/3.//g') - if [[ $version -ge 9 ]]; then #Checks if python version >= 3.9 + if [[ $version -ge 10 ]]; then #Checks if python version >= 3.10 install_mac_newer_python_dependencies fi } @@ -277,7 +277,7 @@ function install() { install_redhat else echo "This script does not support your OS." - echo "If you have Python version 3.9 - 3.12, pip, virtualenv, ta-lib you can continue." + echo "If you have Python version 3.10 - 3.12, pip, virtualenv, ta-lib you can continue." echo "Wait 10 seconds to continue the next install steps or use ctrl+c to interrupt this shell." sleep 10 fi @@ -304,7 +304,7 @@ function help() { echo " -p,--plot Install dependencies for Plotting scripts." } -# Verify if 3.9+ is installed +# Verify if 3.10+ is installed check_installed_python case $* in diff --git a/tests/commands/test_build_config.py b/tests/commands/test_build_config.py index 5d287a35f..9d4a57db1 100644 --- a/tests/commands/test_build_config.py +++ b/tests/commands/test_build_config.py @@ -4,10 +4,10 @@ from unittest.mock import MagicMock import pytest import rapidjson -from freqtrade.commands.build_config_commands import ( +from freqtrade.commands.build_config_commands import start_new_config +from freqtrade.configuration.deploy_config import ( ask_user_config, ask_user_overwrite, - start_new_config, validate_is_float, validate_is_int, ) @@ -39,7 +39,7 @@ def test_start_new_config(mocker, caplog, exchange): wt_mock = mocker.patch.object(Path, "write_text", MagicMock()) mocker.patch.object(Path, "exists", MagicMock(return_value=True)) unlink_mock = mocker.patch.object(Path, "unlink", MagicMock()) - mocker.patch("freqtrade.commands.build_config_commands.ask_user_overwrite", return_value=True) + mocker.patch("freqtrade.configuration.deploy_config.ask_user_overwrite", return_value=True) sample_selections = { "max_open_trades": 3, @@ -62,7 +62,7 @@ def test_start_new_config(mocker, caplog, exchange): "api_server_password": "MoneyMachine", } mocker.patch( - "freqtrade.commands.build_config_commands.ask_user_config", return_value=sample_selections + "freqtrade.configuration.deploy_config.ask_user_config", return_value=sample_selections ) args = ["new-config", "--config", "coolconfig.json"] start_new_config(get_args(args)) @@ -80,7 +80,7 @@ def test_start_new_config(mocker, caplog, exchange): def test_start_new_config_exists(mocker, caplog): mocker.patch.object(Path, "exists", MagicMock(return_value=True)) - mocker.patch("freqtrade.commands.build_config_commands.ask_user_overwrite", return_value=False) + mocker.patch("freqtrade.configuration.deploy_config.ask_user_overwrite", return_value=False) args = ["new-config", "--config", "coolconfig.json"] with pytest.raises(OperationalException, match=r"Configuration .* already exists\."): start_new_config(get_args(args)) @@ -91,14 +91,14 @@ def test_ask_user_overwrite(mocker): Once https://github.com/tmbo/questionary/issues/35 is implemented, improve this test. """ prompt_mock = mocker.patch( - "freqtrade.commands.build_config_commands.prompt", return_value={"overwrite": False} + "freqtrade.configuration.deploy_config.prompt", return_value={"overwrite": False} ) assert not ask_user_overwrite(Path("test.json")) assert prompt_mock.call_count == 1 prompt_mock.reset_mock() prompt_mock = mocker.patch( - "freqtrade.commands.build_config_commands.prompt", return_value={"overwrite": True} + "freqtrade.configuration.deploy_config.prompt", return_value={"overwrite": True} ) assert ask_user_overwrite(Path("test.json")) assert prompt_mock.call_count == 1 @@ -109,13 +109,13 @@ def test_ask_user_config(mocker): Once https://github.com/tmbo/questionary/issues/35 is implemented, improve this test. """ prompt_mock = mocker.patch( - "freqtrade.commands.build_config_commands.prompt", return_value={"overwrite": False} + "freqtrade.configuration.deploy_config.prompt", return_value={"overwrite": False} ) answers = ask_user_config() assert isinstance(answers, dict) assert prompt_mock.call_count == 1 - prompt_mock = mocker.patch("freqtrade.commands.build_config_commands.prompt", return_value={}) + prompt_mock = mocker.patch("freqtrade.configuration.deploy_config.prompt", return_value={}) with pytest.raises(OperationalException, match=r"User interrupted interactive questions\."): ask_user_config() diff --git a/tests/commands/test_commands.py b/tests/commands/test_commands.py index c55126db1..28baffd5a 100644 --- a/tests/commands/test_commands.py +++ b/tests/commands/test_commands.py @@ -11,6 +11,7 @@ import pytest from freqtrade.commands import ( start_backtesting_show, start_convert_data, + start_convert_db, start_convert_trades, start_create_userdir, start_download_data, @@ -19,6 +20,8 @@ from freqtrade.commands import ( start_install_ui, start_list_data, start_list_exchanges, + start_list_freqAI_models, + start_list_hyperopt_loss_functions, start_list_markets, start_list_strategies, start_list_timeframes, @@ -30,14 +33,12 @@ from freqtrade.commands import ( start_trading, start_webserver, ) -from freqtrade.commands.db_commands import start_convert_db -from freqtrade.commands.deploy_commands import ( +from freqtrade.commands.deploy_ui import ( clean_ui_subdir, download_and_install_ui, get_ui_download_url, read_ui_version, ) -from freqtrade.commands.list_commands import start_list_freqAI_models from freqtrade.configuration import setup_utils_configuration from freqtrade.enums import RunMode from freqtrade.exceptions import OperationalException @@ -571,8 +572,12 @@ def test_create_datadir_failed(caplog): def test_create_datadir(caplog, mocker): - cud = mocker.patch("freqtrade.commands.deploy_commands.create_userdata_dir", MagicMock()) - csf = mocker.patch("freqtrade.commands.deploy_commands.copy_sample_files", MagicMock()) + cud = mocker.patch( + "freqtrade.configuration.directory_operations.create_userdata_dir", MagicMock() + ) + csf = mocker.patch( + "freqtrade.configuration.directory_operations.copy_sample_files", MagicMock() + ) args = ["create-userdir", "--userdir", "/temp/freqtrade/test"] start_create_userdir(get_args(args)) @@ -591,7 +596,7 @@ def test_start_new_strategy(mocker, caplog): assert "CoolNewStrategy" in wt_mock.call_args_list[0][0][0] assert log_has_re("Writing strategy to .*", caplog) - mocker.patch("freqtrade.commands.deploy_commands.setup_utils_configuration") + 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\." @@ -608,13 +613,13 @@ def test_start_new_strategy_no_arg(mocker, caplog): def test_start_install_ui(mocker): - clean_mock = mocker.patch("freqtrade.commands.deploy_commands.clean_ui_subdir") + clean_mock = mocker.patch("freqtrade.commands.deploy_ui.clean_ui_subdir") get_url_mock = mocker.patch( - "freqtrade.commands.deploy_commands.get_ui_download_url", + "freqtrade.commands.deploy_ui.get_ui_download_url", return_value=("https://example.com/whatever", "0.0.1"), ) - download_mock = mocker.patch("freqtrade.commands.deploy_commands.download_and_install_ui") - mocker.patch("freqtrade.commands.deploy_commands.read_ui_version", return_value=None) + download_mock = mocker.patch("freqtrade.commands.deploy_ui.download_and_install_ui") + mocker.patch("freqtrade.commands.deploy_ui.read_ui_version", return_value=None) args = [ "install-ui", ] @@ -638,13 +643,13 @@ def test_start_install_ui(mocker): def test_clean_ui_subdir(mocker, tmp_path, caplog): - mocker.patch("freqtrade.commands.deploy_commands.Path.is_dir", side_effect=[True, True]) - mocker.patch("freqtrade.commands.deploy_commands.Path.is_file", side_effect=[False, True]) - rd_mock = mocker.patch("freqtrade.commands.deploy_commands.Path.rmdir") - ul_mock = mocker.patch("freqtrade.commands.deploy_commands.Path.unlink") + mocker.patch("freqtrade.commands.deploy_ui.Path.is_dir", side_effect=[True, True]) + mocker.patch("freqtrade.commands.deploy_ui.Path.is_file", side_effect=[False, True]) + rd_mock = mocker.patch("freqtrade.commands.deploy_ui.Path.rmdir") + ul_mock = mocker.patch("freqtrade.commands.deploy_ui.Path.unlink") mocker.patch( - "freqtrade.commands.deploy_commands.Path.glob", + "freqtrade.commands.deploy_ui.Path.glob", return_value=[Path("test1"), Path("test2"), Path(".gitkeep")], ) folder = tmp_path / "uitests" @@ -664,10 +669,10 @@ def test_download_and_install_ui(mocker, tmp_path): file_like_object.seek(0) requests_mock.content = file_like_object.read() - mocker.patch("freqtrade.commands.deploy_commands.requests.get", return_value=requests_mock) + mocker.patch("freqtrade.commands.deploy_ui.requests.get", return_value=requests_mock) - mocker.patch("freqtrade.commands.deploy_commands.Path.is_dir", side_effect=[True, False]) - wb_mock = mocker.patch("freqtrade.commands.deploy_commands.Path.write_bytes") + mocker.patch("freqtrade.commands.deploy_ui.Path.is_dir", side_effect=[True, False]) + wb_mock = mocker.patch("freqtrade.commands.deploy_ui.Path.write_bytes") folder = tmp_path / "uitests_dl" folder.mkdir(exist_ok=True) @@ -689,9 +694,7 @@ def test_get_ui_download_url(mocker): [{"browser_download_url": "http://download.zip"}], ] ) - get_mock = mocker.patch( - "freqtrade.commands.deploy_commands.requests.get", return_value=response - ) + get_mock = mocker.patch("freqtrade.commands.deploy_ui.requests.get", return_value=response) x, last_version = get_ui_download_url() assert get_mock.call_count == 2 assert last_version == "0.0.1" @@ -714,9 +717,7 @@ def test_get_ui_download_url_direct(mocker): }, ] ) - get_mock = mocker.patch( - "freqtrade.commands.deploy_commands.requests.get", return_value=response - ) + get_mock = mocker.patch("freqtrade.commands.deploy_ui.requests.get", return_value=response) x, last_version = get_ui_download_url() assert get_mock.call_count == 1 assert last_version == "0.0.2" @@ -734,7 +735,7 @@ def test_get_ui_download_url_direct(mocker): def test_download_data_keyboardInterrupt(mocker, markets): dl_mock = mocker.patch( - "freqtrade.commands.data_commands.download_data_main", + "freqtrade.data.history.download_data_main", MagicMock(side_effect=KeyboardInterrupt), ) patch_exchange(mocker) @@ -972,7 +973,7 @@ def test_download_data_data_invalid(mocker): def test_start_convert_trades(mocker): convert_mock = mocker.patch( - "freqtrade.commands.data_commands.convert_trades_to_ohlcv", MagicMock(return_value=[]) + "freqtrade.data.converter.convert_trades_to_ohlcv", MagicMock(return_value=[]) ) patch_exchange(mocker) mocker.patch(f"{EXMS}.get_markets") @@ -1055,6 +1056,28 @@ def test_start_list_strategies(capsys): assert str(Path("broken_strats/broken_futures_strategies.py")) in captured.out +def test_start_list_hyperopt_loss_functions(capsys): + args = ["list-hyperoptloss", "-1"] + pargs = get_args(args) + pargs["config"] = None + start_list_hyperopt_loss_functions(pargs) + captured = capsys.readouterr() + assert "CalmarHyperOptLoss" in captured.out + assert "MaxDrawDownHyperOptLoss" in captured.out + assert "SortinoHyperOptLossDaily" in captured.out + assert "/hyperopt_loss_sortino_daily.py" not in captured.out + + args = ["list-hyperoptloss"] + pargs = get_args(args) + pargs["config"] = None + start_list_hyperopt_loss_functions(pargs) + captured = capsys.readouterr() + assert "CalmarHyperOptLoss" in captured.out + assert "MaxDrawDownHyperOptLoss" in captured.out + assert "SortinoHyperOptLossDaily" in captured.out + assert "/hyperopt_loss_sortino_daily.py" in captured.out + + def test_start_list_freqAI_models(capsys): args = ["list-freqaimodels", "-1"] pargs = get_args(args) @@ -1522,7 +1545,7 @@ def test_hyperopt_show(mocker, capsys): mocker.patch( "freqtrade.optimize.hyperopt_tools.HyperoptTools._read_results", side_effect=fake_iterator ) - mocker.patch("freqtrade.commands.hyperopt_commands.show_backtest_result") + mocker.patch("freqtrade.optimize.optimize_reports.show_backtest_result") args = [ "hyperopt-show", @@ -1579,8 +1602,8 @@ def test_hyperopt_show(mocker, capsys): def test_convert_data(mocker, testdatadir): - ohlcv_mock = mocker.patch("freqtrade.commands.data_commands.convert_ohlcv_format") - trades_mock = mocker.patch("freqtrade.commands.data_commands.convert_trades_format") + ohlcv_mock = mocker.patch("freqtrade.data.converter.convert_ohlcv_format") + trades_mock = mocker.patch("freqtrade.data.converter.convert_trades_format") args = [ "convert-data", "--format-from", @@ -1601,8 +1624,8 @@ def test_convert_data(mocker, testdatadir): def test_convert_data_trades(mocker, testdatadir): - ohlcv_mock = mocker.patch("freqtrade.commands.data_commands.convert_ohlcv_format") - trades_mock = mocker.patch("freqtrade.commands.data_commands.convert_trades_format") + ohlcv_mock = mocker.patch("freqtrade.data.converter.convert_ohlcv_format") + trades_mock = mocker.patch("freqtrade.data.converter.convert_trades_format") args = [ "convert-trade-data", "--format-from", diff --git a/tests/commands/test_startup_time.py b/tests/commands/test_startup_time.py new file mode 100644 index 000000000..37e2eeea2 --- /dev/null +++ b/tests/commands/test_startup_time.py @@ -0,0 +1,17 @@ +import subprocess +import time + + +MAXIMUM_STARTUP_TIME = 0.5 + + +def test_startup_time(): + # warm up to generate pyc + subprocess.run(["freqtrade", "-h"]) + + start = time.time() + subprocess.run(["freqtrade", "-h"]) + elapsed = time.time() - start + assert ( + elapsed < MAXIMUM_STARTUP_TIME + ), "The startup time is too long, try to use lazy import in the command entry function" diff --git a/tests/conftest_trades.py b/tests/conftest_trades.py index 7103b5169..f493a9302 100644 --- a/tests/conftest_trades.py +++ b/tests/conftest_trades.py @@ -38,7 +38,7 @@ def mock_trade_1(fee, is_short: bool): trade = Trade( pair="ETH/BTC", stake_amount=0.001, - amount=123.0, + amount=50.0, amount_requested=123.0, fee_open=fee.return_value, fee_close=fee.return_value, @@ -201,7 +201,7 @@ def mock_trade_4(fee, is_short: bool): trade = Trade( pair="ETC/BTC", stake_amount=0.001, - amount=123.0, + amount=0.0, amount_requested=124.0, fee_open=fee.return_value, fee_close=fee.return_value, diff --git a/tests/conftest_trades_usdt.py b/tests/conftest_trades_usdt.py index 1fc458279..3a547cce2 100644 --- a/tests/conftest_trades_usdt.py +++ b/tests/conftest_trades_usdt.py @@ -224,7 +224,7 @@ def mock_trade_usdt_4(fee, is_short: bool): trade = Trade( pair="NEO/USDT", stake_amount=20.0, - amount=10.0, + amount=0.0, amount_requested=10.01, fee_open=fee.return_value, fee_close=fee.return_value, diff --git a/tests/exchange/test_binance.py b/tests/exchange/test_binance.py index 7b0831520..623a9f17a 100644 --- a/tests/exchange/test_binance.py +++ b/tests/exchange/test_binance.py @@ -7,6 +7,7 @@ import pytest from freqtrade.enums import CandleType, MarginMode, TradingMode from freqtrade.exceptions import DependencyException, InvalidOrderException, OperationalException +from freqtrade.persistence import Trade from tests.conftest import EXMS, get_mock_coro, get_patched_exchange, log_has_re from tests.exchange.test_exchange import ccxt_exceptionhandlers @@ -171,59 +172,101 @@ def test_stoploss_adjust_binance(mocker, default_conf, sl1, sl2, sl3, side): @pytest.mark.parametrize( - "is_short, trading_mode, margin_mode, wallet_balance, " - "mm_ex_1, upnl_ex_1, maintenance_amt, amount, open_rate, " + "pair, is_short, trading_mode, margin_mode, wallet_balance, " + "maintenance_amt, amount, open_rate, open_trades," "mm_ratio, expected", [ ( + "ETH/USDT:USDT", False, "futures", "isolated", 1535443.01, - 0.0, - 0.0, 135365.00, 3683.979, 1456.84, + [], 0.10, 1114.78, ), ( + "ETH/USDT:USDT", False, "futures", "isolated", 1535443.01, - 0.0, - 0.0, 16300.000, 109.488, 32481.980, + [], 0.025, 18778.73, ), ( + "ETH/USDT:USDT", False, "futures", "cross", 1535443.01, - 71200.81144, - -56354.57, 135365.00, - 3683.979, - 1456.84, + 3683.979, # amount + 1456.84, # open_rate + [ + { + # From calc example + "pair": "BTC/USDT:USDT", + "open_rate": 32481.98, + "amount": 109.488, + "stake_amount": 3556387.02624, # open_rate * amount + "mark_price": 31967.27, + "mm_ratio": 0.025, + "maintenance_amt": 16300.0, + }, + { + # From calc example + "pair": "ETH/USDT:USDT", + "open_rate": 1456.84, + "amount": 3683.979, + "stake_amount": 5366967.96, + "mark_price": 1335.18, + "mm_ratio": 0.10, + "maintenance_amt": 135365.00, + }, + ], 0.10, 1153.26, ), ( + "BTC/USDT:USDT", False, "futures", "cross", 1535443.01, - 356512.508, - -448192.89, - 16300.000, - 109.488, - 32481.980, + 16300.0, + 109.488, # amount + 32481.980, # open_rate + [ + { + # From calc example + "pair": "BTC/USDT:USDT", + "open_rate": 32481.98, + "amount": 109.488, + "stake_amount": 3556387.02624, # open_rate * amount + "mark_price": 31967.27, + "mm_ratio": 0.025, + "maintenance_amt": 16300.0, + }, + { + # From calc example + "pair": "ETH/USDT:USDT", + "open_rate": 1456.84, + "amount": 3683.979, + "stake_amount": 5366967.96, + "mark_price": 1335.18, + "mm_ratio": 0.10, + "maintenance_amt": 135365.00, + }, + ], 0.025, 26316.89, ), @@ -232,15 +275,15 @@ def test_stoploss_adjust_binance(mocker, default_conf, sl1, sl2, sl3, side): def test_liquidation_price_binance( mocker, default_conf, - open_rate, + pair, is_short, trading_mode, margin_mode, wallet_balance, - mm_ex_1, - upnl_ex_1, maintenance_amt, amount, + open_rate, + open_trades, mm_ratio, expected, ): @@ -248,20 +291,48 @@ def test_liquidation_price_binance( default_conf["margin_mode"] = margin_mode default_conf["liquidation_buffer"] = 0.0 exchange = get_patched_exchange(mocker, default_conf, exchange="binance") - exchange.get_maintenance_ratio_and_amt = MagicMock(return_value=(mm_ratio, maintenance_amt)) + + def get_maint_ratio(pair_, stake_amount): + if pair_ != pair: + oc = [c for c in open_trades if c["pair"] == pair_][0] + return oc["mm_ratio"], oc["maintenance_amt"] + return mm_ratio, maintenance_amt + + def fetch_funding_rates(*args, **kwargs): + return { + t["pair"]: { + "symbol": t["pair"], + "markPrice": t["mark_price"], + } + for t in open_trades + } + + exchange.get_maintenance_ratio_and_amt = get_maint_ratio + exchange.fetch_funding_rates = fetch_funding_rates + + open_trade_objects = [ + Trade( + pair=t["pair"], + open_rate=t["open_rate"], + amount=t["amount"], + stake_amount=t["stake_amount"], + fee_open=0, + ) + for t in open_trades + ] + assert ( pytest.approx( round( exchange.get_liquidation_price( - pair="DOGE/USDT", + pair=pair, open_rate=open_rate, is_short=is_short, wallet_balance=wallet_balance, - mm_ex_1=mm_ex_1, - upnl_ex_1=upnl_ex_1, amount=amount, stake_amount=open_rate * amount, leverage=5, + open_trades=open_trade_objects, ), 2, ) diff --git a/tests/exchange/test_exchange.py b/tests/exchange/test_exchange.py index d71d2062e..07d5927c2 100644 --- a/tests/exchange/test_exchange.py +++ b/tests/exchange/test_exchange.py @@ -1274,6 +1274,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 +1289,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 +1369,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 +1516,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 +1561,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 +1671,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 @@ -5524,8 +5529,6 @@ def test_liquidation_price_is_none( stake_amount=open_rate * 71200.81144, leverage=5, wallet_balance=-56354.57, - mm_ex_1=0.10, - upnl_ex_1=0.0, ) is None ) @@ -6011,6 +6014,7 @@ def test_get_liquidation_price1(mocker, default_conf): stake_amount=18.884 * 0.8, leverage=leverage, wallet_balance=18.884 * 0.8, + open_trades=[], ) @@ -6141,6 +6145,7 @@ def test_get_liquidation_price( wallet_balance=amount * open_rate / leverage, leverage=leverage, is_short=is_short, + open_trades=[], ) if expected_liq is None: assert liq is None diff --git a/tests/exchange_online/conftest.py b/tests/exchange_online/conftest.py index 466020551..e9c594299 100644 --- a/tests/exchange_online/conftest.py +++ b/tests/exchange_online/conftest.py @@ -1,6 +1,5 @@ from copy import deepcopy from pathlib import Path -from typing import Tuple import pytest @@ -10,8 +9,8 @@ from freqtrade.resolvers.exchange_resolver import ExchangeResolver from tests.conftest import EXMS, get_default_conf_usdt -EXCHANGE_FIXTURE_TYPE = Tuple[Exchange, str] -EXCHANGE_WS_FIXTURE_TYPE = Tuple[Exchange, str, str] +EXCHANGE_FIXTURE_TYPE = tuple[Exchange, str] +EXCHANGE_WS_FIXTURE_TYPE = tuple[Exchange, str, str] # Exchanges that should be tested online @@ -74,6 +73,7 @@ EXCHANGES = { "hasQuoteVolume": True, "timeframe": "1h", "futures": False, + "skip_ws_tests": True, "sample_order": [ { "symbol": "SOLUSDT", @@ -424,14 +424,17 @@ def exchange_mode(request): def exchange_ws(request, exchange_conf, exchange_mode, class_mocker): class_mocker.patch("freqtrade.exchange.bybit.Bybit.additional_exchange_init") exchange_conf["exchange"]["enable_ws"] = True + exchange_param = EXCHANGES[request.param] + if exchange_param.get("skip_ws_tests"): + pytest.skip(f"{request.param} does not support websocket tests.") if exchange_mode == "spot": exchange, name = get_exchange(request.param, exchange_conf) - pair = EXCHANGES[request.param]["pair"] - elif EXCHANGES[request.param].get("futures"): + pair = exchange_param["pair"] + elif exchange_param.get("futures"): exchange, name = get_futures_exchange( request.param, exchange_conf, class_mocker=class_mocker ) - pair = EXCHANGES[request.param]["futures_pair"] + pair = exchange_param["futures_pair"] else: pytest.skip("Exchange does not support futures.") diff --git a/tests/exchange_online/test_ccxt_compat.py b/tests/exchange_online/test_ccxt_compat.py index fbb6bca05..32133d8e5 100644 --- a/tests/exchange_online/test_ccxt_compat.py +++ b/tests/exchange_online/test_ccxt_compat.py @@ -457,6 +457,7 @@ class TestCCXTExchange: stake_amount=100, leverage=5, wallet_balance=100, + open_trades=[], ) assert isinstance(liquidation_price, float) assert liquidation_price >= 0.0 @@ -469,6 +470,7 @@ class TestCCXTExchange: stake_amount=100, leverage=5, wallet_balance=100, + open_trades=[], ) assert isinstance(liquidation_price, float) assert liquidation_price >= 0.0 diff --git a/tests/freqai/conftest.py b/tests/freqai/conftest.py index 887dfe3a4..442f53020 100644 --- a/tests/freqai/conftest.py +++ b/tests/freqai/conftest.py @@ -2,7 +2,7 @@ import platform import sys from copy import deepcopy from pathlib import Path -from typing import Any, Dict +from typing import Any from unittest.mock import MagicMock import pytest @@ -112,7 +112,7 @@ def make_rl_config(conf): return conf -def mock_pytorch_mlp_model_training_parameters() -> Dict[str, Any]: +def mock_pytorch_mlp_model_training_parameters() -> dict[str, Any]: return { "learning_rate": 3e-4, "trainer_kwargs": { diff --git a/tests/freqtradebot/test_freqtradebot.py b/tests/freqtradebot/test_freqtradebot.py index 8587e7f9d..836f84580 100644 --- a/tests/freqtradebot/test_freqtradebot.py +++ b/tests/freqtradebot/test_freqtradebot.py @@ -5,7 +5,6 @@ import logging import time from copy import deepcopy from datetime import timedelta -from typing import List from unittest.mock import ANY, MagicMock, PropertyMock, patch import pytest @@ -553,7 +552,7 @@ def test_enter_positions_global_pairlock( @pytest.mark.parametrize("is_short", [False, True]) def test_handle_protections(mocker, default_conf_usdt, fee, is_short): - default_conf_usdt["protections"] = [ + default_conf_usdt["_strategy_protections"] = [ {"method": "CooldownPeriod", "stop_duration": 60}, { "method": "StoplossGuard", @@ -5442,7 +5441,7 @@ def test_position_adjust(mocker, default_conf_usdt, fee) -> None: assert trade.amount == 10 assert trade.stake_amount == 110 assert not trade.fee_updated("buy") - trades: List[Trade] = Trade.get_open_trades_without_assigned_fees() + trades: list[Trade] = Trade.get_open_trades_without_assigned_fees() assert len(trades) == 1 assert trade.is_open assert not trade.fee_updated("buy") @@ -5468,7 +5467,7 @@ def test_position_adjust(mocker, default_conf_usdt, fee) -> None: assert orders assert len(orders) == 2 # Assert that the trade is found as open and without fees - trades: List[Trade] = Trade.get_open_trades_without_assigned_fees() + trades: list[Trade] = Trade.get_open_trades_without_assigned_fees() assert len(trades) == 1 # Assert trade is as expected trade = Trade.session.scalars(select(Trade)).first() @@ -5525,7 +5524,7 @@ def test_position_adjust(mocker, default_conf_usdt, fee) -> None: assert order.order_id == "651" # Assert that the trade is not found as open and without fees - trades: List[Trade] = Trade.get_open_trades_without_assigned_fees() + trades: list[Trade] = Trade.get_open_trades_without_assigned_fees() assert len(trades) == 1 # Add a second DCA @@ -5725,7 +5724,7 @@ def test_position_adjust2(mocker, default_conf_usdt, fee) -> None: exit_check=ExitCheckTuple(exit_type=ExitType.PARTIAL_EXIT), sub_trade_amt=amount, ) - trades: List[Trade] = trade.get_open_trades_without_assigned_fees() + trades: list[Trade] = trade.get_open_trades_without_assigned_fees() assert len(trades) == 1 # Assert trade is as expected (averaged dca) diff --git a/tests/leverage/test_update_liquidation_price.py b/tests/leverage/test_update_liquidation_price.py new file mode 100644 index 000000000..1b25babd0 --- /dev/null +++ b/tests/leverage/test_update_liquidation_price.py @@ -0,0 +1,57 @@ +from unittest.mock import MagicMock + +import pytest + +from freqtrade.enums.marginmode import MarginMode +from freqtrade.leverage.liquidation_price import update_liquidation_prices + + +@pytest.mark.parametrize("dry_run", [False, True]) +@pytest.mark.parametrize("margin_mode", [MarginMode.CROSS, MarginMode.ISOLATED]) +def test_update_liquidation_prices(mocker, margin_mode, dry_run): + # Heavily mocked test - Only testing the logic of the function + # update liquidation price for trade in isolated mode + # update liquidation price for all trades in cross mode + exchange = MagicMock() + exchange.margin_mode = margin_mode + wallets = MagicMock() + trade_mock = MagicMock() + + mocker.patch("freqtrade.persistence.Trade.get_open_trades", return_value=[trade_mock]) + + update_liquidation_prices( + trade=trade_mock, + exchange=exchange, + wallets=wallets, + stake_currency="USDT", + dry_run=dry_run, + ) + + assert trade_mock.set_liquidation_price.call_count == 1 + + assert wallets.get_total.call_count == ( + 0 if margin_mode == MarginMode.ISOLATED or not dry_run else 1 + ) + + # Test with multiple trades + trade_mock.reset_mock() + trade_mock_2 = MagicMock() + + mocker.patch( + "freqtrade.persistence.Trade.get_open_trades", return_value=[trade_mock, trade_mock_2] + ) + + update_liquidation_prices( + trade=trade_mock, + exchange=exchange, + wallets=wallets, + stake_currency="USDT", + dry_run=dry_run, + ) + # Trade2 is only updated in cross mode + assert trade_mock_2.set_liquidation_price.call_count == ( + 1 if margin_mode == MarginMode.CROSS else 0 + ) + assert trade_mock.set_liquidation_price.call_count == 1 + + assert wallets.call_count == 0 if not dry_run else 1 diff --git a/tests/optimize/__init__.py b/tests/optimize/__init__.py index c824e4484..15e19cd75 100644 --- a/tests/optimize/__init__.py +++ b/tests/optimize/__init__.py @@ -1,5 +1,5 @@ from datetime import timedelta -from typing import Dict, List, NamedTuple, Optional +from typing import NamedTuple, Optional from pandas import DataFrame @@ -29,10 +29,10 @@ class BTContainer(NamedTuple): Minimal BacktestContainer defining Backtest inputs and results. """ - data: List[List[float]] + data: list[list[float]] stop_loss: float - roi: Dict[str, float] - trades: List[BTrade] + roi: dict[str, float] + trades: list[BTrade] profit_perc: float trailing_stop: bool = False trailing_only_offset_is_reached: bool = False diff --git a/tests/optimize/test_backtesting.py b/tests/optimize/test_backtesting.py index b25230791..416ffa262 100644 --- a/tests/optimize/test_backtesting.py +++ b/tests/optimize/test_backtesting.py @@ -1299,7 +1299,7 @@ def test_backtest_pricecontours_protections(default_conf, fee, mocker, testdatad # While this test IS a copy of test_backtest_pricecontours, it's needed to ensure # results do not carry-over to the next run, which is not given by using parametrize. patch_exchange(mocker) - default_conf["protections"] = [ + default_conf["_strategy_protections"] = [ { "method": "CooldownPeriod", "stop_duration": 3, @@ -1358,7 +1358,7 @@ def test_backtest_pricecontours( default_conf, mocker, testdatadir, protections, contour, expected ) -> None: if protections: - default_conf["protections"] = protections + default_conf["_strategy_protections"] = protections default_conf["enable_protections"] = True patch_exchange(mocker) @@ -1681,6 +1681,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, diff --git a/tests/optimize/test_hyperopt_tools.py b/tests/optimize/test_hyperopt_tools.py index c8a54e462..2351acbba 100644 --- a/tests/optimize/test_hyperopt_tools.py +++ b/tests/optimize/test_hyperopt_tools.py @@ -1,7 +1,6 @@ import logging import re from pathlib import Path -from typing import Dict, List import numpy as np import pytest @@ -14,7 +13,7 @@ from tests.conftest import CURRENT_TEST_STRATEGY, log_has, log_has_re # Functions for recurrent object patching -def create_results() -> List[Dict]: +def create_results() -> list[dict]: return [{"loss": 1, "result": "foo", "params": {}, "is_best": True}] diff --git a/tests/optimize/test_hyperoptloss.py b/tests/optimize/test_hyperoptloss.py index b78cdde30..53de37a0e 100644 --- a/tests/optimize/test_hyperoptloss.py +++ b/tests/optimize/test_hyperoptloss.py @@ -95,6 +95,7 @@ def test_loss_calculation_has_limited_profit(hyperopt_conf, hyperopt_results) -> "MaxDrawDownRelativeHyperOptLoss", "CalmarHyperOptLoss", "ProfitDrawDownHyperOptLoss", + "MultiMetricHyperOptLoss", ], ) def test_loss_functions_better_profits(default_conf, hyperopt_results, lossfunction) -> None: diff --git a/tests/persistence/test_persistence.py b/tests/persistence/test_persistence.py index 05160c74a..b92791ca5 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", ) diff --git a/tests/plugins/test_protections.py b/tests/plugins/test_protections.py index 3fb27ce3d..bec2671eb 100644 --- a/tests/plugins/test_protections.py +++ b/tests/plugins/test_protections.py @@ -3,14 +3,17 @@ from datetime import datetime, timedelta, timezone import pytest -from freqtrade import constants from freqtrade.enums import ExitType +from freqtrade.exceptions import OperationalException from freqtrade.persistence import PairLocks, Trade from freqtrade.persistence.trade_model import Order from freqtrade.plugins.protectionmanager import ProtectionManager from tests.conftest import get_patched_freqtradebot, log_has_re +AVAILABLE_PROTECTIONS = ["CooldownPeriod", "LowProfitPairs", "MaxDrawdown", "StoplossGuard"] + + def generate_mock_trade( pair: str, fee: float, @@ -88,19 +91,76 @@ def generate_mock_trade( def test_protectionmanager(mocker, default_conf): - default_conf["protections"] = [ - {"method": protection} for protection in constants.AVAILABLE_PROTECTIONS + default_conf["_strategy_protections"] = [ + {"method": protection} for protection in AVAILABLE_PROTECTIONS ] freqtrade = get_patched_freqtradebot(mocker, default_conf) for handler in freqtrade.protections._protection_handlers: - assert handler.name in constants.AVAILABLE_PROTECTIONS + assert handler.name in AVAILABLE_PROTECTIONS if not handler.has_global_stop: assert handler.global_stop(datetime.now(timezone.utc), "*") is None if not handler.has_local_stop: assert handler.stop_per_pair("XRP/BTC", datetime.now(timezone.utc), "*") is None +@pytest.mark.parametrize( + "protconf,expected", + [ + ([], None), + ([{"method": "StoplossGuard", "lookback_period": 2000, "stop_duration_candles": 10}], None), + ([{"method": "StoplossGuard", "lookback_period_candles": 20, "stop_duration": 10}], None), + ( + [ + { + "method": "StoplossGuard", + "lookback_period_candles": 20, + "lookback_period": 2000, + "stop_duration": 10, + } + ], + r"Protections must specify either `lookback_period`.*", + ), + ( + [ + { + "method": "StoplossGuard", + "lookback_period": 20, + "stop_duration": 10, + "stop_duration_candles": 10, + } + ], + r"Protections must specify either `stop_duration`.*", + ), + ( + [ + { + "method": "StoplossGuard", + "lookback_period": 20, + "stop_duration": 10, + "unlock_at": "20:02", + } + ], + r"Protections must specify either `unlock_at`, `stop_duration` or.*", + ), + ( + [{"method": "StoplossGuard", "lookback_period_candles": 20, "unlock_at": "20:02"}], + None, + ), + ( + [{"method": "StoplossGuard", "lookback_period_candles": 20, "unlock_at": "55:102"}], + "Invalid date format for unlock_at: 55:102.", + ), + ], +) +def test_validate_protections(protconf, expected): + if expected: + with pytest.raises(OperationalException, match=expected): + ProtectionManager.validate_protections(protconf) + else: + ProtectionManager.validate_protections(protconf) + + @pytest.mark.parametrize( "timeframe,expected_lookback,expected_stop,protconf", [ @@ -196,7 +256,7 @@ def test_protections_init(default_conf, timeframe, expected_lookback, expected_s @pytest.mark.usefixtures("init_persistence") def test_stoploss_guard(mocker, default_conf, fee, caplog, is_short): # Active for both sides (long and short) - default_conf["protections"] = [ + default_conf["_strategy_protections"] = [ {"method": "StoplossGuard", "lookback_period": 60, "stop_duration": 40, "trade_limit": 3} ] freqtrade = get_patched_freqtradebot(mocker, default_conf) @@ -268,7 +328,7 @@ def test_stoploss_guard(mocker, default_conf, fee, caplog, is_short): @pytest.mark.parametrize("only_per_side", [False, True]) @pytest.mark.usefixtures("init_persistence") def test_stoploss_guard_perpair(mocker, default_conf, fee, caplog, only_per_pair, only_per_side): - default_conf["protections"] = [ + default_conf["_strategy_protections"] = [ { "method": "StoplossGuard", "lookback_period": 60, @@ -379,7 +439,7 @@ def test_stoploss_guard_perpair(mocker, default_conf, fee, caplog, only_per_pair @pytest.mark.usefixtures("init_persistence") def test_CooldownPeriod(mocker, default_conf, fee, caplog): - default_conf["protections"] = [ + default_conf["_strategy_protections"] = [ { "method": "CooldownPeriod", "stop_duration": 60, @@ -425,7 +485,7 @@ def test_CooldownPeriod(mocker, default_conf, fee, caplog): @pytest.mark.usefixtures("init_persistence") def test_CooldownPeriod_unlock_at(mocker, default_conf, fee, caplog, time_machine): - default_conf["protections"] = [ + default_conf["_strategy_protections"] = [ { "method": "CooldownPeriod", "unlock_at": "05:00", @@ -509,7 +569,7 @@ def test_CooldownPeriod_unlock_at(mocker, default_conf, fee, caplog, time_machin @pytest.mark.parametrize("only_per_side", [False, True]) @pytest.mark.usefixtures("init_persistence") def test_LowProfitPairs(mocker, default_conf, fee, caplog, only_per_side): - default_conf["protections"] = [ + default_conf["_strategy_protections"] = [ { "method": "LowProfitPairs", "lookback_period": 400, @@ -599,7 +659,7 @@ def test_LowProfitPairs(mocker, default_conf, fee, caplog, only_per_side): @pytest.mark.usefixtures("init_persistence") def test_MaxDrawdown(mocker, default_conf, fee, caplog): - default_conf["protections"] = [ + default_conf["_strategy_protections"] = [ { "method": "MaxDrawdown", "lookback_period": 1000, @@ -812,7 +872,7 @@ def test_MaxDrawdown(mocker, default_conf, fee, caplog): def test_protection_manager_desc( mocker, default_conf, protectionconf, desc_expected, exception_expected ): - default_conf["protections"] = [protectionconf] + default_conf["_strategy_protections"] = [protectionconf] freqtrade = get_patched_freqtradebot(mocker, default_conf) short_desc = str(freqtrade.protections.short_desc()) diff --git a/tests/rpc/test_rpc_apiserver.py b/tests/rpc/test_rpc_apiserver.py index 4f5ef7860..730a6b05e 100644 --- a/tests/rpc/test_rpc_apiserver.py +++ b/tests/rpc/test_rpc_apiserver.py @@ -185,7 +185,7 @@ def test_api_ui_fallback(botclient, mocker): def test_api_ui_version(botclient, mocker): _ftbot, client = botclient - mocker.patch("freqtrade.commands.deploy_commands.read_ui_version", return_value="0.1.2") + mocker.patch("freqtrade.commands.deploy_ui.read_ui_version", return_value="0.1.2") rc = client_get(client, "/ui_version") assert rc.status_code == 200 assert rc.json()["version"] == "0.1.2" @@ -1269,7 +1269,7 @@ def test_api_mix_tag(botclient, fee): @pytest.mark.parametrize( "is_short,current_rate,open_trade_value", - [(True, 1.098e-05, 15.0911775), (False, 1.099e-05, 15.1668225)], + [(True, 1.098e-05, 6.134625), (False, 1.099e-05, 6.165375)], ) def test_api_status( botclient, mocker, ticker, fee, markets, is_short, current_rate, open_trade_value @@ -1294,7 +1294,7 @@ def test_api_status( assert_response(rc) assert len(rc.json()) == 4 assert rc.json()[0] == { - "amount": 123.0, + "amount": 50.0, "amount_requested": 123.0, "close_date": None, "close_timestamp": None, @@ -2189,6 +2189,22 @@ def test_api_exchanges(botclient): } +def test_list_hyperoptloss(botclient, tmp_path): + ftbot, client = botclient + ftbot.config["user_data_dir"] = tmp_path + + rc = client_get(client, f"{BASE_URI}/hyperoptloss") + assert_response(rc) + response = rc.json() + assert isinstance(response["loss_functions"], list) + assert len(response["loss_functions"]) > 0 + + sharpeloss = [r for r in response["loss_functions"] if r["name"] == "SharpeHyperOptLoss"] + assert len(sharpeloss) == 1 + assert "Sharpe Ratio calculation" in sharpeloss[0]["description"] + assert len([r for r in response["loss_functions"] if r["name"] == "SortinoHyperOptLoss"]) == 1 + + def test_api_freqaimodels(botclient, tmp_path, mocker): ftbot, client = botclient ftbot.config["user_data_dir"] = tmp_path @@ -2339,9 +2355,7 @@ def test_api_pairlists_evaluate(botclient, tmp_path, mocker): ] assert response["result"]["length"] == 2 # Patch __run_pairlists - plm = mocker.patch( - "freqtrade.rpc.api_server.api_background_tasks.__run_pairlist", return_value=None - ) + plm = mocker.patch("freqtrade.rpc.api_server.api_pairlists.__run_pairlist", return_value=None) body = { "pairlists": [ { @@ -2598,6 +2612,8 @@ def test_api_delete_backtest_history_entry(botclient, tmp_path: Path): file_path.touch() meta_path = file_path.with_suffix(".meta.json") meta_path.touch() + market_change_path = file_path.with_name(file_path.stem + "_market_change.feather") + market_change_path.touch() rc = client_delete(client, f"{BASE_URI}/backtest/history/randomFile.json") assert_response(rc, 503) @@ -2614,6 +2630,7 @@ def test_api_delete_backtest_history_entry(botclient, tmp_path: Path): assert not file_path.exists() assert not meta_path.exists() + assert not market_change_path.exists() def test_api_patch_backtest_history_entry(botclient, tmp_path: Path): diff --git a/tests/rpc/test_rpc_manager.py b/tests/rpc/test_rpc_manager.py index 2792fd082..67755255f 100644 --- a/tests/rpc/test_rpc_manager.py +++ b/tests/rpc/test_rpc_manager.py @@ -173,7 +173,7 @@ def test_startupmessages_telegram_enabled(mocker, default_conf) -> None: telegram_mock.reset_mock() default_conf["dry_run"] = True default_conf["whitelist"] = {"method": "VolumePairList", "config": {"number_assets": 20}} - default_conf["protections"] = [ + default_conf["_strategy_protections"] = [ {"method": "StoplossGuard", "lookback_period": 60, "trade_limit": 2, "stop_duration": 60} ] freqtradebot = get_patched_freqtradebot(mocker, default_conf) diff --git a/tests/strategy/strats/freqai_rl_test_strat.py b/tests/strategy/strats/freqai_rl_test_strat.py index 359ac764d..3c505fa81 100644 --- a/tests/strategy/strats/freqai_rl_test_strat.py +++ b/tests/strategy/strats/freqai_rl_test_strat.py @@ -1,6 +1,5 @@ import logging from functools import reduce -from typing import Dict import talib.abstract as ta from pandas import DataFrame @@ -26,19 +25,19 @@ class freqai_rl_test_strat(IStrategy): can_short = False def feature_engineering_expand_all( - self, dataframe: DataFrame, period: int, metadata: Dict, **kwargs + self, dataframe: DataFrame, period: int, metadata: dict, **kwargs ): dataframe["%-rsi-period"] = ta.RSI(dataframe, timeperiod=period) return dataframe - def feature_engineering_expand_basic(self, dataframe: DataFrame, metadata: Dict, **kwargs): + def feature_engineering_expand_basic(self, dataframe: DataFrame, metadata: dict, **kwargs): dataframe["%-pct-change"] = dataframe["close"].pct_change() dataframe["%-raw_volume"] = dataframe["volume"] return dataframe - def feature_engineering_standard(self, dataframe: DataFrame, metadata: Dict, **kwargs): + def feature_engineering_standard(self, dataframe: DataFrame, metadata: dict, **kwargs): dataframe["%-day_of_week"] = dataframe["date"].dt.dayofweek dataframe["%-hour_of_day"] = dataframe["date"].dt.hour @@ -49,7 +48,7 @@ class freqai_rl_test_strat(IStrategy): return dataframe - def set_freqai_targets(self, dataframe: DataFrame, metadata: Dict, **kwargs): + def set_freqai_targets(self, dataframe: DataFrame, metadata: dict, **kwargs): dataframe["&-action"] = 0 return dataframe diff --git a/tests/strategy/strats/freqai_test_classifier.py b/tests/strategy/strats/freqai_test_classifier.py index ab8ab87cb..f1b49fee6 100644 --- a/tests/strategy/strats/freqai_test_classifier.py +++ b/tests/strategy/strats/freqai_test_classifier.py @@ -1,6 +1,5 @@ import logging from functools import reduce -from typing import Dict import numpy as np import talib.abstract as ta @@ -58,7 +57,7 @@ class freqai_test_classifier(IStrategy): return informative_pairs def feature_engineering_expand_all( - self, dataframe: DataFrame, period: int, metadata: Dict, **kwargs + self, dataframe: DataFrame, period: int, metadata: dict, **kwargs ): dataframe["%-rsi-period"] = ta.RSI(dataframe, timeperiod=period) dataframe["%-mfi-period"] = ta.MFI(dataframe, timeperiod=period) @@ -66,20 +65,20 @@ class freqai_test_classifier(IStrategy): return dataframe - def feature_engineering_expand_basic(self, dataframe: DataFrame, metadata: Dict, **kwargs): + def feature_engineering_expand_basic(self, dataframe: DataFrame, metadata: dict, **kwargs): dataframe["%-pct-change"] = dataframe["close"].pct_change() dataframe["%-raw_volume"] = dataframe["volume"] dataframe["%-raw_price"] = dataframe["close"] return dataframe - def feature_engineering_standard(self, dataframe: DataFrame, metadata: Dict, **kwargs): + def feature_engineering_standard(self, dataframe: DataFrame, metadata: dict, **kwargs): dataframe["%-day_of_week"] = dataframe["date"].dt.dayofweek dataframe["%-hour_of_day"] = dataframe["date"].dt.hour return dataframe - def set_freqai_targets(self, dataframe: DataFrame, metadata: Dict, **kwargs): + def set_freqai_targets(self, dataframe: DataFrame, metadata: dict, **kwargs): self.freqai.class_names = ["down", "up"] dataframe["&s-up_or_down"] = np.where( dataframe["close"].shift(-100) > dataframe["close"], "up", "down" diff --git a/tests/strategy/strats/freqai_test_multimodel_classifier_strat.py b/tests/strategy/strats/freqai_test_multimodel_classifier_strat.py index ef32edf2a..61e1b5165 100644 --- a/tests/strategy/strats/freqai_test_multimodel_classifier_strat.py +++ b/tests/strategy/strats/freqai_test_multimodel_classifier_strat.py @@ -1,6 +1,5 @@ import logging from functools import reduce -from typing import Dict import numpy as np import talib.abstract as ta @@ -45,7 +44,7 @@ class freqai_test_multimodel_classifier_strat(IStrategy): max_roi_time_long = IntParameter(0, 800, default=400, space="sell", optimize=False, load=True) def feature_engineering_expand_all( - self, dataframe: DataFrame, period: int, metadata: Dict, **kwargs + self, dataframe: DataFrame, period: int, metadata: dict, **kwargs ): dataframe["%-rsi-period"] = ta.RSI(dataframe, timeperiod=period) dataframe["%-mfi-period"] = ta.MFI(dataframe, timeperiod=period) @@ -53,20 +52,20 @@ class freqai_test_multimodel_classifier_strat(IStrategy): return dataframe - def feature_engineering_expand_basic(self, dataframe: DataFrame, metadata: Dict, **kwargs): + def feature_engineering_expand_basic(self, dataframe: DataFrame, metadata: dict, **kwargs): dataframe["%-pct-change"] = dataframe["close"].pct_change() dataframe["%-raw_volume"] = dataframe["volume"] dataframe["%-raw_price"] = dataframe["close"] return dataframe - def feature_engineering_standard(self, dataframe: DataFrame, metadata: Dict, **kwargs): + def feature_engineering_standard(self, dataframe: DataFrame, metadata: dict, **kwargs): dataframe["%-day_of_week"] = dataframe["date"].dt.dayofweek dataframe["%-hour_of_day"] = dataframe["date"].dt.hour return dataframe - def set_freqai_targets(self, dataframe: DataFrame, metadata: Dict, **kwargs): + def set_freqai_targets(self, dataframe: DataFrame, metadata: dict, **kwargs): dataframe["&s-up_or_down"] = np.where( dataframe["close"].shift(-50) > dataframe["close"], "up", "down" ) diff --git a/tests/strategy/strats/freqai_test_multimodel_strat.py b/tests/strategy/strats/freqai_test_multimodel_strat.py index 46df7e275..e0d7cd2bc 100644 --- a/tests/strategy/strats/freqai_test_multimodel_strat.py +++ b/tests/strategy/strats/freqai_test_multimodel_strat.py @@ -1,6 +1,5 @@ import logging from functools import reduce -from typing import Dict import talib.abstract as ta from pandas import DataFrame @@ -44,7 +43,7 @@ class freqai_test_multimodel_strat(IStrategy): max_roi_time_long = IntParameter(0, 800, default=400, space="sell", optimize=False, load=True) def feature_engineering_expand_all( - self, dataframe: DataFrame, period: int, metadata: Dict, **kwargs + self, dataframe: DataFrame, period: int, metadata: dict, **kwargs ): dataframe["%-rsi-period"] = ta.RSI(dataframe, timeperiod=period) dataframe["%-mfi-period"] = ta.MFI(dataframe, timeperiod=period) @@ -52,20 +51,20 @@ class freqai_test_multimodel_strat(IStrategy): return dataframe - def feature_engineering_expand_basic(self, dataframe: DataFrame, metadata: Dict, **kwargs): + def feature_engineering_expand_basic(self, dataframe: DataFrame, metadata: dict, **kwargs): dataframe["%-pct-change"] = dataframe["close"].pct_change() dataframe["%-raw_volume"] = dataframe["volume"] dataframe["%-raw_price"] = dataframe["close"] return dataframe - def feature_engineering_standard(self, dataframe: DataFrame, metadata: Dict, **kwargs): + def feature_engineering_standard(self, dataframe: DataFrame, metadata: dict, **kwargs): dataframe["%-day_of_week"] = dataframe["date"].dt.dayofweek dataframe["%-hour_of_day"] = dataframe["date"].dt.hour return dataframe - def set_freqai_targets(self, dataframe: DataFrame, metadata: Dict, **kwargs): + def set_freqai_targets(self, dataframe: DataFrame, metadata: dict, **kwargs): dataframe["&-s_close"] = ( dataframe["close"] .shift(-self.freqai_info["feature_parameters"]["label_period_candles"]) diff --git a/tests/strategy/strats/freqai_test_strat.py b/tests/strategy/strats/freqai_test_strat.py index 90c4642ba..349f4c531 100644 --- a/tests/strategy/strats/freqai_test_strat.py +++ b/tests/strategy/strats/freqai_test_strat.py @@ -1,6 +1,5 @@ import logging from functools import reduce -from typing import Dict import talib.abstract as ta from pandas import DataFrame @@ -44,7 +43,7 @@ class freqai_test_strat(IStrategy): max_roi_time_long = IntParameter(0, 800, default=400, space="sell", optimize=False, load=True) def feature_engineering_expand_all( - self, dataframe: DataFrame, period: int, metadata: Dict, **kwargs + self, dataframe: DataFrame, period: int, metadata: dict, **kwargs ): dataframe["%-rsi-period"] = ta.RSI(dataframe, timeperiod=period) dataframe["%-mfi-period"] = ta.MFI(dataframe, timeperiod=period) @@ -52,20 +51,20 @@ class freqai_test_strat(IStrategy): return dataframe - def feature_engineering_expand_basic(self, dataframe: DataFrame, metadata: Dict, **kwargs): + def feature_engineering_expand_basic(self, dataframe: DataFrame, metadata: dict, **kwargs): dataframe["%-pct-change"] = dataframe["close"].pct_change() dataframe["%-raw_volume"] = dataframe["volume"] dataframe["%-raw_price"] = dataframe["close"] return dataframe - def feature_engineering_standard(self, dataframe: DataFrame, metadata: Dict, **kwargs): + def feature_engineering_standard(self, dataframe: DataFrame, metadata: dict, **kwargs): dataframe["%-day_of_week"] = dataframe["date"].dt.dayofweek dataframe["%-hour_of_day"] = dataframe["date"].dt.hour return dataframe - def set_freqai_targets(self, dataframe: DataFrame, metadata: Dict, **kwargs): + def set_freqai_targets(self, dataframe: DataFrame, metadata: dict, **kwargs): dataframe["&-s_close"] = ( dataframe["close"] .shift(-self.freqai_info["feature_parameters"]["label_period_candles"]) diff --git a/tests/strategy/strats/strategy_test_v3.py b/tests/strategy/strats/strategy_test_v3.py index 71404242a..007c7655e 100644 --- a/tests/strategy/strats/strategy_test_v3.py +++ b/tests/strategy/strats/strategy_test_v3.py @@ -75,15 +75,13 @@ class StrategyTestV3(IStrategy): protection_cooldown_lookback = IntParameter([0, 50], default=30) # TODO: Can this work with protection tests? (replace HyperoptableStrategy implicitly ... ) - # @property - # def protections(self): - # prot = [] - # if self.protection_enabled.value: - # prot.append({ - # "method": "CooldownPeriod", - # "stop_duration_candles": self.protection_cooldown_lookback.value - # }) - # return prot + @property + def protections(self): + prot = [] + if self.protection_enabled.value: + # Workaround to simplify tests. This will not work in real scenarios. + prot = self.config.get("_strategy_protections", {}) + return prot bot_started = False diff --git a/tests/strategy/strats/strategy_test_v3_recursive_issue.py b/tests/strategy/strats/strategy_test_v3_recursive_issue.py index d03486886..e709d7ad4 100644 --- a/tests/strategy/strats/strategy_test_v3_recursive_issue.py +++ b/tests/strategy/strats/strategy_test_v3_recursive_issue.py @@ -33,6 +33,9 @@ class strategy_test_v3_recursive_issue(IStrategy): # Has both bias1 and bias2 dataframe["rsi_lookahead"] = ta.RSI(dataframe, timeperiod=50).shift(-1) + # String columns shouldn't cause issues + dataframe["test_string_column"] = f"a{len(dataframe)}" + return dataframe def populate_entry_trend(self, dataframe: DataFrame, metadata: dict) -> DataFrame: diff --git a/tests/strategy/test_interface.py b/tests/strategy/test_interface.py index 5459a0ff2..441eb5673 100644 --- a/tests/strategy/test_interface.py +++ b/tests/strategy/test_interface.py @@ -296,13 +296,6 @@ def test_assert_df(ohlcv_history, caplog): ohlcv_history.loc[df_len, "close"], ohlcv_history.loc[0, "date"], ) - with pytest.raises(StrategyError, match="enter_long/buy column not set."): - _STRATEGY.assert_df( - ohlcv_history.drop("enter_long", axis=1), - len(ohlcv_history), - ohlcv_history.loc[df_len, "close"], - ohlcv_history.loc[0, "date"], - ) _STRATEGY.disable_dataframe_checks = True caplog.clear() diff --git a/tests/test_configuration.py b/tests/test_configuration.py index d77fae6a8..829bf699a 100644 --- a/tests/test_configuration.py +++ b/tests/test_configuration.py @@ -812,65 +812,6 @@ def test_validate_whitelist(default_conf): validate_config_consistency(conf) -@pytest.mark.parametrize( - "protconf,expected", - [ - ([], None), - ([{"method": "StoplossGuard", "lookback_period": 2000, "stop_duration_candles": 10}], None), - ([{"method": "StoplossGuard", "lookback_period_candles": 20, "stop_duration": 10}], None), - ( - [ - { - "method": "StoplossGuard", - "lookback_period_candles": 20, - "lookback_period": 2000, - "stop_duration": 10, - } - ], - r"Protections must specify either `lookback_period`.*", - ), - ( - [ - { - "method": "StoplossGuard", - "lookback_period": 20, - "stop_duration": 10, - "stop_duration_candles": 10, - } - ], - r"Protections must specify either `stop_duration`.*", - ), - ( - [ - { - "method": "StoplossGuard", - "lookback_period": 20, - "stop_duration": 10, - "unlock_at": "20:02", - } - ], - r"Protections must specify either `unlock_at`, `stop_duration` or.*", - ), - ( - [{"method": "StoplossGuard", "lookback_period_candles": 20, "unlock_at": "20:02"}], - None, - ), - ( - [{"method": "StoplossGuard", "lookback_period_candles": 20, "unlock_at": "55:102"}], - "Invalid date format for unlock_at: 55:102.", - ), - ], -) -def test_validate_protections(default_conf, protconf, expected): - conf = deepcopy(default_conf) - conf["protections"] = protconf - if expected: - with pytest.raises(OperationalException, match=expected): - validate_config_consistency(conf) - else: - validate_config_consistency(conf) - - def test_validate_ask_orderbook(default_conf, caplog) -> None: conf = deepcopy(default_conf) conf["exit_pricing"]["use_order_book"] = True @@ -1533,8 +1474,8 @@ def test_process_deprecated_protections(default_conf, caplog): assert not log_has(message, caplog) config["protections"] = [] - process_temporary_deprecated_settings(config) - assert log_has(message, caplog) + with pytest.raises(ConfigurationError, match=message): + process_temporary_deprecated_settings(config) def test_flat_vars_to_nested_dict(caplog): diff --git a/tests/test_log_setup.py b/tests/test_log_setup.py index 142134b34..6dc88b79d 100644 --- a/tests/test_log_setup.py +++ b/tests/test_log_setup.py @@ -86,7 +86,7 @@ def test_set_loggers_Filehandler(tmp_path): logger = logging.getLogger() orig_handlers = logger.handlers logger.handlers = [] - logfile = tmp_path / "ft_logfile.log" + logfile = tmp_path / "logs/ft_logfile.log" config = { "verbosity": 2, "logfile": str(logfile), @@ -107,6 +107,29 @@ def test_set_loggers_Filehandler(tmp_path): logger.handlers = orig_handlers +@pytest.mark.skipif(sys.platform == "win32", reason="does not run on windows") +def test_set_loggers_Filehandler_without_permission(tmp_path): + logger = logging.getLogger() + orig_handlers = logger.handlers + logger.handlers = [] + + try: + tmp_path.chmod(0o400) + logfile = tmp_path / "logs/ft_logfile.log" + config = { + "verbosity": 2, + "logfile": str(logfile), + } + + setup_logging_pre() + with pytest.raises(OperationalException): + setup_logging(config) + + logger.handlers = orig_handlers + finally: + tmp_path.chmod(0o700) + + @pytest.mark.skip(reason="systemd is not installed on every system, so we're not testing this.") def test_set_loggers_journald(mocker): logger = logging.getLogger() diff --git a/tests/test_main.py b/tests/test_main.py index b230d4e99..3c8150c38 100644 --- a/tests/test_main.py +++ b/tests/test_main.py @@ -121,7 +121,7 @@ def test_main_operational_exception(mocker, default_conf, caplog) -> None: def test_main_operational_exception1(mocker, default_conf, caplog) -> None: patch_exchange(mocker) mocker.patch( - "freqtrade.commands.list_commands.list_available_exchanges", + "freqtrade.exchange.list_available_exchanges", MagicMock(side_effect=ValueError("Oh snap!")), ) patched_configuration_load_config_file(mocker, default_conf) @@ -135,7 +135,7 @@ def test_main_operational_exception1(mocker, default_conf, caplog) -> None: assert log_has("Fatal exception!", caplog) assert not log_has_re(r"SIGINT.*", caplog) mocker.patch( - "freqtrade.commands.list_commands.list_available_exchanges", + "freqtrade.exchange.list_available_exchanges", MagicMock(side_effect=KeyboardInterrupt), ) with pytest.raises(SystemExit): @@ -147,7 +147,7 @@ def test_main_operational_exception1(mocker, default_conf, caplog) -> None: def test_main_ConfigurationError(mocker, default_conf, caplog) -> None: patch_exchange(mocker) mocker.patch( - "freqtrade.commands.list_commands.list_available_exchanges", + "freqtrade.exchange.list_available_exchanges", MagicMock(side_effect=ConfigurationError("Oh snap!")), ) patched_configuration_load_config_file(mocker, default_conf) diff --git a/tests/test_strategy_updater.py b/tests/test_strategy_updater.py index a53c6c9b7..96daee973 100644 --- a/tests/test_strategy_updater.py +++ b/tests/test_strategy_updater.py @@ -2,20 +2,13 @@ import re import shutil -import sys from pathlib import Path -import pytest - from freqtrade.commands.strategy_utils_commands import start_strategy_update from freqtrade.strategy.strategyupdater import StrategyUpdater from tests.conftest import get_args -if sys.version_info < (3, 9): - pytest.skip("StrategyUpdater is not compatible with Python 3.8", allow_module_level=True) - - def test_strategy_updater_start(user_dir, capsys) -> None: # Effective test without mocks. teststrats = Path(__file__).parent / "strategy/strats" diff --git a/tests/test_wallets.py b/tests/test_wallets.py index ef3129b88..545793151 100644 --- a/tests/test_wallets.py +++ b/tests/test_wallets.py @@ -362,7 +362,8 @@ def test_sync_wallet_dry(mocker, default_conf_usdt, fee): assert len(freqtrade.wallets._wallets) == 5 assert len(freqtrade.wallets._positions) == 0 bal = freqtrade.wallets.get_all_balances() - assert bal["NEO"].total == 10 + # NEO trade is not filled yet. + assert bal["NEO"].total == 0 assert bal["XRP"].total == 10 assert bal["LTC"].total == 2 usdt_bal = bal["USDT"] @@ -410,11 +411,11 @@ def test_sync_wallet_futures_dry(mocker, default_conf, fee): def test_check_exit_amount(mocker, default_conf, fee): freqtrade = get_patched_freqtradebot(mocker, default_conf) update_mock = mocker.patch("freqtrade.wallets.Wallets.update") - total_mock = mocker.patch("freqtrade.wallets.Wallets.get_total", return_value=123) + total_mock = mocker.patch("freqtrade.wallets.Wallets.get_total", return_value=50.0) create_mock_trades(fee, is_short=None) trade = Trade.session.scalars(select(Trade)).first() - assert trade.amount == 123 + assert trade.amount == 50.0 assert freqtrade.wallets.check_exit_amount(trade) is True assert update_mock.call_count == 0 @@ -423,7 +424,7 @@ def test_check_exit_amount(mocker, default_conf, fee): update_mock.reset_mock() # Reduce returned amount to below the trade amount - which should # trigger a wallet update and return False, triggering "order refinding" - total_mock = mocker.patch("freqtrade.wallets.Wallets.get_total", return_value=100) + total_mock = mocker.patch("freqtrade.wallets.Wallets.get_total", return_value=40) assert freqtrade.wallets.check_exit_amount(trade) is False assert update_mock.call_count == 1 assert total_mock.call_count == 2 @@ -433,12 +434,12 @@ def test_check_exit_amount_futures(mocker, default_conf, fee): default_conf["trading_mode"] = "futures" default_conf["margin_mode"] = "isolated" freqtrade = get_patched_freqtradebot(mocker, default_conf) - total_mock = mocker.patch("freqtrade.wallets.Wallets.get_total", return_value=123) + total_mock = mocker.patch("freqtrade.wallets.Wallets.get_total", return_value=50) create_mock_trades(fee, is_short=None) trade = Trade.session.scalars(select(Trade)).first() trade.trading_mode = "futures" - assert trade.amount == 123 + assert trade.amount == 50 assert freqtrade.wallets.check_exit_amount(trade) is True assert total_mock.call_count == 0