diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml index 63a0b7468..cfc8ac3b6 100644 --- a/.github/workflows/ci.yml +++ b/.github/workflows/ci.yml @@ -23,7 +23,7 @@ jobs: runs-on: ${{ matrix.os }} strategy: matrix: - os: [ ubuntu-18.04, ubuntu-20.04, ubuntu-22.04 ] + os: [ ubuntu-20.04, ubuntu-22.04 ] python-version: ["3.8", "3.9", "3.10"] steps: diff --git a/.pre-commit-config.yaml b/.pre-commit-config.yaml index 9e502e97b..57ce81b8c 100644 --- a/.pre-commit-config.yaml +++ b/.pre-commit-config.yaml @@ -2,7 +2,7 @@ # See https://pre-commit.com/hooks.html for more hooks repos: - repo: https://github.com/pycqa/flake8 - rev: "4.0.1" + rev: "6.0.0" hooks: - id: flake8 # stages: [push] @@ -13,22 +13,22 @@ repos: - id: mypy exclude: build_helpers additional_dependencies: - - types-cachetools==5.2.1 + - types-cachetools==5.3.0.0 - types-filelock==3.2.7 - - types-requests==2.28.11.8 + - types-requests==2.28.11.13 - types-tabulate==0.9.0.0 - types-python-dateutil==2.8.19.6 # stages: [push] - repo: https://github.com/pycqa/isort - rev: "5.10.1" + rev: "5.12.0" hooks: - id: isort name: isort (python) # stages: [push] - repo: https://github.com/pre-commit/pre-commit-hooks - rev: v2.4.0 + rev: v4.4.0 hooks: - id: end-of-file-fixer exclude: | diff --git a/Dockerfile b/Dockerfile index b3e5d5e88..6a4a168c1 100644 --- a/Dockerfile +++ b/Dockerfile @@ -1,4 +1,4 @@ -FROM python:3.10.7-slim-bullseye as base +FROM python:3.10.10-slim-bullseye as base # Setup env ENV LANG C.UTF-8 diff --git a/README.md b/README.md index 2ab62793d..c8bc50dac 100644 --- a/README.md +++ b/README.md @@ -40,6 +40,7 @@ Please read the [exchange specific notes](docs/exchanges.md) to learn about even - [X] [Binance](https://www.binance.com/) - [X] [Gate.io](https://www.gate.io/ref/6266643) - [X] [OKX](https://okx.com/) +- [X] [Bybit](https://bybit.com/) Please make sure to read the [exchange specific notes](docs/exchanges.md), as well as the [trading with leverage](docs/leverage.md) documentation before diving in. @@ -164,6 +165,10 @@ first. If it hasn't been reported, please ensure you follow the template guide so that the team can assist you as quickly as possible. +For every [issue](https://github.com/freqtrade/freqtrade/issues/new/choose) created, kindly follow up and mark satisfaction or reminder to close issue when equilibrium ground is reached. + +--Maintain github's [community policy](https://docs.github.com/en/site-policy/github-terms/github-community-code-of-conduct)-- + ### [Feature Requests](https://github.com/freqtrade/freqtrade/labels/enhancement) Have you a great idea to improve the bot you want to share? Please, diff --git a/build_helpers/TA_Lib-0.4.25-cp311-cp311-win_amd64.whl b/build_helpers/TA_Lib-0.4.25-cp311-cp311-win_amd64.whl new file mode 100644 index 000000000..4d55d18c8 Binary files /dev/null and b/build_helpers/TA_Lib-0.4.25-cp311-cp311-win_amd64.whl differ diff --git a/build_helpers/install_windows.ps1 b/build_helpers/install_windows.ps1 index 461726a03..36606cc3a 100644 --- a/build_helpers/install_windows.ps1 +++ b/build_helpers/install_windows.ps1 @@ -14,5 +14,8 @@ if ($pyv -eq '3.9') { if ($pyv -eq '3.10') { pip install build_helpers\TA_Lib-0.4.25-cp310-cp310-win_amd64.whl } +if ($pyv -eq '3.11') { + pip install build_helpers\TA_Lib-0.4.25-cp311-cp311-win_amd64.whl +} pip install -r requirements-dev.txt pip install -e . diff --git a/build_helpers/pyarrow-10.0.0-cp39-cp39-linux_armv7l.whl b/build_helpers/pyarrow-11.0.0-cp39-cp39-linux_armv7l.whl similarity index 62% rename from build_helpers/pyarrow-10.0.0-cp39-cp39-linux_armv7l.whl rename to build_helpers/pyarrow-11.0.0-cp39-cp39-linux_armv7l.whl index a6c879cf5..a7ad80bdf 100644 Binary files a/build_helpers/pyarrow-10.0.0-cp39-cp39-linux_armv7l.whl and b/build_helpers/pyarrow-11.0.0-cp39-cp39-linux_armv7l.whl differ diff --git a/config_examples/config_freqai.example.json b/config_examples/config_freqai.example.json index 645c30227..65a93379e 100644 --- a/config_examples/config_freqai.example.json +++ b/config_examples/config_freqai.example.json @@ -48,7 +48,7 @@ ], "freqai": { "enabled": true, - "purge_old_models": true, + "purge_old_models": 2, "train_period_days": 15, "backtest_period_days": 7, "live_retrain_hours": 0, diff --git a/config_examples/config_full.example.json b/config_examples/config_full.example.json index b60957b58..64e5b76ea 100644 --- a/config_examples/config_full.example.json +++ b/config_examples/config_full.example.json @@ -60,6 +60,7 @@ "force_entry": "market", "stoploss": "market", "stoploss_on_exchange": false, + "stoploss_price_type": "last", "stoploss_on_exchange_interval": 60, "stoploss_on_exchange_limit_ratio": 0.99 }, diff --git a/docker/Dockerfile.armhf b/docker/Dockerfile.armhf index 7b663ae6c..4972e7109 100644 --- a/docker/Dockerfile.armhf +++ b/docker/Dockerfile.armhf @@ -1,4 +1,4 @@ -FROM python:3.9.12-slim-bullseye as base +FROM python:3.9.16-slim-bullseye as base # Setup env ENV LANG C.UTF-8 diff --git a/docs/advanced-setup.md b/docs/advanced-setup.md index 93a2025ed..54d489aa1 100644 --- a/docs/advanced-setup.md +++ b/docs/advanced-setup.md @@ -192,7 +192,7 @@ $RepeatedMsgReduction on ### Logging to journald -This needs the `systemd` python package installed as the dependency, which is not available on Windows. Hence, the whole journald logging functionality is not available for a bot running on Windows. +This needs the `cysystemd` python package installed as dependency (`pip install cysystemd`), which is not available on Windows. Hence, the whole journald logging functionality is not available for a bot running on Windows. To send Freqtrade log messages to `journald` system service use the `--logfile` command line option with the value in the following format: diff --git a/docs/configuration.md b/docs/configuration.md index 4b6695d17..8a1aeb40e 100644 --- a/docs/configuration.md +++ b/docs/configuration.md @@ -666,7 +666,7 @@ You should also make sure to read the [Exchanges](exchanges.md) section of the d ### Using proxy with Freqtrade To use a proxy with freqtrade, export your proxy settings using the variables `"HTTP_PROXY"` and `"HTTPS_PROXY"` set to the appropriate values. -This will have the proxy settings applied to everything (telegram, coingecko, ...) except exchange requests. +This will have the proxy settings applied to everything (telegram, coingecko, ...) **except** for exchange requests. ``` bash export HTTP_PROXY="http://addr:port" @@ -682,11 +682,12 @@ To use a proxy for exchange connections - you will have to define the proxies as { "exchange": { "ccxt_config": { - "aiohttp_proxy": "http://addr:port", - "proxies": { - "http": "http://addr:port", - "https": "http://addr:port" - }, + "aiohttp_proxy": "http://addr:port", + "proxies": { + "http": "http://addr:port", + "https": "http://addr:port" + }, + } } } ``` diff --git a/docs/developer.md b/docs/developer.md index ea2e36ce1..0546c20e9 100644 --- a/docs/developer.md +++ b/docs/developer.md @@ -363,7 +363,7 @@ from pathlib import Path exchange = ccxt.binance({ 'apiKey': '', 'secret': '' - 'options': {'defaultType': 'future'} + 'options': {'defaultType': 'swap'} }) _ = exchange.load_markets() diff --git a/docs/exchanges.md b/docs/exchanges.md index 48b14c470..997d012e1 100644 --- a/docs/exchanges.md +++ b/docs/exchanges.md @@ -243,8 +243,8 @@ OKX requires a passphrase for each api key, you will therefore need to add this OKX only provides 100 candles per api call. Therefore, the strategy will only have a pretty low amount of data available in backtesting mode. !!! Warning "Futures" - OKX Futures has the concept of "position mode" - which can be Net or long/short (hedge mode). - Freqtrade supports both modes (we recommend to use net mode) - but changing the mode mid-trading is not supported and will lead to exceptions and failures to place trades. + OKX Futures has the concept of "position mode" - which can be "Buy/Sell" or long/short (hedge mode). + Freqtrade supports both modes (we recommend to use Buy/Sell mode) - but changing the mode mid-trading is not supported and will lead to exceptions and failures to place trades. OKX also only provides MARK candles for the past ~3 months. Backtesting futures prior to that date will therefore lead to slight deviations, as funding-fees cannot be calculated correctly without this data. ## Gate.io @@ -255,6 +255,18 @@ OKX requires a passphrase for each api key, you will therefore need to add this Gate.io allows the use of `POINT` to pay for fees. As this is not a tradable currency (no regular market available), automatic fee calculations will fail (and default to a fee of 0). The configuration parameter `exchange.unknown_fee_rate` can be used to specify the exchange rate between Point and the stake currency. Obviously, changing the stake-currency will also require changes to this value. +## Bybit + +Futures trading on bybit is currently supported for USDT markets, and will use isolated futures mode. +Users with unified accounts (there's no way back) can create a Sub-account which will start as "non-unified", and can therefore use isolated futures. +On startup, freqtrade will set the position mode to "One-way Mode" for the whole (sub)account. This avoids making this call over and over again (slowing down bot operations), but means that changes to this setting may result in exceptions and errors. + +As bybit doesn't provide funding rate history, the dry-run calculation is used for live trades as well. + +!!! Tip "Stoploss on Exchange" + Bybit (futures only) supports `stoploss_on_exchange` and uses `stop-loss-limit` orders. It provides great advantages, so we recommend to benefit from it by enabling stoploss on exchange. + On futures, Bybit supports both `stop-limit` as well as `stop-market` orders. You can use either `"limit"` or `"market"` in the `order_types.stoploss` configuration setting to decide which type to use. + ## All exchanges Should you experience constant errors with Nonce (like `InvalidNonce`), it is best to regenerate the API keys. Resetting Nonce is difficult and it's usually easier to regenerate the API keys. diff --git a/docs/faq.md b/docs/faq.md index bcceaf898..b52a77c6b 100644 --- a/docs/faq.md +++ b/docs/faq.md @@ -2,7 +2,7 @@ ## Supported Markets -Freqtrade supports spot trading only. +Freqtrade supports spot trading, as well as (isolated) futures trading for some selected exchanges. Please refer to the [documentation start page](index.md#supported-futures-exchanges-experimental) for an uptodate list of supported exchanges. ### Can my bot open short positions? @@ -248,8 +248,26 @@ The Edge module is mostly a result of brainstorming of [@mishaker](https://githu You can find further info on expectancy, win rate, risk management and position size in the following sources: - https://www.tradeciety.com/ultimate-math-guide-for-traders/ -- http://www.vantharp.com/tharp-concepts/expectancy.asp - https://samuraitradingacademy.com/trading-expectancy/ - https://www.learningmarkets.com/determining-expectancy-in-your-trading/ -- http://www.lonestocktrader.com/make-money-trading-positive-expectancy/ +- https://www.lonestocktrader.com/make-money-trading-positive-expectancy/ - https://www.babypips.com/trading/trade-expectancy-matter + +## Official channels + +Freqtrade is using exclusively the following official channels: + +* [Freqtrade discord server](https://discord.gg/p7nuUNVfP7) +* [Freqtrade documentation (https://freqtrade.io)](https://freqtrade.io) +* [Freqtrade github organization](https://github.com/freqtrade) + +Nobody affiliated with the freqtrade project will ask you about your exchange keys or anything else exposing your funds to exploitation. +Should you be asked to expose your exchange keys or send funds to some random wallet, then please don't follow these instructions. + +Failing to follow these guidelines will not be responsibility of freqtrade. + +## "Freqtrade token" + +Freqtrade does not have a Crypto token offering. + +Token offerings you find on the internet referring Freqtrade, FreqAI or freqUI must be considered to be a scam, trying to exploit freqtrade's popularity for their own, nefarious gains. diff --git a/docs/freqai-configuration.md b/docs/freqai-configuration.md index 9d89800be..886dc2338 100644 --- a/docs/freqai-configuration.md +++ b/docs/freqai-configuration.md @@ -9,7 +9,7 @@ FreqAI is configured through the typical [Freqtrade config file](configuration.m ```json "freqai": { "enabled": true, - "purge_old_models": true, + "purge_old_models": 2, "train_period_days": 30, "backtest_period_days": 7, "identifier" : "unique-id", @@ -165,10 +165,10 @@ Below are the values you can expect to include/use inside a typical strategy dat ## Setting the `startup_candle_count` -The `startup_candle_count` in the FreqAI strategy needs to be set up in the same way as in the standard Freqtrade strategy (see details [here](strategy-customization.md#strategy-startup-period)). This value is used by Freqtrade to ensure that a sufficient amount of data is provided when calling the `dataprovider`, to avoid any NaNs at the beginning of the first training. You can easily set this value by identifying the longest period (in candle units) which is passed to the indicator creation functions (e.g., Ta-Lib functions). In the presented example, `startup_candle_count` is 20 since this is the maximum value in `indicators_periods_candles`. +The `startup_candle_count` in the FreqAI strategy needs to be set up in the same way as in the standard Freqtrade strategy (see details [here](strategy-customization.md#strategy-startup-period)). This value is used by Freqtrade to ensure that a sufficient amount of data is provided when calling the `dataprovider`, to avoid any NaNs at the beginning of the first training. You can easily set this value by identifying the longest period (in candle units) which is passed to the indicator creation functions (e.g., TA-Lib functions). In the presented example, `startup_candle_count` is 20 since this is the maximum value in `indicators_periods_candles`. !!! Note - There are instances where the Ta-Lib functions actually require more data than just the passed `period` or else the feature dataset gets populated with NaNs. Anecdotally, multiplying the `startup_candle_count` by 2 always leads to a fully NaN free training dataset. Hence, it is typically safest to multiply the expected `startup_candle_count` by 2. Look out for this log message to confirm that the data is clean: + There are instances where the TA-Lib functions actually require more data than just the passed `period` or else the feature dataset gets populated with NaNs. Anecdotally, multiplying the `startup_candle_count` by 2 always leads to a fully NaN free training dataset. Hence, it is typically safest to multiply the expected `startup_candle_count` by 2. Look out for this log message to confirm that the data is clean: ``` 2022-08-31 15:14:04 - freqtrade.freqai.data_kitchen - INFO - dropped 0 training points due to NaNs in populated dataset 4319. @@ -205,7 +205,7 @@ All of the aforementioned model libraries implement gradient boosted decision tr * LightGBM: https://lightgbm.readthedocs.io/en/v3.3.2/# * XGBoost: https://xgboost.readthedocs.io/en/stable/# -There are also numerous online articles describing and comparing the algorithms. Some relatively light-weight examples would be [CatBoost vs. LightGBM vs. XGBoost — Which is the best algorithm?](https://towardsdatascience.com/catboost-vs-lightgbm-vs-xgboost-c80f40662924#:~:text=In%20CatBoost%2C%20symmetric%20trees%2C%20or,the%20same%20depth%20can%20differ.) and [XGBoost, LightGBM or CatBoost — which boosting algorithm should I use?](https://medium.com/riskified-technology/xgboost-lightgbm-or-catboost-which-boosting-algorithm-should-i-use-e7fda7bb36bc). Keep in mind that the performance of each model is highly dependent on the application and so any reported metrics might not be true for your particular use of the model. +There are also numerous online articles describing and comparing the algorithms. Some relatively lightweight examples would be [CatBoost vs. LightGBM vs. XGBoost — Which is the best algorithm?](https://towardsdatascience.com/catboost-vs-lightgbm-vs-xgboost-c80f40662924#:~:text=In%20CatBoost%2C%20symmetric%20trees%2C%20or,the%20same%20depth%20can%20differ.) and [XGBoost, LightGBM or CatBoost — which boosting algorithm should I use?](https://medium.com/riskified-technology/xgboost-lightgbm-or-catboost-which-boosting-algorithm-should-i-use-e7fda7bb36bc). Keep in mind that the performance of each model is highly dependent on the application and so any reported metrics might not be true for your particular use of the model. Apart from the models already available in FreqAI, it is also possible to customize and create your own prediction models using the `IFreqaiModel` class. You are encouraged to inherit `fit()`, `train()`, and `predict()` to customize various aspects of the training procedures. You can place custom FreqAI models in `user_data/freqaimodels` - and freqtrade will pick them up from there based on the provided `--freqaimodel` name - which has to correspond to the class name of your custom model. Make sure to use unique names to avoid overriding built-in models. diff --git a/docs/freqai-feature-engineering.md b/docs/freqai-feature-engineering.md index 6c8c5bb46..6389bd9e5 100644 --- a/docs/freqai-feature-engineering.md +++ b/docs/freqai-feature-engineering.md @@ -8,7 +8,7 @@ Low level feature engineering is performed in the user strategy within a set of |---------------|-------------| | `feature_engineering__expand_all()` | This optional function will automatically expand the defined features on the config defined `indicator_periods_candles`, `include_timeframes`, `include_shifted_candles`, and `include_corr_pairs`. | `feature_engineering__expand_basic()` | This optional function will automatically expand the defined features on the config defined `include_timeframes`, `include_shifted_candles`, and `include_corr_pairs`. Note: this function does *not* expand across `include_periods_candles`. -| `feature_engineering_standard()` | This optional function will be called once with the dataframe of the base timeframe. This is the final function to be called, which means that the dataframe entering this function will contain all the features and columns from the base asset created by the other `feature_engineering_expand` functions. This function is a good place to do custom exotic feature extractions (e.g. tsfresh). This function is also a good place for any feature that should not be auto-expanded upon (e.g. day of the week). +| `feature_engineering_standard()` | This optional function will be called once with the dataframe of the base timeframe. This is the final function to be called, which means that the dataframe entering this function will contain all the features and columns from the base asset created by the other `feature_engineering_expand` functions. This function is a good place to do custom exotic feature extractions (e.g. tsfresh). This function is also a good place for any feature that should not be auto-expanded upon (e.g., day of the week). | `set_freqai_targets()` | Required function to set the targets for the model. All targets must be prepended with `&` to be recognized by the FreqAI internals. Meanwhile, high level feature engineering is handled within `"feature_parameters":{}` in the FreqAI config. Within this file, it is possible to decide large scale feature expansions on top of the `base_features` such as "including correlated pairs" or "including informative timeframes" or even "including recent candles." @@ -16,7 +16,7 @@ Meanwhile, high level feature engineering is handled within `"feature_parameters It is advisable to start from the template `feature_engineering_*` functions in the source provided example strategy (found in `templates/FreqaiExampleStrategy.py`) to ensure that the feature definitions are following the correct conventions. Here is an example of how to set the indicators and labels in the strategy: ```python - def feature_engineering_expand_all(self, dataframe, period, **kwargs): + def feature_engineering_expand_all(self, dataframe, period, metadata, **kwargs): """ *Only functional with FreqAI enabled strategies* This function will automatically expand the defined features on the config defined @@ -28,8 +28,13 @@ It is advisable to start from the template `feature_engineering_*` functions in All features must be prepended with `%` to be recognized by FreqAI internals. + Access metadata such as the current pair/timeframe/period with: + + `metadata["pair"]` `metadata["tf"]` `metadata["period"]` + :param df: strategy dataframe which will receive the features :param period: period of the indicator - usage example: + :param metadata: metadata of current pair dataframe["%-ema-period"] = ta.EMA(dataframe, timeperiod=period) """ @@ -62,7 +67,7 @@ It is advisable to start from the template `feature_engineering_*` functions in return dataframe - def feature_engineering_expand_basic(self, dataframe, **kwargs): + def feature_engineering_expand_basic(self, dataframe, metadata, **kwargs): """ *Only functional with FreqAI enabled strategies* This function will automatically expand the defined features on the config defined @@ -75,9 +80,14 @@ It is advisable to start from the template `feature_engineering_*` functions in Features defined here will *not* be automatically duplicated on user defined `indicator_periods_candles` + Access metadata such as the current pair/timeframe with: + + `metadata["pair"]` `metadata["tf"]` + All features must be prepended with `%` to be recognized by FreqAI internals. :param df: strategy dataframe which will receive the features + :param metadata: metadata of current pair dataframe["%-pct-change"] = dataframe["close"].pct_change() dataframe["%-ema-200"] = ta.EMA(dataframe, timeperiod=200) """ @@ -86,7 +96,7 @@ It is advisable to start from the template `feature_engineering_*` functions in dataframe["%-raw_price"] = dataframe["close"] return dataframe - def feature_engineering_standard(self, dataframe, **kwargs): + def feature_engineering_standard(self, dataframe, metadata, **kwargs): """ *Only functional with FreqAI enabled strategies* This optional function will be called once with the dataframe of the base timeframe. @@ -98,22 +108,32 @@ It is advisable to start from the template `feature_engineering_*` functions in This function is a good place for any feature that should not be auto-expanded upon (e.g. day of the week). + Access metadata such as the current pair with: + + `metadata["pair"]` + All features must be prepended with `%` to be recognized by FreqAI internals. :param df: strategy dataframe which will receive the features + :param metadata: metadata of current pair usage example: dataframe["%-day_of_week"] = (dataframe["date"].dt.dayofweek + 1) / 7 """ dataframe["%-day_of_week"] = (dataframe["date"].dt.dayofweek + 1) / 7 dataframe["%-hour_of_day"] = (dataframe["date"].dt.hour + 1) / 25 return dataframe - def set_freqai_targets(self, dataframe, **kwargs): + def set_freqai_targets(self, dataframe, metadata, **kwargs): """ *Only functional with FreqAI enabled strategies* Required function to set the targets for the model. All targets must be prepended with `&` to be recognized by the FreqAI internals. + Access metadata such as the current pair with: + + `metadata["pair"]` + :param df: strategy dataframe which will receive the targets + :param metadata: metadata of current pair usage example: dataframe["&-target"] = dataframe["close"].shift(-1) / dataframe["close"] """ dataframe["&-s_close"] = ( @@ -161,6 +181,19 @@ You can ask for each of the defined features to be included also for informative In total, the number of features the user of the presented example strat has created is: length of `include_timeframes` * no. features in `feature_engineering_expand_*()` * length of `include_corr_pairlist` * no. `include_shifted_candles` * length of `indicator_periods_candles` $= 3 * 3 * 3 * 2 * 2 = 108$. + + ### Gain finer control over `feature_engineering_*` functions with `metadata` + + All `feature_engineering_*` and `set_freqai_targets()` functions are passed a `metadata` dictionary which contains information about the `pair`, `tf` (timeframe), and `period` that FreqAI is automating for feature building. As such, a user can use `metadata` inside `feature_engineering_*` functions as criteria for blocking/reserving features for certain timeframes, periods, pairs etc. + + ```py +def feature_engineering_expand_all(self, dataframe, period, metadata, **kwargs): + if metadata["tf"] == "1h": + dataframe["%-roc-period"] = ta.ROC(dataframe, timeperiod=period) +``` + +This will block `ta.ROC()` from being added to any timeframes other than `"1h"`. + ### Returning additional info from training Important metrics can be returned to the strategy at the end of each model training by assigning them to `dk.data['extra_returns_per_train']['my_new_value'] = XYZ` inside the custom prediction model class. @@ -201,7 +234,7 @@ This will perform PCA on the features and reduce their dimensionality so that th ## Inlier metric -The `inlier_metric` is a metric aimed at quantifying how similar a the features of a data point are to the most recent historic data points. +The `inlier_metric` is a metric aimed at quantifying how similar the features of a data point are to the most recent historical data points. You define the lookback window by setting `inlier_metric_window` and FreqAI computes the distance between the present time point and each of the previous `inlier_metric_window` lookback points. A Weibull function is fit to each of the lookback distributions and its cumulative distribution function (CDF) is used to produce a quantile for each lookback point. The `inlier_metric` is then computed for each time point as the average of the corresponding lookback quantiles. The figure below explains the concept for an `inlier_metric_window` of 5. diff --git a/docs/freqai-parameter-table.md b/docs/freqai-parameter-table.md index 046fa8008..275062a33 100644 --- a/docs/freqai-parameter-table.md +++ b/docs/freqai-parameter-table.md @@ -15,10 +15,9 @@ Mandatory parameters are marked as **Required** and have to be set in one of the | `identifier` | **Required.**
A unique ID for the current model. If models are saved to disk, the `identifier` allows for reloading specific pre-trained models/data.
**Datatype:** String. | `live_retrain_hours` | Frequency of retraining during dry/live runs.
**Datatype:** Float > 0.
Default: `0` (models retrain as often as possible). | `expiration_hours` | Avoid making predictions if a model is more than `expiration_hours` old.
**Datatype:** Positive integer.
Default: `0` (models never expire). -| `purge_old_models` | Delete all unused models during live runs (not relevant to backtesting). If set to false (not default), dry/live runs will accumulate all unused models to disk. If
**Datatype:** Boolean.
Default: `True`. +| `purge_old_models` | Number of models to keep on disk (not relevant to backtesting). Default is 2, which means that dry/live runs will keep the latest 2 models on disk. Setting to 0 keeps all models. This parameter also accepts a boolean to maintain backwards compatibility.
**Datatype:** Integer.
Default: `2`. | `save_backtest_models` | Save models to disk when running backtesting. Backtesting operates most efficiently by saving the prediction data and reusing them directly for subsequent runs (when you wish to tune entry/exit parameters). Saving backtesting models to disk also allows to use the same model files for starting a dry/live instance with the same model `identifier`.
**Datatype:** Boolean.
Default: `False` (no models are saved). | `fit_live_predictions_candles` | Number of historical candles to use for computing target (label) statistics from prediction data, instead of from the training dataset (more information can be found [here](freqai-configuration.md#creating-a-dynamic-target-threshold)).
**Datatype:** Positive integer. -| `follow_mode` | Use a `follower` that will look for models associated with a specific `identifier` and load those for inferencing. A `follower` will **not** train new models.
**Datatype:** Boolean.
Default: `False`. | `continual_learning` | Use the final state of the most recently trained model as starting point for the new model, allowing for incremental learning (more information can be found [here](freqai-running.md#continual-learning)).
**Datatype:** Boolean.
Default: `False`. | `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. @@ -46,13 +45,15 @@ Mandatory parameters are marked as **Required** and have to be set in one of the | `noise_standard_deviation` | If set, FreqAI adds noise to the training features with the aim of preventing overfitting. FreqAI generates random deviates from a gaussian distribution with a standard deviation of `noise_standard_deviation` and adds them to all data points. `noise_standard_deviation` should be kept relative to the normalized space, i.e., between -1 and 1. In other words, since data in FreqAI is always normalized to be between -1 and 1, `noise_standard_deviation: 0.05` would result in 32% of the data being randomly increased/decreased by more than 2.5% (i.e., the percent of data falling within the first standard deviation).
**Datatype:** Integer.
Default: `0`. | `outlier_protection_percentage` | Enable to prevent outlier detection methods from discarding too much data. If more than `outlier_protection_percentage` % of points are detected as outliers by the SVM or DBSCAN, FreqAI will log a warning message and ignore outlier detection, i.e., the original dataset will be kept intact. If the outlier protection is triggered, no predictions will be made based on the training dataset.
**Datatype:** Float.
Default: `30`. | `reverse_train_test_order` | Split the feature dataset (see below) and use the latest data split for training and test on historical split of the data. This allows the model to be trained up to the most recent data point, while avoiding overfitting. However, you should be careful to understand the unorthodox nature of this parameter before employing it.
**Datatype:** Boolean.
Default: `False` (no reversal). +| `shuffle_after_split` | Split the data into train and test sets, and then shuffle both sets individually.
**Datatype:** Boolean.
Default: `False`. +| `buffer_train_data_candles` | Cut `buffer_train_data_candles` off the beginning and end of the training data *after* the indicators were populated. The main example use is when predicting maxima and minima, the argrelextrema function cannot know the maxima/minima at the edges of the timerange. To improve model accuracy, it is best to compute argrelextrema on the full timerange and then use this function to cut off the edges (buffer) by the kernel. In another case, if the targets are set to a shifted price movement, this buffer is unnecessary because the shifted candles at the end of the timerange will be NaN and FreqAI will automatically cut those off of the training dataset.
**Datatype:** Boolean.
Default: `False`. ### Data split parameters | Parameter | Description | |------------|-------------| | | **Data split parameters within the `freqai.data_split_parameters` sub dictionary** -| `data_split_parameters` | Include any additional parameters available from Scikit-learn `test_train_split()`, which are shown [here](https://scikit-learn.org/stable/modules/generated/sklearn.model_selection.train_test_split.html) (external website).
**Datatype:** Dictionary. +| `data_split_parameters` | Include any additional parameters available from scikit-learn `test_train_split()`, which are shown [here](https://scikit-learn.org/stable/modules/generated/sklearn.model_selection.train_test_split.html) (external website).
**Datatype:** Dictionary. | `test_size` | The fraction of data that should be used for testing instead of training.
**Datatype:** Positive float < 1. | `shuffle` | Shuffle the training data points during training. Typically, to not remove the chronological order of data in time-series forecasting, this is set to `False`.
**Datatype:** Boolean.
Defaut: `False`. @@ -89,6 +90,6 @@ Mandatory parameters are marked as **Required** and have to be set in one of the | Parameter | Description | |------------|-------------| | | **Extraneous parameters** -| `freqai.keras` | If the selected model makes use of Keras (typical for Tensorflow-based prediction models), this flag needs to be activated so that the model save/loading follows Keras standards.
**Datatype:** Boolean.
Default: `False`. +| `freqai.keras` | If the selected model makes use of Keras (typical for TensorFlow-based prediction models), this flag needs to be activated so that the model save/loading follows Keras standards.
**Datatype:** Boolean.
Default: `False`. | `freqai.conv_width` | The width of a convolutional neural network input tensor. This replaces the need for shifting candles (`include_shifted_candles`) by feeding in historical data points as the second dimension of the tensor. Technically, this parameter can also be used for regressors, but it only adds computational overhead and does not change the model training/prediction.
**Datatype:** Integer.
Default: `2`. | `freqai.reduce_df_footprint` | Recast all numeric columns to float32/int32, with the objective of reducing ram/disk usage and decreasing train/inference timing. This parameter is set in the main level of the Freqtrade configuration file (not inside FreqAI).
**Datatype:** Boolean.
Default: `False`. diff --git a/docs/freqai-reinforcement-learning.md b/docs/freqai-reinforcement-learning.md index 5c9733403..3810aec4e 100644 --- a/docs/freqai-reinforcement-learning.md +++ b/docs/freqai-reinforcement-learning.md @@ -24,7 +24,7 @@ The framework is built on stable_baselines3 (torch) and OpenAI gym for the base ### Important considerations -As explained above, the agent is "trained" in an artificial trading "environment". In our case, that environment may seem quite similar to a real Freqtrade backtesting environment, but it is *NOT*. In fact, the RL training environment is much more simplified. It does not incorporate any of the complicated strategy logic, such as callbacks like `custom_exit`, `custom_stoploss`, leverage controls, etc. The RL environment is instead a very "raw" representation of the true market, where the agent has free-will to learn the policy (read: stoploss, take profit, etc.) which is enforced by the `calculate_reward()`. Thus, it is important to consider that the agent training environment is not identical to the real world. +As explained above, the agent is "trained" in an artificial trading "environment". In our case, that environment may seem quite similar to a real Freqtrade backtesting environment, but it is *NOT*. In fact, the RL training environment is much more simplified. It does not incorporate any of the complicated strategy logic, such as callbacks like `custom_exit`, `custom_stoploss`, leverage controls, etc. The RL environment is instead a very "raw" representation of the true market, where the agent has free will to learn the policy (read: stoploss, take profit, etc.) which is enforced by the `calculate_reward()`. Thus, it is important to consider that the agent training environment is not identical to the real world. ## Running Reinforcement Learning @@ -175,10 +175,21 @@ As you begin to modify the strategy and the prediction model, you will quickly r pnl = self.get_unrealized_profit() factor = 100 + + # you can use feature values from dataframe + # Assumes the shifted RSI indicator has been generated in the strategy. + rsi_now = self.raw_features[f"%-rsi-period-10_shift-1_{self.pair}_" + f"{self.config['timeframe']}"].iloc[self._current_tick] + # reward agent for entering trades - if action in (Actions.Long_enter.value, Actions.Short_enter.value) \ - and self._position == Positions.Neutral: - return 25 + if (action in (Actions.Long_enter.value, Actions.Short_enter.value) + and self._position == Positions.Neutral): + if rsi_now < 40: + factor = 40 / rsi_now + else: + factor = 1 + return 25 * factor + # discourage agent from not entering trades if action == Actions.Neutral.value and self._position == Positions.Neutral: return -1 diff --git a/docs/freqai-running.md b/docs/freqai-running.md index a75e30e83..1eaee1bf2 100644 --- a/docs/freqai-running.md +++ b/docs/freqai-running.md @@ -120,7 +120,7 @@ In the presented example config, the user will only allow predictions on models Model training parameters are unique to the selected machine learning library. FreqAI allows you to set any parameter for any library using the `model_training_parameters` dictionary in the config. The example config (found in `config_examples/config_freqai.example.json`) shows some of the example parameters associated with `Catboost` and `LightGBM`, but you can add any parameters available in those libraries or any other machine learning library you choose to implement. -Data split parameters are defined in `data_split_parameters` which can be any parameters associated with Scikit-learn's `train_test_split()` function. `train_test_split()` has a parameters called `shuffle` which allows to shuffle the data or keep it unshuffled. This is particularly useful to avoid biasing training with temporally auto-correlated data. More details about these parameters can be found the [Scikit-learn website](https://scikit-learn.org/stable/modules/generated/sklearn.model_selection.train_test_split.html) (external website). +Data split parameters are defined in `data_split_parameters` which can be any parameters associated with scikit-learn's `train_test_split()` function. `train_test_split()` has a parameters called `shuffle` which allows to shuffle the data or keep it unshuffled. This is particularly useful to avoid biasing training with temporally auto-correlated data. More details about these parameters can be found the [scikit-learn website](https://scikit-learn.org/stable/modules/generated/sklearn.model_selection.train_test_split.html) (external website). The FreqAI specific parameter `label_period_candles` defines the offset (number of candles into the future) used for the `labels`. In the presented [example config](freqai-configuration.md#setting-up-the-configuration-file), the user is asking for `labels` that are 24 candles in the future. @@ -165,20 +165,3 @@ tensorboard --logdir user_data/models/unique-id where `unique-id` is the `identifier` set in the `freqai` configuration file. This command must be run in a separate shell if you wish to view the output in your browser at 127.0.0.1:6060 (6060 is the default port used by Tensorboard). ![tensorboard](assets/tensorboard.jpg) - -## Setting up a follower - -You can indicate to the bot that it should not train models, but instead should look for models trained by a leader with a specific `identifier` by defining: - -```json - "freqai": { - "enabled": true, - "follow_mode": true, - "identifier": "example", - "feature_parameters": { - // leader bots feature_parameters inserted here - }, - } -``` - -In this example, the user has a leader bot with the `"identifier": "example"`. The leader bot is already running or is launched simultaneously with the follower. The follower will load models created by the leader and inference them to obtain predictions instead of training its own models. The user will also need to duplicate the `feature_parameters` parameters from from the leaders freqai configuration file into the freqai section of the followers config. diff --git a/docs/freqai.md b/docs/freqai.md index d13d43f66..5c6b5b2ce 100644 --- a/docs/freqai.md +++ b/docs/freqai.md @@ -4,7 +4,10 @@ ## Introduction -FreqAI is a software designed to automate a variety of tasks associated with training a predictive machine learning model to generate market forecasts given a set of input signals. In general, the FreqAI aims to be a sand-box for easily deploying robust machine-learning libraries on real-time data ([details])(#freqai-position-in-open-source-machine-learning-landscape). +FreqAI is a software designed to automate a variety of tasks associated with training a predictive machine learning model to generate market forecasts given a set of input signals. In general, FreqAI aims to be a sandbox for easily deploying robust machine learning libraries on real-time data ([details](#freqai-position-in-open-source-machine-learning-landscape)). + +!!! Note + FreqAI is, and always will be, a not-for-profit, open-source project. FreqAI does *not* have a crypto token, FreqAI does *not* sell signals, and FreqAI does not have a domain besides the present [freqtrade documentation](https://www.freqtrade.io/en/latest/freqai/). Features include: @@ -19,7 +22,7 @@ Features include: * **Automatic data download** - Compute timeranges for data downloads and update historic data (in live deployments) * **Cleaning of incoming data** - Handle NaNs safely before training and model inferencing * **Dimensionality reduction** - Reduce the size of the training data via [Principal Component Analysis](freqai-feature-engineering.md#data-dimensionality-reduction-with-principal-component-analysis) -* **Deploying bot fleets** - Set one bot to train models while a fleet of [follower bots](freqai-running.md#setting-up-a-follower) inference the models and handle trades +* **Deploying bot fleets** - Set one bot to train models while a fleet of [consumers](producer-consumer.md) use signals. ## Quick start @@ -70,11 +73,11 @@ pip install -r requirements-freqai.txt ### Usage with docker -If you are using docker, a dedicated tag with FreqAI dependencies is available as `:freqai`. As such - you can replace the image line in your docker-compose file with `image: freqtradeorg/freqtrade:develop_freqai`. This image contains the regular FreqAI dependencies. Similar to native installs, Catboost will not be available on ARM based devices. +If you are using docker, a dedicated tag with FreqAI dependencies is available as `:freqai`. As such - you can replace the image line in your docker compose file with `image: freqtradeorg/freqtrade:develop_freqai`. This image contains the regular FreqAI dependencies. Similar to native installs, Catboost will not be available on ARM based devices. ### FreqAI position in open-source machine learning landscape -Forecasting chaotic time-series based systems, such as equity/cryptocurrency markets, requires a broad set of tools geared toward testing a wide range of hypotheses. Fortunately, a recent maturation of robust machine learning libraries (e.g. `scikit-learn`) has opened up a wide range of research possibilities. Scientists from a diverse range of fields can now easily prototype their studies on an abundance of established machine learning algorithms. Similarly, these user-friendly libraries enable "citzen scientists" to use their basic Python skills for data-exploration. However, leveraging these machine learning libraries on historical and live chaotic data sources can be logistically difficult and expensive. Additionally, robust data-collection, storage, and handling presents a disparate challenge. [`FreqAI`](#freqai) aims to provide a generalized and extensible open-sourced framework geared toward live deployments of adaptive modeling for market forecasting. The `FreqAI` framework is effectively a sandbox for the rich world of open-source machine learning libraries. Inside the `FreqAI` sandbox, users find they can combine a wide variety of third-party libraries to test creative hypotheses on a free live 24/7 chaotic data source - cryptocurrency exchange data. +Forecasting chaotic time-series based systems, such as equity/cryptocurrency markets, requires a broad set of tools geared toward testing a wide range of hypotheses. Fortunately, a recent maturation of robust machine learning libraries (e.g. `scikit-learn`) has opened up a wide range of research possibilities. Scientists from a diverse range of fields can now easily prototype their studies on an abundance of established machine learning algorithms. Similarly, these user-friendly libraries enable "citzen scientists" to use their basic Python skills for data exploration. However, leveraging these machine learning libraries on historical and live chaotic data sources can be logistically difficult and expensive. Additionally, robust data collection, storage, and handling presents a disparate challenge. [`FreqAI`](#freqai) aims to provide a generalized and extensible open-sourced framework geared toward live deployments of adaptive modeling for market forecasting. The `FreqAI` framework is effectively a sandbox for the rich world of open-source machine learning libraries. Inside the `FreqAI` sandbox, users find they can combine a wide variety of third-party libraries to test creative hypotheses on a free live 24/7 chaotic data source - cryptocurrency exchange data. ### Citing FreqAI diff --git a/docs/index.md b/docs/index.md index 40b9e98ad..c24d1f36b 100644 --- a/docs/index.md +++ b/docs/index.md @@ -52,6 +52,7 @@ Please read the [exchange specific notes](exchanges.md) to learn about eventual, - [X] [Binance](https://www.binance.com/) - [X] [Gate.io](https://www.gate.io/ref/6266643) - [X] [OKX](https://okx.com/) +- [X] [Bybit](https://bybit.com/) Please make sure to read the [exchange specific notes](exchanges.md), as well as the [trading with leverage](leverage.md) documentation before diving in. diff --git a/docs/installation.md b/docs/installation.md index 9dd14274a..1c0aed7ba 100644 --- a/docs/installation.md +++ b/docs/installation.md @@ -30,6 +30,12 @@ The easiest way to install and run Freqtrade is to clone the bot Github reposito !!! Warning "Up-to-date clock" The clock on the system running the bot must be accurate, synchronized to a NTP server frequently enough to avoid problems with communication to the exchanges. +!!! Error "Running setup.py install for gym did not run successfully." + If you get an error related with gym we suggest you to downgrade setuptools it to version 65.5.0 you can do it with the following command: + ```bash + pip install setuptools==65.5.0 + ``` + ------ ## Requirements diff --git a/docs/requirements-docs.txt b/docs/requirements-docs.txt index 774f40114..f5e671e88 100644 --- a/docs/requirements-docs.txt +++ b/docs/requirements-docs.txt @@ -1,6 +1,6 @@ markdown==3.3.7 mkdocs==1.4.2 -mkdocs-material==9.0.5 +mkdocs-material==9.0.13 mdx_truly_sane_lists==1.3 -pymdown-extensions==9.9.1 +pymdown-extensions==9.9.2 jinja2==3.1.2 diff --git a/docs/rest-api.md b/docs/rest-api.md index 62ad586dd..5f604ef43 100644 --- a/docs/rest-api.md +++ b/docs/rest-api.md @@ -163,7 +163,7 @@ python3 scripts/rest_client.py --config rest_config.json [optional par | `strategy ` | Get specific Strategy content. **Alpha** | `available_pairs` | List available backtest data. **Alpha** | `version` | Show version. -| `sysinfo` | Show informations about the system load. +| `sysinfo` | Show information about the system load. | `health` | Show bot health (last bot loop). !!! Warning "Alpha status" @@ -192,6 +192,11 @@ blacklist :param add: List of coins to add (example: "BNB/BTC") +cancel_open_order + Cancel open order for trade. + + :param trade_id: Cancels open orders for this trade. + count Return the amount of open trades. @@ -274,7 +279,6 @@ reload_config Reload configuration. show_config - Returns part of the configuration, relevant for trading operations. start @@ -320,6 +324,7 @@ version whitelist Show the current whitelist. + ``` ### Message WebSocket diff --git a/docs/stoploss.md b/docs/stoploss.md index 20e53d8f5..7af717955 100644 --- a/docs/stoploss.md +++ b/docs/stoploss.md @@ -24,7 +24,7 @@ These modes can be configured with these values: ``` !!! Note - Stoploss on exchange is only supported for Binance (stop-loss-limit), Huobi (stop-limit), Kraken (stop-loss-market, stop-loss-limit), Gateio (stop-limit), and Kucoin (stop-limit and stop-market) as of now. + Stoploss on exchange is only supported for Binance (stop-loss-limit), Huobi (stop-limit), Kraken (stop-loss-market, stop-loss-limit), Gate (stop-limit), and Kucoin (stop-limit and stop-market) as of now. Do not set too low/tight stoploss value if using stop loss on exchange! If set to low/tight then you have greater risk of missing fill on the order and stoploss will not work. @@ -52,6 +52,18 @@ The bot cannot do these every 5 seconds (at each iteration), otherwise it would So this parameter will tell the bot how often it should update the stoploss order. The default value is 60 (1 minute). This same logic will reapply a stoploss order on the exchange should you cancel it accidentally. +### stoploss_price_type + +!!! Warning "Only applies to futures" + `stoploss_price_type` only applies to futures markets (on exchanges where it's available). + Freqtrade will perform a validation of this setting on startup, failing to start if an invalid setting for your exchange has been selected. + Supported price types are gonna differs between each exchanges. Please check with your exchange on which price types it supports. + +Stoploss on exchange on futures markets can trigger on different price types. +The naming for these prices in exchange terminology often varies, but is usually something around "last" (or "contract price" ), "mark" and "index". + +Acceptable values for this setting are `"last"`, `"mark"` and `"index"` - which freqtrade will transfer automatically to the corresponding API type, and place the [stoploss on exchange](#stoploss_on_exchange-and-stoploss_on_exchange_limit_ratio) order correspondingly. + ### force_exit `force_exit` is an optional value, which defaults to the same value as `exit` and is used when sending a `/forceexit` command from Telegram or from the Rest API. diff --git a/docs/strategy_analysis_example.md b/docs/strategy_analysis_example.md index e3d2870e2..06dd33bc2 100644 --- a/docs/strategy_analysis_example.md +++ b/docs/strategy_analysis_example.md @@ -80,6 +80,7 @@ from freqtrade.resolvers import StrategyResolver from freqtrade.data.dataprovider import DataProvider strategy = StrategyResolver.load_strategy(config) strategy.dp = DataProvider(config, None, None) +strategy.ft_bot_start() # Generate buy/sell signals using strategy df = strategy.analyze_ticker(candles, {'pair': pair}) diff --git a/docs/telegram-usage.md b/docs/telegram-usage.md index db4a309d0..4626944c5 100644 --- a/docs/telegram-usage.md +++ b/docs/telegram-usage.md @@ -162,26 +162,33 @@ official commands. You can ask at any moment for help with `/help`. | Command | Description | |----------|-------------| +| **System commands** | `/start` | Starts the trader | `/stop` | Stops the trader | `/stopbuy | /stopentry` | Stops the trader from opening new trades. Gracefully closes open trades according to their rules. | `/reload_config` | Reloads the configuration file | `/show_config` | Shows part of the current configuration with relevant settings to operation | `/logs [limit]` | Show last log messages. +| `/help` | Show help message +| `/version` | Show version +| **Status** | | `/status` | Lists all open trades | `/status ` | Lists one or more specific trade. Separate multiple with a blank space. | `/status table` | List all open trades in a table format. Pending buy orders are marked with an asterisk (*) Pending sell orders are marked with a double asterisk (**) | `/trades [limit]` | List all recently closed trades in a table format. -| `/delete ` | Delete a specific trade from the Database. Tries to close open orders. Requires manual handling of this trade on the exchange. | `/count` | Displays number of trades used and available | `/locks` | Show currently locked pairs. | `/unlock ` | Remove the lock for this pair (or for this lock id). -| `/profit []` | Display a summary of your profit/loss from close trades and some stats about your performance, over the last n days (all trades by default) +| **Modify Trade states** | | `/forceexit | /fx ` | Instantly exits the given trade (Ignoring `minimum_roi`). | `/forceexit all | /fx all` | Instantly exits all open trades (Ignoring `minimum_roi`). | `/fx` | alias for `/forceexit` | `/forcelong [rate]` | Instantly buys the given pair. Rate is optional and only applies to limit orders. (`force_entry_enable` must be set to True) | `/forceshort [rate]` | Instantly shorts the given pair. Rate is optional and only applies to limit orders. This will only work on non-spot markets. (`force_entry_enable` must be set to True) +| `/delete ` | Delete a specific trade from the Database. Tries to close open orders. Requires manual handling of this trade on the exchange. +| `/cancel_open_order | /coo ` | Cancel an open order for a trade. +| **Metrics** | +| `/profit []` | Display a summary of your profit/loss from close trades and some stats about your performance, over the last n days (all trades by default) | `/performance` | Show performance of each finished trade grouped by pair | `/balance` | Show account balance per currency | `/daily ` | Shows profit or loss per day, over the last n days (n defaults to 7) @@ -193,8 +200,7 @@ official commands. You can ask at any moment for help with `/help`. | `/whitelist [sorted] [baseonly]` | Show the current whitelist. Optionally display in alphabetical order and/or with just the base currency of each pairing. | `/blacklist [pair]` | Show the current blacklist, or adds a pair to the blacklist. | `/edge` | Show validated pairs by Edge if it is enabled. -| `/help` | Show help message -| `/version` | Show version + ## Telegram commands in action diff --git a/environment.yml b/environment.yml index 5298b2baa..afe7a7ed7 100644 --- a/environment.yml +++ b/environment.yml @@ -12,7 +12,7 @@ dependencies: - py-find-1st - aiohttp - SQLAlchemy - - python-telegram-bot + - python-telegram-bot<20.0.0 - arrow - cachetools - requests @@ -54,7 +54,7 @@ dependencies: # 3/4 req hyperopt - scipy - - scikit-learn + - scikit-learn<1.2.0 - filelock - scikit-optimize - progressbar2 diff --git a/freqtrade/__init__.py b/freqtrade/__init__.py index f531bb605..f25bb2e52 100644 --- a/freqtrade/__init__.py +++ b/freqtrade/__init__.py @@ -1,5 +1,5 @@ """ Freqtrade bot """ -__version__ = '2023.1' +__version__ = '2023.2' if 'dev' in __version__: from pathlib import Path diff --git a/freqtrade/__main__.py b/freqtrade/__main__.py old mode 100644 new mode 100755 diff --git a/freqtrade/commands/analyze_commands.py b/freqtrade/commands/analyze_commands.py old mode 100755 new mode 100644 diff --git a/freqtrade/commands/build_config_commands.py b/freqtrade/commands/build_config_commands.py index f95a08ba5..63bb5c211 100644 --- a/freqtrade/commands/build_config_commands.py +++ b/freqtrade/commands/build_config_commands.py @@ -108,7 +108,7 @@ def ask_user_config() -> Dict[str, Any]: "binance", "binanceus", "bittrex", - "gateio", + "gate", "huobi", "kraken", "kucoin", @@ -123,7 +123,7 @@ def ask_user_config() -> Dict[str, Any]: "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', 'gateio', 'okx'], + "when": lambda x: x["exchange_name"] in ['binance', 'gate', 'okx'], }, { "type": "autocomplete", diff --git a/freqtrade/commands/hyperopt_commands.py b/freqtrade/commands/hyperopt_commands.py old mode 100755 new mode 100644 diff --git a/freqtrade/commands/trade_commands.py b/freqtrade/commands/trade_commands.py index 535844844..0707cc803 100644 --- a/freqtrade/commands/trade_commands.py +++ b/freqtrade/commands/trade_commands.py @@ -1,4 +1,5 @@ import logging +import signal from typing import Any, Dict @@ -12,15 +13,20 @@ def start_trading(args: Dict[str, Any]) -> int: # Import here to avoid loading worker module when it's not used from freqtrade.worker import Worker + def term_handler(signum, frame): + # Raise KeyboardInterrupt - so we can handle it in the same way as Ctrl-C + raise KeyboardInterrupt() + # Create and run worker worker = None try: + signal.signal(signal.SIGTERM, term_handler) worker = Worker(args) worker.run() except Exception as e: logger.error(str(e)) logger.exception("Fatal exception!") - except KeyboardInterrupt: + except (KeyboardInterrupt): logger.info('SIGINT received, aborting ...') finally: if worker: diff --git a/freqtrade/configuration/environment_vars.py b/freqtrade/configuration/environment_vars.py index 473758fe1..d59d4bd23 100644 --- a/freqtrade/configuration/environment_vars.py +++ b/freqtrade/configuration/environment_vars.py @@ -32,7 +32,7 @@ def flat_vars_to_nested_dict(env_dict: Dict[str, Any], prefix: str) -> Dict[str, :param prefix: Prefix to consider (usually FREQTRADE__) :return: Nested dict based on available and relevant variables. """ - no_convert = ['CHAT_ID'] + no_convert = ['CHAT_ID', 'PASSWORD'] relevant_vars: Dict[str, Any] = {} for env_var, val in sorted(env_dict.items()): diff --git a/freqtrade/constants.py b/freqtrade/constants.py index b41a3ad9c..1727da92e 100644 --- a/freqtrade/constants.py +++ b/freqtrade/constants.py @@ -5,7 +5,7 @@ bot constants """ from typing import Any, Dict, List, Literal, Tuple -from freqtrade.enums import CandleType, RPCMessageType +from freqtrade.enums import CandleType, PriceType, RPCMessageType DEFAULT_CONFIG = 'config.json' @@ -25,6 +25,7 @@ PRICING_SIDES = ['ask', 'bid', 'same', 'other'] ORDERTYPE_POSSIBILITIES = ['limit', 'market'] _ORDERTIF_POSSIBILITIES = ['GTC', 'FOK', 'IOC', 'PO'] ORDERTIF_POSSIBILITIES = _ORDERTIF_POSSIBILITIES + [t.lower() for t in _ORDERTIF_POSSIBILITIES] +STOPLOSS_PRICE_TYPES = [p for p in PriceType] HYPEROPT_LOSS_BUILTIN = ['ShortTradeDurHyperOptLoss', 'OnlyProfitHyperOptLoss', 'SharpeHyperOptLoss', 'SharpeHyperOptLossDaily', 'SortinoHyperOptLoss', 'SortinoHyperOptLossDaily', @@ -229,6 +230,7 @@ CONF_SCHEMA = { 'default': 'market'}, 'stoploss': {'type': 'string', 'enum': ORDERTYPE_POSSIBILITIES}, 'stoploss_on_exchange': {'type': 'boolean'}, + 'stoploss_price_type': {'type': 'string', 'enum': STOPLOSS_PRICE_TYPES}, 'stoploss_on_exchange_interval': {'type': 'number'}, 'stoploss_on_exchange_limit_ratio': {'type': 'number', 'minimum': 0.0, 'maximum': 1.0} @@ -544,7 +546,7 @@ CONF_SCHEMA = { "enabled": {"type": "boolean", "default": False}, "keras": {"type": "boolean", "default": False}, "write_metrics_to_disk": {"type": "boolean", "default": False}, - "purge_old_models": {"type": "boolean", "default": True}, + "purge_old_models": {"type": ["boolean", "number"], "default": 2}, "conv_width": {"type": "integer", "default": 1}, "train_period_days": {"type": "integer", "default": 0}, "backtest_period_days": {"type": "number", "default": 7}, @@ -566,7 +568,9 @@ CONF_SCHEMA = { "shuffle": {"type": "boolean", "default": False}, "nu": {"type": "number", "default": 0.1} }, - } + }, + "shuffle_after_split": {"type": "boolean", "default": False}, + "buffer_train_data_candles": {"type": "integer", "default": 0} }, "required": ["include_timeframes", "include_corr_pairlist", ] }, @@ -679,6 +683,7 @@ EntryExit = Literal['entry', 'exit'] BuySell = Literal['buy', 'sell'] MakerTaker = Literal['maker', 'taker'] BidAsk = Literal['bid', 'ask'] +OBLiteral = Literal['asks', 'bids'] Config = Dict[str, Any] IntOrInf = float diff --git a/freqtrade/data/dataprovider.py b/freqtrade/data/dataprovider.py index 3ebfd809c..3991432a4 100644 --- a/freqtrade/data/dataprovider.py +++ b/freqtrade/data/dataprovider.py @@ -9,7 +9,7 @@ from collections import deque from datetime import datetime, timezone from typing import Any, Dict, List, Optional, Tuple -from pandas import DataFrame, to_timedelta +from pandas import DataFrame, Timedelta, Timestamp, to_timedelta from freqtrade.configuration import TimeRange from freqtrade.constants import (FULL_DATAFRAME_THRESHOLD, Config, ListPairsWithTimeframes, @@ -18,6 +18,7 @@ from freqtrade.data.history import load_pair_history from freqtrade.enums import CandleType, RPCMessageType, RunMode from freqtrade.exceptions import ExchangeError, OperationalException from freqtrade.exchange import Exchange, timeframe_to_seconds +from freqtrade.exchange.types import OrderBook from freqtrade.misc import append_candles_to_dataframe from freqtrade.rpc import RPCManager from freqtrade.util import PeriodicCache @@ -206,9 +207,11 @@ class DataProvider: existing_df, _ = self.__producer_pairs_df[producer_name][pair_key] # CHECK FOR MISSING CANDLES - timeframe_delta = to_timedelta(timeframe) # Convert the timeframe to a timedelta for pandas - local_last = existing_df.iloc[-1]['date'] # We want the last date from our copy - incoming_first = dataframe.iloc[0]['date'] # We want the first date from the incoming + # Convert the timeframe to a timedelta for pandas + timeframe_delta: Timedelta = to_timedelta(timeframe) + local_last: Timestamp = existing_df.iloc[-1]['date'] # We want the last date from our copy + # We want the first date from the incoming + incoming_first: Timestamp = dataframe.iloc[0]['date'] # Remove existing candles that are newer than the incoming first candle existing_df1 = existing_df[existing_df['date'] < incoming_first] @@ -221,7 +224,7 @@ class DataProvider: # we missed some candles between our data and the incoming # so return False and candle_difference. if candle_difference > 1: - return (False, candle_difference) + return (False, int(candle_difference)) if existing_df1.empty: appended_df = dataframe else: @@ -421,10 +424,8 @@ class DataProvider: """ if self._exchange is None: raise OperationalException(NO_EXCHANGE_EXCEPTION) - if helping_pairs: - self._exchange.refresh_latest_ohlcv(pairlist + helping_pairs) - else: - self._exchange.refresh_latest_ohlcv(pairlist) + final_pairs = (pairlist + helping_pairs) if helping_pairs else pairlist + self._exchange.refresh_latest_ohlcv(final_pairs) @property def available_pairs(self) -> ListPairsWithTimeframes: @@ -487,7 +488,7 @@ class DataProvider: except ExchangeError: return {} - def orderbook(self, pair: str, maximum: int) -> Dict[str, List]: + def orderbook(self, pair: str, maximum: int) -> OrderBook: """ Fetch latest l2 orderbook data Warning: Does a network request - so use with common sense. diff --git a/freqtrade/data/entryexitanalysis.py b/freqtrade/data/entryexitanalysis.py old mode 100755 new mode 100644 diff --git a/freqtrade/data/history/idatahandler.py b/freqtrade/data/history/idatahandler.py index 7626198dc..6637663ff 100644 --- a/freqtrade/data/history/idatahandler.py +++ b/freqtrade/data/history/idatahandler.py @@ -308,7 +308,7 @@ class IDataHandler(ABC): timerange=timerange_startup, candle_type=candle_type ) - if self._check_empty_df(pairdf, pair, timeframe, candle_type, warn_no_data, True): + if self._check_empty_df(pairdf, pair, timeframe, candle_type, warn_no_data): return pairdf else: enddate = pairdf.iloc[-1]['date'] @@ -316,7 +316,7 @@ class IDataHandler(ABC): if timerange_startup: self._validate_pairdata(pair, pairdf, timeframe, candle_type, timerange_startup) pairdf = trim_dataframe(pairdf, timerange_startup) - if self._check_empty_df(pairdf, pair, timeframe, candle_type, warn_no_data): + if self._check_empty_df(pairdf, pair, timeframe, candle_type, warn_no_data, True): return pairdf # incomplete candles should only be dropped if we didn't trim the end beforehand. diff --git a/freqtrade/edge/edge_positioning.py b/freqtrade/edge/edge_positioning.py index 4656b7c93..73820ecbe 100644 --- a/freqtrade/edge/edge_positioning.py +++ b/freqtrade/edge/edge_positioning.py @@ -195,7 +195,7 @@ class Edge: def stake_amount(self, pair: str, free_capital: float, total_capital: float, capital_in_trade: float) -> float: - stoploss = self.stoploss(pair) + stoploss = self.get_stoploss(pair) available_capital = (total_capital + capital_in_trade) * self._capital_ratio allowed_capital_at_risk = available_capital * self._allowed_risk max_position_size = abs(allowed_capital_at_risk / stoploss) @@ -214,7 +214,7 @@ class Edge: ) return round(position_size, 15) - def stoploss(self, pair: str) -> float: + def get_stoploss(self, pair: str) -> float: if pair in self._cached_pairs: return self._cached_pairs[pair].stoploss else: diff --git a/freqtrade/enums/__init__.py b/freqtrade/enums/__init__.py index eb70a2894..8ef53e12d 100644 --- a/freqtrade/enums/__init__.py +++ b/freqtrade/enums/__init__.py @@ -6,6 +6,7 @@ from freqtrade.enums.exittype import ExitType from freqtrade.enums.hyperoptstate import HyperoptState from freqtrade.enums.marginmode import MarginMode from freqtrade.enums.ordertypevalue import OrderTypeValues +from freqtrade.enums.pricetype import PriceType from freqtrade.enums.rpcmessagetype import NO_ECHO_MESSAGES, RPCMessageType, RPCRequestType from freqtrade.enums.runmode import NON_UTIL_MODES, OPTIMIZE_MODES, TRADING_MODES, RunMode from freqtrade.enums.signaltype import SignalDirection, SignalTagType, SignalType diff --git a/freqtrade/enums/pricetype.py b/freqtrade/enums/pricetype.py new file mode 100644 index 000000000..bf0922b9f --- /dev/null +++ b/freqtrade/enums/pricetype.py @@ -0,0 +1,8 @@ +from enum import Enum + + +class PriceType(str, Enum): + """Enum to distinguish possible trigger prices for stoplosses""" + LAST = "last" + MARK = "mark" + INDEX = "index" diff --git a/freqtrade/exchange/__init__.py b/freqtrade/exchange/__init__.py index 973ed499b..b815fb3ee 100644 --- a/freqtrade/exchange/__init__.py +++ b/freqtrade/exchange/__init__.py @@ -17,7 +17,7 @@ from freqtrade.exchange.exchange_utils import (amount_to_contract_precision, amo timeframe_to_next_date, timeframe_to_prev_date, timeframe_to_seconds, validate_exchange, validate_exchanges) -from freqtrade.exchange.gateio import Gateio +from freqtrade.exchange.gate import Gate from freqtrade.exchange.hitbtc import Hitbtc from freqtrade.exchange.huobi import Huobi from freqtrade.exchange.kraken import Kraken diff --git a/freqtrade/exchange/binance.py b/freqtrade/exchange/binance.py index d85b2fb28..740d6e8a0 100644 --- a/freqtrade/exchange/binance.py +++ b/freqtrade/exchange/binance.py @@ -7,7 +7,7 @@ from typing import Dict, List, Optional, Tuple import arrow import ccxt -from freqtrade.enums import CandleType, MarginMode, TradingMode +from freqtrade.enums import CandleType, MarginMode, PriceType, TradingMode from freqtrade.exceptions import DDosProtection, OperationalException, TemporaryError from freqtrade.exchange import Exchange from freqtrade.exchange.common import retrier @@ -28,11 +28,16 @@ class Binance(Exchange): "trades_pagination": "id", "trades_pagination_arg": "fromId", "l2_limit_range": [5, 10, 20, 50, 100, 500, 1000], - "ccxt_futures_name": "swap" } _ft_has_futures: Dict = { "stoploss_order_types": {"limit": "stop", "market": "stop_market"}, "tickers_have_price": False, + "floor_leverage": True, + "stop_price_type_field": "workingType", + "stop_price_type_value_mapping": { + PriceType.LAST: "CONTRACT_PRICE", + PriceType.MARK: "MARK_PRICE", + }, } _supported_trading_mode_margin_pairs: List[Tuple[TradingMode, MarginMode]] = [ @@ -78,33 +83,9 @@ class Binance(Exchange): raise DDosProtection(e) from e except (ccxt.NetworkError, ccxt.ExchangeError) as e: raise TemporaryError( - f'Could not set leverage due to {e.__class__.__name__}. Message: {e}') from e - except ccxt.BaseError as e: - raise OperationalException(e) from e + f'Error in additional_exchange_init due to {e.__class__.__name__}. Message: {e}' + ) from e - @retrier - def _set_leverage( - self, - leverage: float, - pair: Optional[str] = None, - trading_mode: Optional[TradingMode] = None - ): - """ - Set's the leverage before making a trade, in order to not - have the same leverage on every trade - """ - trading_mode = trading_mode or self.trading_mode - - if self._config['dry_run'] or trading_mode != TradingMode.FUTURES: - return - - try: - self._api.set_leverage(symbol=pair, leverage=round(leverage)) - except ccxt.DDoSProtection as e: - raise DDosProtection(e) from e - except (ccxt.NetworkError, ccxt.ExchangeError) as e: - raise TemporaryError( - f'Could not set leverage due to {e.__class__.__name__}. Message: {e}') from e except ccxt.BaseError as e: raise OperationalException(e) from e @@ -150,6 +131,7 @@ class Binance(Exchange): is_short: bool, amount: float, 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 @@ -159,11 +141,12 @@ class Binance(Exchange): MARGIN: https://www.binance.com/en/support/faq/f6b010588e55413aa58b7d63ee0125ed PERPETUAL: https://www.binance.com/en/support/faq/b3c689c1f50a44cabb3a84e663b81d93 - :param exchange_name: + :param pair: Pair to calculate liquidation price for :param open_rate: Entry price of position :param is_short: True if the trade is a short, false otherwise :param amount: Absolute value of position size incl. leverage (in base currency) :param stake_amount: Stake amount - Collateral in settle currency. + :param leverage: Leverage used for this position. :param trading_mode: SPOT, MARGIN, FUTURES, etc. :param margin_mode: Either ISOLATED or CROSS :param wallet_balance: Amount of margin_mode in the wallet being used to trade diff --git a/freqtrade/exchange/binance_leverage_tiers.json b/freqtrade/exchange/binance_leverage_tiers.json index 908088cda..22db74f06 100644 --- a/freqtrade/exchange/binance_leverage_tiers.json +++ b/freqtrade/exchange/binance_leverage_tiers.json @@ -202,10 +202,10 @@ "minNotional": 0.0, "maxNotional": 5000.0, "maintenanceMarginRate": 0.02, - "maxLeverage": 20.0, + "maxLeverage": 25.0, "info": { "bracket": "1", - "initialLeverage": "20", + "initialLeverage": "25", "notionalCap": "5000", "notionalFloor": "0", "maintMarginRatio": "0.02", @@ -218,10 +218,10 @@ "minNotional": 5000.0, "maxNotional": 25000.0, "maintenanceMarginRate": 0.025, - "maxLeverage": 10.0, + "maxLeverage": 15.0, "info": { "bracket": "2", - "initialLeverage": "10", + "initialLeverage": "15", "notionalCap": "25000", "notionalFloor": "5000", "maintMarginRatio": "0.025", @@ -234,10 +234,10 @@ "minNotional": 25000.0, "maxNotional": 100000.0, "maintenanceMarginRate": 0.05, - "maxLeverage": 8.0, + "maxLeverage": 10.0, "info": { "bracket": "3", - "initialLeverage": "8", + "initialLeverage": "10", "notionalCap": "100000", "notionalFloor": "25000", "maintMarginRatio": "0.05", @@ -264,13 +264,13 @@ "tier": 5.0, "currency": "BUSD", "minNotional": 250000.0, - "maxNotional": 1000000.0, + "maxNotional": 1500000.0, "maintenanceMarginRate": 0.125, - "maxLeverage": 2.0, + "maxLeverage": 4.0, "info": { "bracket": "5", - "initialLeverage": "2", - "notionalCap": "1000000", + "initialLeverage": "4", + "notionalCap": "1500000", "notionalFloor": "250000", "maintMarginRatio": "0.125", "cum": "11900.0" @@ -279,17 +279,33 @@ { "tier": 6.0, "currency": "BUSD", - "minNotional": 1000000.0, - "maxNotional": 5000000.0, + "minNotional": 1500000.0, + "maxNotional": 3000000.0, + "maintenanceMarginRate": 0.25, + "maxLeverage": 2.0, + "info": { + "bracket": "6", + "initialLeverage": "2", + "notionalCap": "3000000", + "notionalFloor": "1500000", + "maintMarginRatio": "0.25", + "cum": "199400.0" + } + }, + { + "tier": 7.0, + "currency": "BUSD", + "minNotional": 3000000.0, + "maxNotional": 8000000.0, "maintenanceMarginRate": 0.5, "maxLeverage": 1.0, "info": { - "bracket": "6", + "bracket": "7", "initialLeverage": "1", - "notionalCap": "5000000", - "notionalFloor": "1000000", + "notionalCap": "8000000", + "notionalFloor": "3000000", "maintMarginRatio": "0.5", - "cum": "386900.0" + "cum": "949400.0" } } ], @@ -298,112 +314,144 @@ "tier": 1.0, "currency": "USDT", "minNotional": 0.0, - "maxNotional": 50000.0, - "maintenanceMarginRate": 0.01, - "maxLeverage": 25.0, + "maxNotional": 5000.0, + "maintenanceMarginRate": 0.0065, + "maxLeverage": 50.0, "info": { "bracket": "1", - "initialLeverage": "25", - "notionalCap": "50000", + "initialLeverage": "50", + "notionalCap": "5000", "notionalFloor": "0", - "maintMarginRatio": "0.01", + "maintMarginRatio": "0.0065", "cum": "0.0" } }, { "tier": 2.0, "currency": "USDT", + "minNotional": 5000.0, + "maxNotional": 25000.0, + "maintenanceMarginRate": 0.0075, + "maxLeverage": 40.0, + "info": { + "bracket": "2", + "initialLeverage": "40", + "notionalCap": "25000", + "notionalFloor": "5000", + "maintMarginRatio": "0.0075", + "cum": "5.0" + } + }, + { + "tier": 3.0, + "currency": "USDT", + "minNotional": 25000.0, + "maxNotional": 50000.0, + "maintenanceMarginRate": 0.01, + "maxLeverage": 25.0, + "info": { + "bracket": "3", + "initialLeverage": "25", + "notionalCap": "50000", + "notionalFloor": "25000", + "maintMarginRatio": "0.01", + "cum": "67.5" + } + }, + { + "tier": 4.0, + "currency": "USDT", "minNotional": 50000.0, "maxNotional": 150000.0, "maintenanceMarginRate": 0.025, "maxLeverage": 20.0, "info": { - "bracket": "2", + "bracket": "4", "initialLeverage": "20", "notionalCap": "150000", "notionalFloor": "50000", "maintMarginRatio": "0.025", - "cum": "750.0" + "cum": "817.5" } }, { - "tier": 3.0, + "tier": 5.0, "currency": "USDT", "minNotional": 150000.0, "maxNotional": 250000.0, "maintenanceMarginRate": 0.05, "maxLeverage": 10.0, "info": { - "bracket": "3", + "bracket": "5", "initialLeverage": "10", "notionalCap": "250000", "notionalFloor": "150000", "maintMarginRatio": "0.05", - "cum": "4500.0" + "cum": "4567.5" } }, { - "tier": 4.0, + "tier": 6.0, "currency": "USDT", "minNotional": 250000.0, "maxNotional": 500000.0, "maintenanceMarginRate": 0.1, "maxLeverage": 5.0, "info": { - "bracket": "4", + "bracket": "6", "initialLeverage": "5", "notionalCap": "500000", "notionalFloor": "250000", "maintMarginRatio": "0.1", - "cum": "17000.0" + "cum": "17067.5" } }, { - "tier": 5.0, + "tier": 7.0, "currency": "USDT", "minNotional": 500000.0, "maxNotional": 1000000.0, "maintenanceMarginRate": 0.125, "maxLeverage": 4.0, "info": { - "bracket": "5", + "bracket": "7", "initialLeverage": "4", "notionalCap": "1000000", "notionalFloor": "500000", "maintMarginRatio": "0.125", - "cum": "29500.0" + "cum": "29567.5" } }, { - "tier": 6.0, + "tier": 8.0, "currency": "USDT", "minNotional": 1000000.0, "maxNotional": 2000000.0, "maintenanceMarginRate": 0.25, "maxLeverage": 2.0, "info": { - "bracket": "6", + "bracket": "8", "initialLeverage": "2", "notionalCap": "2000000", "notionalFloor": "1000000", "maintMarginRatio": "0.25", - "cum": "154500.0" + "cum": "154567.5" } }, { - "tier": 7.0, + "tier": 9.0, "currency": "USDT", "minNotional": 2000000.0, "maxNotional": 30000000.0, "maintenanceMarginRate": 0.5, "maxLeverage": 1.0, "info": { - "bracket": "7", + "bracket": "9", "initialLeverage": "1", "notionalCap": "30000000", "notionalFloor": "2000000", "maintMarginRatio": "0.5", - "cum": "654500.0" + "cum": "654567.5" } } ], @@ -804,144 +852,356 @@ "tier": 1.0, "currency": "USDT", "minNotional": 0.0, - "maxNotional": 10000.0, - "maintenanceMarginRate": 0.0065, - "maxLeverage": 50.0, + "maxNotional": 5000.0, + "maintenanceMarginRate": 0.005, + "maxLeverage": 75.0, "info": { "bracket": "1", - "initialLeverage": "50", - "notionalCap": "10000", + "initialLeverage": "75", + "notionalCap": "5000", "notionalFloor": "0", - "maintMarginRatio": "0.0065", + "maintMarginRatio": "0.005", "cum": "0.0" } }, { "tier": 2.0, "currency": "USDT", + "minNotional": 5000.0, + "maxNotional": 10000.0, + "maintenanceMarginRate": 0.0065, + "maxLeverage": 50.0, + "info": { + "bracket": "2", + "initialLeverage": "50", + "notionalCap": "10000", + "notionalFloor": "5000", + "maintMarginRatio": "0.0065", + "cum": "7.5" + } + }, + { + "tier": 3.0, + "currency": "USDT", "minNotional": 10000.0, "maxNotional": 50000.0, "maintenanceMarginRate": 0.01, "maxLeverage": 40.0, "info": { - "bracket": "2", + "bracket": "3", "initialLeverage": "40", "notionalCap": "50000", "notionalFloor": "10000", "maintMarginRatio": "0.01", - "cum": "35.0" + "cum": "42.5" } }, { - "tier": 3.0, + "tier": 4.0, "currency": "USDT", "minNotional": 50000.0, "maxNotional": 250000.0, "maintenanceMarginRate": 0.02, "maxLeverage": 25.0, "info": { - "bracket": "3", + "bracket": "4", "initialLeverage": "25", "notionalCap": "250000", "notionalFloor": "50000", "maintMarginRatio": "0.02", - "cum": "535.0" + "cum": "542.5" } }, { - "tier": 4.0, + "tier": 5.0, "currency": "USDT", "minNotional": 250000.0, "maxNotional": 1000000.0, "maintenanceMarginRate": 0.05, "maxLeverage": 10.0, "info": { - "bracket": "4", + "bracket": "5", "initialLeverage": "10", "notionalCap": "1000000", "notionalFloor": "250000", "maintMarginRatio": "0.05", - "cum": "8035.0" + "cum": "8042.5" } }, { - "tier": 5.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": "58035.0" + "cum": "58042.5" } }, { - "tier": 6.0, + "tier": 7.0, "currency": "USDT", "minNotional": 2000000.0, "maxNotional": 5000000.0, "maintenanceMarginRate": 0.125, "maxLeverage": 4.0, "info": { - "bracket": "6", + "bracket": "7", "initialLeverage": "4", "notionalCap": "5000000", "notionalFloor": "2000000", "maintMarginRatio": "0.125", - "cum": "108035.0" + "cum": "108042.5" } }, { - "tier": 7.0, + "tier": 8.0, "currency": "USDT", "minNotional": 5000000.0, "maxNotional": 10000000.0, "maintenanceMarginRate": 0.15, "maxLeverage": 3.0, "info": { - "bracket": "7", + "bracket": "8", "initialLeverage": "3", "notionalCap": "10000000", "notionalFloor": "5000000", "maintMarginRatio": "0.15", - "cum": "233035.0" + "cum": "233042.5" } }, { - "tier": 8.0, + "tier": 9.0, "currency": "USDT", "minNotional": 10000000.0, "maxNotional": 20000000.0, "maintenanceMarginRate": 0.25, "maxLeverage": 2.0, "info": { - "bracket": "8", + "bracket": "9", "initialLeverage": "2", "notionalCap": "20000000", "notionalFloor": "10000000", "maintMarginRatio": "0.25", - "cum": "1233035.0" + "cum": "1233042.5" } }, { - "tier": 9.0, + "tier": 10.0, "currency": "USDT", "minNotional": 20000000.0, "maxNotional": 50000000.0, "maintenanceMarginRate": 0.5, "maxLeverage": 1.0, "info": { - "bracket": "9", + "bracket": "10", "initialLeverage": "1", "notionalCap": "50000000", "notionalFloor": "20000000", "maintMarginRatio": "0.5", - "cum": "6233035.0" + "cum": "6233042.5" + } + } + ], + "AGIX/BUSD:BUSD": [ + { + "tier": 1.0, + "currency": "BUSD", + "minNotional": 0.0, + "maxNotional": 5000.0, + "maintenanceMarginRate": 0.02, + "maxLeverage": 15.0, + "info": { + "bracket": "1", + "initialLeverage": "15", + "notionalCap": "5000", + "notionalFloor": "0", + "maintMarginRatio": "0.02", + "cum": "0.0" + } + }, + { + "tier": 2.0, + "currency": "BUSD", + "minNotional": 5000.0, + "maxNotional": 25000.0, + "maintenanceMarginRate": 0.025, + "maxLeverage": 10.0, + "info": { + "bracket": "2", + "initialLeverage": "10", + "notionalCap": "25000", + "notionalFloor": "5000", + "maintMarginRatio": "0.025", + "cum": "25.0" + } + }, + { + "tier": 3.0, + "currency": "BUSD", + "minNotional": 25000.0, + "maxNotional": 100000.0, + "maintenanceMarginRate": 0.05, + "maxLeverage": 8.0, + "info": { + "bracket": "3", + "initialLeverage": "8", + "notionalCap": "100000", + "notionalFloor": "25000", + "maintMarginRatio": "0.05", + "cum": "650.0" + } + }, + { + "tier": 4.0, + "currency": "BUSD", + "minNotional": 100000.0, + "maxNotional": 250000.0, + "maintenanceMarginRate": 0.1, + "maxLeverage": 5.0, + "info": { + "bracket": "4", + "initialLeverage": "5", + "notionalCap": "250000", + "notionalFloor": "100000", + "maintMarginRatio": "0.1", + "cum": "5650.0" + } + }, + { + "tier": 5.0, + "currency": "BUSD", + "minNotional": 250000.0, + "maxNotional": 1000000.0, + "maintenanceMarginRate": 0.125, + "maxLeverage": 2.0, + "info": { + "bracket": "5", + "initialLeverage": "2", + "notionalCap": "1000000", + "notionalFloor": "250000", + "maintMarginRatio": "0.125", + "cum": "11900.0" + } + }, + { + "tier": 6.0, + "currency": "BUSD", + "minNotional": 1000000.0, + "maxNotional": 5000000.0, + "maintenanceMarginRate": 0.5, + "maxLeverage": 1.0, + "info": { + "bracket": "6", + "initialLeverage": "1", + "notionalCap": "5000000", + "notionalFloor": "1000000", + "maintMarginRatio": "0.5", + "cum": "386900.0" + } + } + ], + "AGIX/USDT:USDT": [ + { + "tier": 1.0, + "currency": "USDT", + "minNotional": 0.0, + "maxNotional": 5000.0, + "maintenanceMarginRate": 0.02, + "maxLeverage": 20.0, + "info": { + "bracket": "1", + "initialLeverage": "20", + "notionalCap": "5000", + "notionalFloor": "0", + "maintMarginRatio": "0.02", + "cum": "0.0" + } + }, + { + "tier": 2.0, + "currency": "USDT", + "minNotional": 5000.0, + "maxNotional": 25000.0, + "maintenanceMarginRate": 0.025, + "maxLeverage": 15.0, + "info": { + "bracket": "2", + "initialLeverage": "15", + "notionalCap": "25000", + "notionalFloor": "5000", + "maintMarginRatio": "0.025", + "cum": "25.0" + } + }, + { + "tier": 3.0, + "currency": "USDT", + "minNotional": 25000.0, + "maxNotional": 100000.0, + "maintenanceMarginRate": 0.05, + "maxLeverage": 10.0, + "info": { + "bracket": "3", + "initialLeverage": "10", + "notionalCap": "100000", + "notionalFloor": "25000", + "maintMarginRatio": "0.05", + "cum": "650.0" + } + }, + { + "tier": 4.0, + "currency": "USDT", + "minNotional": 100000.0, + "maxNotional": 250000.0, + "maintenanceMarginRate": 0.1, + "maxLeverage": 5.0, + "info": { + "bracket": "4", + "initialLeverage": "5", + "notionalCap": "250000", + "notionalFloor": "100000", + "maintMarginRatio": "0.1", + "cum": "5650.0" + } + }, + { + "tier": 5.0, + "currency": "USDT", + "minNotional": 250000.0, + "maxNotional": 1000000.0, + "maintenanceMarginRate": 0.125, + "maxLeverage": 2.0, + "info": { + "bracket": "5", + "initialLeverage": "2", + "notionalCap": "1000000", + "notionalFloor": "250000", + "maintMarginRatio": "0.125", + "cum": "11900.0" + } + }, + { + "tier": 6.0, + "currency": "USDT", + "minNotional": 1000000.0, + "maxNotional": 5000000.0, + "maintenanceMarginRate": 0.5, + "maxLeverage": 1.0, + "info": { + "bracket": "6", + "initialLeverage": "1", + "notionalCap": "5000000", + "notionalFloor": "1000000", + "maintMarginRatio": "0.5", + "cum": "386900.0" } } ], @@ -1148,10 +1408,10 @@ "minNotional": 0.0, "maxNotional": 5000.0, "maintenanceMarginRate": 0.02, - "maxLeverage": 21.0, + "maxLeverage": 25.0, "info": { "bracket": "1", - "initialLeverage": "21", + "initialLeverage": "25", "notionalCap": "5000", "notionalFloor": "0", "maintMarginRatio": "0.02", @@ -1178,13 +1438,13 @@ "tier": 3.0, "currency": "USDT", "minNotional": 25000.0, - "maxNotional": 100000.0, + "maxNotional": 200000.0, "maintenanceMarginRate": 0.05, "maxLeverage": 10.0, "info": { "bracket": "3", "initialLeverage": "10", - "notionalCap": "100000", + "notionalCap": "200000", "notionalFloor": "25000", "maintMarginRatio": "0.05", "cum": "650.0" @@ -1193,23 +1453,23 @@ { "tier": 4.0, "currency": "USDT", - "minNotional": 100000.0, - "maxNotional": 250000.0, + "minNotional": 200000.0, + "maxNotional": 500000.0, "maintenanceMarginRate": 0.1, "maxLeverage": 5.0, "info": { "bracket": "4", "initialLeverage": "5", - "notionalCap": "250000", - "notionalFloor": "100000", + "notionalCap": "500000", + "notionalFloor": "200000", "maintMarginRatio": "0.1", - "cum": "5650.0" + "cum": "10650.0" } }, { "tier": 5.0, "currency": "USDT", - "minNotional": 250000.0, + "minNotional": 500000.0, "maxNotional": 1000000.0, "maintenanceMarginRate": 0.125, "maxLeverage": 2.0, @@ -1217,25 +1477,25 @@ "bracket": "5", "initialLeverage": "2", "notionalCap": "1000000", - "notionalFloor": "250000", + "notionalFloor": "500000", "maintMarginRatio": "0.125", - "cum": "11900.0" + "cum": "23150.0" } }, { "tier": 6.0, "currency": "USDT", "minNotional": 1000000.0, - "maxNotional": 3000000.0, + "maxNotional": 5000000.0, "maintenanceMarginRate": 0.5, "maxLeverage": 1.0, "info": { "bracket": "6", "initialLeverage": "1", - "notionalCap": "3000000", + "notionalCap": "5000000", "notionalFloor": "1000000", "maintMarginRatio": "0.5", - "cum": "386900.0" + "cum": "398150.0" } } ], @@ -1734,112 +1994,144 @@ "tier": 1.0, "currency": "USDT", "minNotional": 0.0, - "maxNotional": 50000.0, - "maintenanceMarginRate": 0.01, - "maxLeverage": 25.0, + "maxNotional": 5000.0, + "maintenanceMarginRate": 0.0065, + "maxLeverage": 50.0, "info": { "bracket": "1", - "initialLeverage": "25", - "notionalCap": "50000", + "initialLeverage": "50", + "notionalCap": "5000", "notionalFloor": "0", - "maintMarginRatio": "0.01", + "maintMarginRatio": "0.0065", "cum": "0.0" } }, { "tier": 2.0, "currency": "USDT", + "minNotional": 5000.0, + "maxNotional": 25000.0, + "maintenanceMarginRate": 0.0075, + "maxLeverage": 40.0, + "info": { + "bracket": "2", + "initialLeverage": "40", + "notionalCap": "25000", + "notionalFloor": "5000", + "maintMarginRatio": "0.0075", + "cum": "5.0" + } + }, + { + "tier": 3.0, + "currency": "USDT", + "minNotional": 25000.0, + "maxNotional": 50000.0, + "maintenanceMarginRate": 0.01, + "maxLeverage": 25.0, + "info": { + "bracket": "3", + "initialLeverage": "25", + "notionalCap": "50000", + "notionalFloor": "25000", + "maintMarginRatio": "0.01", + "cum": "67.5" + } + }, + { + "tier": 4.0, + "currency": "USDT", "minNotional": 50000.0, "maxNotional": 150000.0, "maintenanceMarginRate": 0.025, "maxLeverage": 20.0, "info": { - "bracket": "2", + "bracket": "4", "initialLeverage": "20", "notionalCap": "150000", "notionalFloor": "50000", "maintMarginRatio": "0.025", - "cum": "750.0" + "cum": "817.5" } }, { - "tier": 3.0, + "tier": 5.0, "currency": "USDT", "minNotional": 150000.0, "maxNotional": 250000.0, "maintenanceMarginRate": 0.05, "maxLeverage": 10.0, "info": { - "bracket": "3", + "bracket": "5", "initialLeverage": "10", "notionalCap": "250000", "notionalFloor": "150000", "maintMarginRatio": "0.05", - "cum": "4500.0" + "cum": "4567.5" } }, { - "tier": 4.0, + "tier": 6.0, "currency": "USDT", "minNotional": 250000.0, "maxNotional": 500000.0, "maintenanceMarginRate": 0.1, "maxLeverage": 5.0, "info": { - "bracket": "4", + "bracket": "6", "initialLeverage": "5", "notionalCap": "500000", "notionalFloor": "250000", "maintMarginRatio": "0.1", - "cum": "17000.0" + "cum": "17067.5" } }, { - "tier": 5.0, + "tier": 7.0, "currency": "USDT", "minNotional": 500000.0, "maxNotional": 1000000.0, "maintenanceMarginRate": 0.125, "maxLeverage": 4.0, "info": { - "bracket": "5", + "bracket": "7", "initialLeverage": "4", "notionalCap": "1000000", "notionalFloor": "500000", "maintMarginRatio": "0.125", - "cum": "29500.0" + "cum": "29567.5" } }, { - "tier": 6.0, + "tier": 8.0, "currency": "USDT", "minNotional": 1000000.0, - "maxNotional": 2000000.0, + "maxNotional": 5000000.0, "maintenanceMarginRate": 0.25, "maxLeverage": 2.0, "info": { - "bracket": "6", + "bracket": "8", "initialLeverage": "2", - "notionalCap": "2000000", + "notionalCap": "5000000", "notionalFloor": "1000000", "maintMarginRatio": "0.25", - "cum": "154500.0" + "cum": "154567.5" } }, { - "tier": 7.0, + "tier": 9.0, "currency": "USDT", - "minNotional": 2000000.0, - "maxNotional": 5000000.0, + "minNotional": 5000000.0, + "maxNotional": 10000000.0, "maintenanceMarginRate": 0.5, "maxLeverage": 1.0, "info": { - "bracket": "7", + "bracket": "9", "initialLeverage": "1", - "notionalCap": "5000000", - "notionalFloor": "2000000", + "notionalCap": "10000000", + "notionalFloor": "5000000", "maintMarginRatio": "0.5", - "cum": "654500.0" + "cum": "1404567.5" } } ], @@ -1948,10 +2240,10 @@ "minNotional": 0.0, "maxNotional": 5000.0, "maintenanceMarginRate": 0.02, - "maxLeverage": 20.0, + "maxLeverage": 25.0, "info": { "bracket": "1", - "initialLeverage": "20", + "initialLeverage": "25", "notionalCap": "5000", "notionalFloor": "0", "maintMarginRatio": "0.02", @@ -1964,10 +2256,10 @@ "minNotional": 5000.0, "maxNotional": 25000.0, "maintenanceMarginRate": 0.025, - "maxLeverage": 10.0, + "maxLeverage": 15.0, "info": { "bracket": "2", - "initialLeverage": "10", + "initialLeverage": "15", "notionalCap": "25000", "notionalFloor": "5000", "maintMarginRatio": "0.025", @@ -1980,10 +2272,10 @@ "minNotional": 25000.0, "maxNotional": 100000.0, "maintenanceMarginRate": 0.05, - "maxLeverage": 8.0, + "maxLeverage": 10.0, "info": { "bracket": "3", - "initialLeverage": "8", + "initialLeverage": "10", "notionalCap": "100000", "notionalFloor": "25000", "maintMarginRatio": "0.05", @@ -2010,13 +2302,13 @@ "tier": 5.0, "currency": "BUSD", "minNotional": 250000.0, - "maxNotional": 1000000.0, + "maxNotional": 1500000.0, "maintenanceMarginRate": 0.125, - "maxLeverage": 2.0, + "maxLeverage": 4.0, "info": { "bracket": "5", - "initialLeverage": "2", - "notionalCap": "1000000", + "initialLeverage": "4", + "notionalCap": "1500000", "notionalFloor": "250000", "maintMarginRatio": "0.125", "cum": "11900.0" @@ -2025,17 +2317,33 @@ { "tier": 6.0, "currency": "BUSD", - "minNotional": 1000000.0, - "maxNotional": 5000000.0, + "minNotional": 1500000.0, + "maxNotional": 3000000.0, + "maintenanceMarginRate": 0.25, + "maxLeverage": 2.0, + "info": { + "bracket": "6", + "initialLeverage": "2", + "notionalCap": "3000000", + "notionalFloor": "1500000", + "maintMarginRatio": "0.25", + "cum": "199400.0" + } + }, + { + "tier": 7.0, + "currency": "BUSD", + "minNotional": 3000000.0, + "maxNotional": 8000000.0, "maintenanceMarginRate": 0.5, "maxLeverage": 1.0, "info": { - "bracket": "6", + "bracket": "7", "initialLeverage": "1", - "notionalCap": "5000000", - "notionalFloor": "1000000", + "notionalCap": "8000000", + "notionalFloor": "3000000", "maintMarginRatio": "0.5", - "cum": "386900.0" + "cum": "949400.0" } } ], @@ -2044,112 +2352,144 @@ "tier": 1.0, "currency": "USDT", "minNotional": 0.0, - "maxNotional": 150000.0, - "maintenanceMarginRate": 0.02, - "maxLeverage": 21.0, + "maxNotional": 5000.0, + "maintenanceMarginRate": 0.015, + "maxLeverage": 25.0, "info": { "bracket": "1", - "initialLeverage": "21", - "notionalCap": "150000", + "initialLeverage": "25", + "notionalCap": "5000", "notionalFloor": "0", - "maintMarginRatio": "0.02", + "maintMarginRatio": "0.015", "cum": "0.0" } }, { "tier": 2.0, "currency": "USDT", - "minNotional": 150000.0, - "maxNotional": 250000.0, - "maintenanceMarginRate": 0.025, + "minNotional": 5000.0, + "maxNotional": 25000.0, + "maintenanceMarginRate": 0.02, "maxLeverage": 20.0, "info": { "bracket": "2", "initialLeverage": "20", - "notionalCap": "250000", - "notionalFloor": "150000", - "maintMarginRatio": "0.025", - "cum": "750.0" + "notionalCap": "25000", + "notionalFloor": "5000", + "maintMarginRatio": "0.02", + "cum": "25.0" } }, { "tier": 3.0, "currency": "USDT", - "minNotional": 250000.0, - "maxNotional": 1000000.0, - "maintenanceMarginRate": 0.05, - "maxLeverage": 10.0, + "minNotional": 25000.0, + "maxNotional": 150000.0, + "maintenanceMarginRate": 0.0225, + "maxLeverage": 15.0, "info": { "bracket": "3", - "initialLeverage": "10", - "notionalCap": "1000000", - "notionalFloor": "250000", - "maintMarginRatio": "0.05", - "cum": "7000.0" + "initialLeverage": "15", + "notionalCap": "150000", + "notionalFloor": "25000", + "maintMarginRatio": "0.0225", + "cum": "87.5" } }, { "tier": 4.0, "currency": "USDT", + "minNotional": 150000.0, + "maxNotional": 250000.0, + "maintenanceMarginRate": 0.025, + "maxLeverage": 10.0, + "info": { + "bracket": "4", + "initialLeverage": "10", + "notionalCap": "250000", + "notionalFloor": "150000", + "maintMarginRatio": "0.025", + "cum": "462.5" + } + }, + { + "tier": 5.0, + "currency": "USDT", + "minNotional": 250000.0, + "maxNotional": 1000000.0, + "maintenanceMarginRate": 0.05, + "maxLeverage": 8.0, + "info": { + "bracket": "5", + "initialLeverage": "8", + "notionalCap": "1000000", + "notionalFloor": "250000", + "maintMarginRatio": "0.05", + "cum": "6712.5" + } + }, + { + "tier": 6.0, + "currency": "USDT", "minNotional": 1000000.0, "maxNotional": 2000000.0, "maintenanceMarginRate": 0.1, "maxLeverage": 5.0, "info": { - "bracket": "4", + "bracket": "6", "initialLeverage": "5", "notionalCap": "2000000", "notionalFloor": "1000000", "maintMarginRatio": "0.1", - "cum": "57000.0" + "cum": "56712.5" } }, { - "tier": 5.0, + "tier": 7.0, "currency": "USDT", "minNotional": 2000000.0, "maxNotional": 5000000.0, "maintenanceMarginRate": 0.125, "maxLeverage": 4.0, "info": { - "bracket": "5", + "bracket": "7", "initialLeverage": "4", "notionalCap": "5000000", "notionalFloor": "2000000", "maintMarginRatio": "0.125", - "cum": "107000.0" + "cum": "106712.5" } }, { - "tier": 6.0, + "tier": 8.0, "currency": "USDT", "minNotional": 5000000.0, "maxNotional": 10000000.0, "maintenanceMarginRate": 0.25, "maxLeverage": 2.0, "info": { - "bracket": "6", + "bracket": "8", "initialLeverage": "2", "notionalCap": "10000000", "notionalFloor": "5000000", "maintMarginRatio": "0.25", - "cum": "732000.0" + "cum": "731712.5" } }, { - "tier": 7.0, + "tier": 9.0, "currency": "USDT", "minNotional": 10000000.0, - "maxNotional": 11000000.0, + "maxNotional": 20000000.0, "maintenanceMarginRate": 0.5, "maxLeverage": 1.0, "info": { - "bracket": "7", + "bracket": "9", "initialLeverage": "1", - "notionalCap": "11000000", + "notionalCap": "20000000", "notionalFloor": "10000000", "maintMarginRatio": "0.5", - "cum": "3232000.0" + "cum": "3231712.5" } } ], @@ -2349,6 +2689,104 @@ } } ], + "ASTR/USDT:USDT": [ + { + "tier": 1.0, + "currency": "USDT", + "minNotional": 0.0, + "maxNotional": 5000.0, + "maintenanceMarginRate": 0.02, + "maxLeverage": 20.0, + "info": { + "bracket": "1", + "initialLeverage": "20", + "notionalCap": "5000", + "notionalFloor": "0", + "maintMarginRatio": "0.02", + "cum": "0.0" + } + }, + { + "tier": 2.0, + "currency": "USDT", + "minNotional": 5000.0, + "maxNotional": 25000.0, + "maintenanceMarginRate": 0.025, + "maxLeverage": 15.0, + "info": { + "bracket": "2", + "initialLeverage": "15", + "notionalCap": "25000", + "notionalFloor": "5000", + "maintMarginRatio": "0.025", + "cum": "25.0" + } + }, + { + "tier": 3.0, + "currency": "USDT", + "minNotional": 25000.0, + "maxNotional": 100000.0, + "maintenanceMarginRate": 0.05, + "maxLeverage": 10.0, + "info": { + "bracket": "3", + "initialLeverage": "10", + "notionalCap": "100000", + "notionalFloor": "25000", + "maintMarginRatio": "0.05", + "cum": "650.0" + } + }, + { + "tier": 4.0, + "currency": "USDT", + "minNotional": 100000.0, + "maxNotional": 250000.0, + "maintenanceMarginRate": 0.1, + "maxLeverage": 5.0, + "info": { + "bracket": "4", + "initialLeverage": "5", + "notionalCap": "250000", + "notionalFloor": "100000", + "maintMarginRatio": "0.1", + "cum": "5650.0" + } + }, + { + "tier": 5.0, + "currency": "USDT", + "minNotional": 250000.0, + "maxNotional": 1000000.0, + "maintenanceMarginRate": 0.125, + "maxLeverage": 2.0, + "info": { + "bracket": "5", + "initialLeverage": "2", + "notionalCap": "1000000", + "notionalFloor": "250000", + "maintMarginRatio": "0.125", + "cum": "11900.0" + } + }, + { + "tier": 6.0, + "currency": "USDT", + "minNotional": 1000000.0, + "maxNotional": 5000000.0, + "maintenanceMarginRate": 0.5, + "maxLeverage": 1.0, + "info": { + "bracket": "6", + "initialLeverage": "1", + "notionalCap": "5000000", + "notionalFloor": "1000000", + "maintMarginRatio": "0.5", + "cum": "386900.0" + } + } + ], "ATA/USDT:USDT": [ { "tier": 1.0, @@ -2453,14 +2891,14 @@ "currency": "USDT", "minNotional": 0.0, "maxNotional": 5000.0, - "maintenanceMarginRate": 0.01, - "maxLeverage": 20.0, + "maintenanceMarginRate": 0.0065, + "maxLeverage": 50.0, "info": { "bracket": "1", - "initialLeverage": "20", + "initialLeverage": "50", "notionalCap": "5000", "notionalFloor": "0", - "maintMarginRatio": "0.01", + "maintMarginRatio": "0.0065", "cum": "0.0" } }, @@ -2468,64 +2906,64 @@ "tier": 2.0, "currency": "USDT", "minNotional": 5000.0, - "maxNotional": 25000.0, - "maintenanceMarginRate": 0.025, - "maxLeverage": 10.0, + "maxNotional": 50000.0, + "maintenanceMarginRate": 0.01, + "maxLeverage": 25.0, "info": { "bracket": "2", - "initialLeverage": "10", - "notionalCap": "25000", + "initialLeverage": "25", + "notionalCap": "50000", "notionalFloor": "5000", - "maintMarginRatio": "0.025", - "cum": "75.0" + "maintMarginRatio": "0.01", + "cum": "17.5" } }, { "tier": 3.0, "currency": "USDT", - "minNotional": 25000.0, - "maxNotional": 100000.0, - "maintenanceMarginRate": 0.05, - "maxLeverage": 8.0, + "minNotional": 50000.0, + "maxNotional": 200000.0, + "maintenanceMarginRate": 0.025, + "maxLeverage": 20.0, "info": { "bracket": "3", - "initialLeverage": "8", - "notionalCap": "100000", - "notionalFloor": "25000", - "maintMarginRatio": "0.05", - "cum": "700.0" + "initialLeverage": "20", + "notionalCap": "200000", + "notionalFloor": "50000", + "maintMarginRatio": "0.025", + "cum": "767.5" } }, { "tier": 4.0, "currency": "USDT", - "minNotional": 100000.0, - "maxNotional": 250000.0, - "maintenanceMarginRate": 0.1, - "maxLeverage": 5.0, + "minNotional": 200000.0, + "maxNotional": 400000.0, + "maintenanceMarginRate": 0.05, + "maxLeverage": 10.0, "info": { "bracket": "4", - "initialLeverage": "5", - "notionalCap": "250000", - "notionalFloor": "100000", - "maintMarginRatio": "0.1", - "cum": "5700.0" + "initialLeverage": "10", + "notionalCap": "400000", + "notionalFloor": "200000", + "maintMarginRatio": "0.05", + "cum": "5767.5" } }, { "tier": 5.0, "currency": "USDT", - "minNotional": 250000.0, + "minNotional": 400000.0, "maxNotional": 1000000.0, - "maintenanceMarginRate": 0.125, - "maxLeverage": 2.0, + "maintenanceMarginRate": 0.1, + "maxLeverage": 5.0, "info": { "bracket": "5", - "initialLeverage": "2", + "initialLeverage": "5", "notionalCap": "1000000", - "notionalFloor": "250000", - "maintMarginRatio": "0.125", - "cum": "11950.0" + "notionalFloor": "400000", + "maintMarginRatio": "0.1", + "cum": "25767.5" } }, { @@ -2533,15 +2971,47 @@ "currency": "USDT", "minNotional": 1000000.0, "maxNotional": 5000000.0, + "maintenanceMarginRate": 0.125, + "maxLeverage": 4.0, + "info": { + "bracket": "6", + "initialLeverage": "4", + "notionalCap": "5000000", + "notionalFloor": "1000000", + "maintMarginRatio": "0.125", + "cum": "50767.5" + } + }, + { + "tier": 7.0, + "currency": "USDT", + "minNotional": 5000000.0, + "maxNotional": 6000000.0, + "maintenanceMarginRate": 0.25, + "maxLeverage": 2.0, + "info": { + "bracket": "7", + "initialLeverage": "2", + "notionalCap": "6000000", + "notionalFloor": "5000000", + "maintMarginRatio": "0.25", + "cum": "675767.5" + } + }, + { + "tier": 8.0, + "currency": "USDT", + "minNotional": 6000000.0, + "maxNotional": 10000000.0, "maintenanceMarginRate": 0.5, "maxLeverage": 1.0, "info": { - "bracket": "6", + "bracket": "8", "initialLeverage": "1", - "notionalCap": "5000000", - "notionalFloor": "1000000", + "notionalCap": "10000000", + "notionalFloor": "6000000", "maintMarginRatio": "0.5", - "cum": "386950.0" + "cum": "2175767.5" } } ], @@ -2748,10 +3218,10 @@ "minNotional": 0.0, "maxNotional": 5000.0, "maintenanceMarginRate": 0.02, - "maxLeverage": 20.0, + "maxLeverage": 25.0, "info": { "bracket": "1", - "initialLeverage": "20", + "initialLeverage": "25", "notionalCap": "5000", "notionalFloor": "0", "maintMarginRatio": "0.02", @@ -2764,10 +3234,10 @@ "minNotional": 5000.0, "maxNotional": 25000.0, "maintenanceMarginRate": 0.025, - "maxLeverage": 10.0, + "maxLeverage": 15.0, "info": { "bracket": "2", - "initialLeverage": "10", + "initialLeverage": "15", "notionalCap": "25000", "notionalFloor": "5000", "maintMarginRatio": "0.025", @@ -2780,10 +3250,10 @@ "minNotional": 25000.0, "maxNotional": 100000.0, "maintenanceMarginRate": 0.05, - "maxLeverage": 8.0, + "maxLeverage": 10.0, "info": { "bracket": "3", - "initialLeverage": "8", + "initialLeverage": "10", "notionalCap": "100000", "notionalFloor": "25000", "maintMarginRatio": "0.05", @@ -2810,13 +3280,13 @@ "tier": 5.0, "currency": "BUSD", "minNotional": 250000.0, - "maxNotional": 1000000.0, + "maxNotional": 1500000.0, "maintenanceMarginRate": 0.125, - "maxLeverage": 2.0, + "maxLeverage": 4.0, "info": { "bracket": "5", - "initialLeverage": "2", - "notionalCap": "1000000", + "initialLeverage": "4", + "notionalCap": "1500000", "notionalFloor": "250000", "maintMarginRatio": "0.125", "cum": "11900.0" @@ -2825,17 +3295,33 @@ { "tier": 6.0, "currency": "BUSD", - "minNotional": 1000000.0, - "maxNotional": 5000000.0, + "minNotional": 1500000.0, + "maxNotional": 3000000.0, + "maintenanceMarginRate": 0.25, + "maxLeverage": 2.0, + "info": { + "bracket": "6", + "initialLeverage": "2", + "notionalCap": "3000000", + "notionalFloor": "1500000", + "maintMarginRatio": "0.25", + "cum": "199400.0" + } + }, + { + "tier": 7.0, + "currency": "BUSD", + "minNotional": 3000000.0, + "maxNotional": 8000000.0, "maintenanceMarginRate": 0.5, "maxLeverage": 1.0, "info": { - "bracket": "6", + "bracket": "7", "initialLeverage": "1", - "notionalCap": "5000000", - "notionalFloor": "1000000", + "notionalCap": "8000000", + "notionalFloor": "3000000", "maintMarginRatio": "0.5", - "cum": "386900.0" + "cum": "949400.0" } } ], @@ -2844,112 +3330,160 @@ "tier": 1.0, "currency": "USDT", "minNotional": 0.0, - "maxNotional": 50000.0, - "maintenanceMarginRate": 0.01, - "maxLeverage": 25.0, + "maxNotional": 5000.0, + "maintenanceMarginRate": 0.005, + "maxLeverage": 50.0, "info": { "bracket": "1", - "initialLeverage": "25", - "notionalCap": "50000", + "initialLeverage": "50", + "notionalCap": "5000", "notionalFloor": "0", - "maintMarginRatio": "0.01", + "maintMarginRatio": "0.005", "cum": "0.0" } }, { "tier": 2.0, "currency": "USDT", + "minNotional": 5000.0, + "maxNotional": 10000.0, + "maintenanceMarginRate": 0.0065, + "maxLeverage": 40.0, + "info": { + "bracket": "2", + "initialLeverage": "40", + "notionalCap": "10000", + "notionalFloor": "5000", + "maintMarginRatio": "0.0065", + "cum": "7.5" + } + }, + { + "tier": 3.0, + "currency": "USDT", + "minNotional": 10000.0, + "maxNotional": 25000.0, + "maintenanceMarginRate": 0.0075, + "maxLeverage": 30.0, + "info": { + "bracket": "3", + "initialLeverage": "30", + "notionalCap": "25000", + "notionalFloor": "10000", + "maintMarginRatio": "0.0075", + "cum": "17.5" + } + }, + { + "tier": 4.0, + "currency": "USDT", + "minNotional": 25000.0, + "maxNotional": 50000.0, + "maintenanceMarginRate": 0.01, + "maxLeverage": 25.0, + "info": { + "bracket": "4", + "initialLeverage": "25", + "notionalCap": "50000", + "notionalFloor": "25000", + "maintMarginRatio": "0.01", + "cum": "80.0" + } + }, + { + "tier": 5.0, + "currency": "USDT", "minNotional": 50000.0, "maxNotional": 250000.0, "maintenanceMarginRate": 0.025, "maxLeverage": 20.0, "info": { - "bracket": "2", + "bracket": "5", "initialLeverage": "20", "notionalCap": "250000", "notionalFloor": "50000", "maintMarginRatio": "0.025", - "cum": "750.0" + "cum": "830.0" } }, { - "tier": 3.0, + "tier": 6.0, "currency": "USDT", "minNotional": 250000.0, "maxNotional": 1000000.0, "maintenanceMarginRate": 0.05, "maxLeverage": 10.0, "info": { - "bracket": "3", + "bracket": "6", "initialLeverage": "10", "notionalCap": "1000000", "notionalFloor": "250000", "maintMarginRatio": "0.05", - "cum": "7000.0" + "cum": "7080.0" } }, { - "tier": 4.0, + "tier": 7.0, "currency": "USDT", "minNotional": 1000000.0, "maxNotional": 2000000.0, "maintenanceMarginRate": 0.1, "maxLeverage": 5.0, "info": { - "bracket": "4", + "bracket": "7", "initialLeverage": "5", "notionalCap": "2000000", "notionalFloor": "1000000", "maintMarginRatio": "0.1", - "cum": "57000.0" + "cum": "57080.0" } }, { - "tier": 5.0, + "tier": 8.0, "currency": "USDT", "minNotional": 2000000.0, "maxNotional": 5000000.0, "maintenanceMarginRate": 0.125, "maxLeverage": 4.0, "info": { - "bracket": "5", + "bracket": "8", "initialLeverage": "4", "notionalCap": "5000000", "notionalFloor": "2000000", "maintMarginRatio": "0.125", - "cum": "107000.0" + "cum": "107080.0" } }, { - "tier": 6.0, + "tier": 9.0, "currency": "USDT", "minNotional": 5000000.0, - "maxNotional": 10000000.0, + "maxNotional": 20000000.0, "maintenanceMarginRate": 0.25, "maxLeverage": 2.0, "info": { - "bracket": "6", + "bracket": "9", "initialLeverage": "2", - "notionalCap": "10000000", + "notionalCap": "20000000", "notionalFloor": "5000000", "maintMarginRatio": "0.25", - "cum": "732000.0" + "cum": "732080.0" } }, { - "tier": 7.0, + "tier": 10.0, "currency": "USDT", - "minNotional": 10000000.0, - "maxNotional": 20000000.0, + "minNotional": 20000000.0, + "maxNotional": 50000000.0, "maintenanceMarginRate": 0.5, "maxLeverage": 1.0, "info": { - "bracket": "7", + "bracket": "10", "initialLeverage": "1", - "notionalCap": "20000000", - "notionalFloor": "10000000", + "notionalCap": "50000000", + "notionalFloor": "20000000", "maintMarginRatio": "0.5", - "cum": "3232000.0" + "cum": "5732080.0" } } ], @@ -2958,128 +3492,160 @@ "tier": 1.0, "currency": "USDT", "minNotional": 0.0, - "maxNotional": 50000.0, - "maintenanceMarginRate": 0.01, + "maxNotional": 5000.0, + "maintenanceMarginRate": 0.0065, "maxLeverage": 25.0, "info": { "bracket": "1", "initialLeverage": "25", - "notionalCap": "50000", + "notionalCap": "5000", "notionalFloor": "0", - "maintMarginRatio": "0.01", + "maintMarginRatio": "0.0065", "cum": "0.0" } }, { "tier": 2.0, "currency": "USDT", - "minNotional": 50000.0, - "maxNotional": 250000.0, - "maintenanceMarginRate": 0.02, + "minNotional": 5000.0, + "maxNotional": 25000.0, + "maintenanceMarginRate": 0.0075, "maxLeverage": 20.0, "info": { "bracket": "2", "initialLeverage": "20", - "notionalCap": "250000", - "notionalFloor": "50000", - "maintMarginRatio": "0.02", - "cum": "500.0" + "notionalCap": "25000", + "notionalFloor": "5000", + "maintMarginRatio": "0.0075", + "cum": "5.0" } }, { "tier": 3.0, "currency": "USDT", + "minNotional": 25000.0, + "maxNotional": 50000.0, + "maintenanceMarginRate": 0.01, + "maxLeverage": 18.0, + "info": { + "bracket": "3", + "initialLeverage": "18", + "notionalCap": "50000", + "notionalFloor": "25000", + "maintMarginRatio": "0.01", + "cum": "67.5" + } + }, + { + "tier": 4.0, + "currency": "USDT", + "minNotional": 50000.0, + "maxNotional": 250000.0, + "maintenanceMarginRate": 0.02, + "maxLeverage": 15.0, + "info": { + "bracket": "4", + "initialLeverage": "15", + "notionalCap": "250000", + "notionalFloor": "50000", + "maintMarginRatio": "0.02", + "cum": "567.5" + } + }, + { + "tier": 5.0, + "currency": "USDT", "minNotional": 250000.0, "maxNotional": 1000000.0, "maintenanceMarginRate": 0.05, "maxLeverage": 10.0, "info": { - "bracket": "3", + "bracket": "5", "initialLeverage": "10", "notionalCap": "1000000", "notionalFloor": "250000", "maintMarginRatio": "0.05", - "cum": "8000.0" + "cum": "8067.5" } }, { - "tier": 4.0, + "tier": 6.0, "currency": "USDT", "minNotional": 1000000.0, "maxNotional": 2000000.0, "maintenanceMarginRate": 0.1, "maxLeverage": 5.0, "info": { - "bracket": "4", + "bracket": "6", "initialLeverage": "5", "notionalCap": "2000000", "notionalFloor": "1000000", "maintMarginRatio": "0.1", - "cum": "58000.0" + "cum": "58067.5" } }, { - "tier": 5.0, + "tier": 7.0, "currency": "USDT", "minNotional": 2000000.0, "maxNotional": 5000000.0, "maintenanceMarginRate": 0.125, "maxLeverage": 4.0, "info": { - "bracket": "5", + "bracket": "7", "initialLeverage": "4", "notionalCap": "5000000", "notionalFloor": "2000000", "maintMarginRatio": "0.125", - "cum": "108000.0" + "cum": "108067.5" } }, { - "tier": 6.0, + "tier": 8.0, "currency": "USDT", "minNotional": 5000000.0, "maxNotional": 10000000.0, "maintenanceMarginRate": 0.1665, "maxLeverage": 3.0, "info": { - "bracket": "6", + "bracket": "8", "initialLeverage": "3", "notionalCap": "10000000", "notionalFloor": "5000000", "maintMarginRatio": "0.1665", - "cum": "315500.0" + "cum": "315567.5" } }, { - "tier": 7.0, + "tier": 9.0, "currency": "USDT", "minNotional": 10000000.0, "maxNotional": 15000000.0, "maintenanceMarginRate": 0.25, "maxLeverage": 2.0, "info": { - "bracket": "7", + "bracket": "9", "initialLeverage": "2", "notionalCap": "15000000", "notionalFloor": "10000000", "maintMarginRatio": "0.25", - "cum": "1150500.0" + "cum": "1150567.5" } }, { - "tier": 8.0, + "tier": 10.0, "currency": "USDT", "minNotional": 15000000.0, "maxNotional": 20000000.0, "maintenanceMarginRate": 0.5, "maxLeverage": 1.0, "info": { - "bracket": "8", + "bracket": "10", "initialLeverage": "1", "notionalCap": "20000000", "notionalFloor": "15000000", "maintMarginRatio": "0.5", - "cum": "4900500.0" + "cum": "4900567.5" } } ], @@ -3480,144 +4046,160 @@ "tier": 1.0, "currency": "USDT", "minNotional": 0.0, - "maxNotional": 10000.0, - "maintenanceMarginRate": 0.0065, - "maxLeverage": 50.0, + "maxNotional": 5000.0, + "maintenanceMarginRate": 0.005, + "maxLeverage": 75.0, "info": { "bracket": "1", - "initialLeverage": "50", - "notionalCap": "10000", + "initialLeverage": "75", + "notionalCap": "5000", "notionalFloor": "0", - "maintMarginRatio": "0.0065", + "maintMarginRatio": "0.005", "cum": "0.0" } }, { "tier": 2.0, "currency": "USDT", + "minNotional": 5000.0, + "maxNotional": 10000.0, + "maintenanceMarginRate": 0.0065, + "maxLeverage": 50.0, + "info": { + "bracket": "2", + "initialLeverage": "50", + "notionalCap": "10000", + "notionalFloor": "5000", + "maintMarginRatio": "0.0065", + "cum": "7.5" + } + }, + { + "tier": 3.0, + "currency": "USDT", "minNotional": 10000.0, "maxNotional": 50000.0, "maintenanceMarginRate": 0.01, "maxLeverage": 40.0, "info": { - "bracket": "2", + "bracket": "3", "initialLeverage": "40", "notionalCap": "50000", "notionalFloor": "10000", "maintMarginRatio": "0.01", - "cum": "35.0" + "cum": "42.5" } }, { - "tier": 3.0, + "tier": 4.0, "currency": "USDT", "minNotional": 50000.0, "maxNotional": 250000.0, "maintenanceMarginRate": 0.02, "maxLeverage": 25.0, "info": { - "bracket": "3", + "bracket": "4", "initialLeverage": "25", "notionalCap": "250000", "notionalFloor": "50000", "maintMarginRatio": "0.02", - "cum": "535.0" + "cum": "542.5" } }, { - "tier": 4.0, + "tier": 5.0, "currency": "USDT", "minNotional": 250000.0, "maxNotional": 1000000.0, "maintenanceMarginRate": 0.05, "maxLeverage": 10.0, "info": { - "bracket": "4", + "bracket": "5", "initialLeverage": "10", "notionalCap": "1000000", "notionalFloor": "250000", "maintMarginRatio": "0.05", - "cum": "8035.0" + "cum": "8042.5" } }, { - "tier": 5.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": "58035.0" + "cum": "58042.5" } }, { - "tier": 6.0, + "tier": 7.0, "currency": "USDT", "minNotional": 2000000.0, "maxNotional": 5000000.0, "maintenanceMarginRate": 0.125, "maxLeverage": 4.0, "info": { - "bracket": "6", + "bracket": "7", "initialLeverage": "4", "notionalCap": "5000000", "notionalFloor": "2000000", "maintMarginRatio": "0.125", - "cum": "108035.0" + "cum": "108042.5" } }, { - "tier": 7.0, + "tier": 8.0, "currency": "USDT", "minNotional": 5000000.0, "maxNotional": 10000000.0, "maintenanceMarginRate": 0.15, "maxLeverage": 3.0, "info": { - "bracket": "7", + "bracket": "8", "initialLeverage": "3", "notionalCap": "10000000", "notionalFloor": "5000000", "maintMarginRatio": "0.15", - "cum": "233035.0" + "cum": "233042.5" } }, { - "tier": 8.0, + "tier": 9.0, "currency": "USDT", "minNotional": 10000000.0, "maxNotional": 20000000.0, "maintenanceMarginRate": 0.25, "maxLeverage": 2.0, "info": { - "bracket": "8", + "bracket": "9", "initialLeverage": "2", "notionalCap": "20000000", "notionalFloor": "10000000", "maintMarginRatio": "0.25", - "cum": "1233035.0" + "cum": "1233042.5" } }, { - "tier": 9.0, + "tier": 10.0, "currency": "USDT", "minNotional": 20000000.0, "maxNotional": 50000000.0, "maintenanceMarginRate": 0.5, "maxLeverage": 1.0, "info": { - "bracket": "9", + "bracket": "10", "initialLeverage": "1", "notionalCap": "50000000", "notionalFloor": "20000000", "maintMarginRatio": "0.5", - "cum": "6233035.0" + "cum": "6233042.5" } } ], @@ -4018,144 +4600,160 @@ "tier": 1.0, "currency": "USDT", "minNotional": 0.0, - "maxNotional": 10000.0, - "maintenanceMarginRate": 0.0065, - "maxLeverage": 50.0, + "maxNotional": 5000.0, + "maintenanceMarginRate": 0.005, + "maxLeverage": 75.0, "info": { "bracket": "1", - "initialLeverage": "50", - "notionalCap": "10000", + "initialLeverage": "75", + "notionalCap": "5000", "notionalFloor": "0", - "maintMarginRatio": "0.0065", + "maintMarginRatio": "0.005", "cum": "0.0" } }, { "tier": 2.0, "currency": "USDT", + "minNotional": 5000.0, + "maxNotional": 10000.0, + "maintenanceMarginRate": 0.0065, + "maxLeverage": 50.0, + "info": { + "bracket": "2", + "initialLeverage": "50", + "notionalCap": "10000", + "notionalFloor": "5000", + "maintMarginRatio": "0.0065", + "cum": "7.5" + } + }, + { + "tier": 3.0, + "currency": "USDT", "minNotional": 10000.0, "maxNotional": 50000.0, "maintenanceMarginRate": 0.01, "maxLeverage": 40.0, "info": { - "bracket": "2", + "bracket": "3", "initialLeverage": "40", "notionalCap": "50000", "notionalFloor": "10000", "maintMarginRatio": "0.01", - "cum": "35.0" + "cum": "42.5" } }, { - "tier": 3.0, + "tier": 4.0, "currency": "USDT", "minNotional": 50000.0, "maxNotional": 250000.0, "maintenanceMarginRate": 0.02, "maxLeverage": 25.0, "info": { - "bracket": "3", + "bracket": "4", "initialLeverage": "25", "notionalCap": "250000", "notionalFloor": "50000", "maintMarginRatio": "0.02", - "cum": "535.0" + "cum": "542.5" } }, { - "tier": 4.0, + "tier": 5.0, "currency": "USDT", "minNotional": 250000.0, "maxNotional": 1000000.0, "maintenanceMarginRate": 0.05, "maxLeverage": 10.0, "info": { - "bracket": "4", + "bracket": "5", "initialLeverage": "10", "notionalCap": "1000000", "notionalFloor": "250000", "maintMarginRatio": "0.05", - "cum": "8035.0" + "cum": "8042.5" } }, { - "tier": 5.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": "58035.0" + "cum": "58042.5" } }, { - "tier": 6.0, + "tier": 7.0, "currency": "USDT", "minNotional": 2000000.0, "maxNotional": 5000000.0, "maintenanceMarginRate": 0.125, "maxLeverage": 4.0, "info": { - "bracket": "6", + "bracket": "7", "initialLeverage": "4", "notionalCap": "5000000", "notionalFloor": "2000000", "maintMarginRatio": "0.125", - "cum": "108035.0" + "cum": "108042.5" } }, { - "tier": 7.0, + "tier": 8.0, "currency": "USDT", "minNotional": 5000000.0, "maxNotional": 10000000.0, "maintenanceMarginRate": 0.15, "maxLeverage": 3.0, "info": { - "bracket": "7", + "bracket": "8", "initialLeverage": "3", "notionalCap": "10000000", "notionalFloor": "5000000", "maintMarginRatio": "0.15", - "cum": "233035.0" + "cum": "233042.5" } }, { - "tier": 8.0, + "tier": 9.0, "currency": "USDT", "minNotional": 10000000.0, "maxNotional": 20000000.0, "maintenanceMarginRate": 0.25, "maxLeverage": 2.0, "info": { - "bracket": "8", + "bracket": "9", "initialLeverage": "2", "notionalCap": "20000000", "notionalFloor": "10000000", "maintMarginRatio": "0.25", - "cum": "1233035.0" + "cum": "1233042.5" } }, { - "tier": 9.0, + "tier": 10.0, "currency": "USDT", "minNotional": 20000000.0, "maxNotional": 50000000.0, "maintenanceMarginRate": 0.5, "maxLeverage": 1.0, "info": { - "bracket": "9", + "bracket": "10", "initialLeverage": "1", "notionalCap": "50000000", "notionalFloor": "20000000", "maintMarginRatio": "0.5", - "cum": "6233035.0" + "cum": "6233042.5" } } ], @@ -4264,10 +4862,10 @@ "minNotional": 0.0, "maxNotional": 50000.0, "maintenanceMarginRate": 0.004, - "maxLeverage": 50.0, + "maxLeverage": 75.0, "info": { "bracket": "1", - "initialLeverage": "50", + "initialLeverage": "75", "notionalCap": "50000", "notionalFloor": "0", "maintMarginRatio": "0.004", @@ -4280,10 +4878,10 @@ "minNotional": 50000.0, "maxNotional": 250000.0, "maintenanceMarginRate": 0.005, - "maxLeverage": 25.0, + "maxLeverage": 50.0, "info": { "bracket": "2", - "initialLeverage": "25", + "initialLeverage": "50", "notionalCap": "250000", "notionalFloor": "50000", "maintMarginRatio": "0.005", @@ -4296,10 +4894,10 @@ "minNotional": 250000.0, "maxNotional": 1000000.0, "maintenanceMarginRate": 0.01, - "maxLeverage": 20.0, + "maxLeverage": 25.0, "info": { "bracket": "3", - "initialLeverage": "20", + "initialLeverage": "25", "notionalCap": "1000000", "notionalFloor": "250000", "maintMarginRatio": "0.01", @@ -4312,10 +4910,10 @@ "minNotional": 1000000.0, "maxNotional": 7500000.0, "maintenanceMarginRate": 0.025, - "maxLeverage": 10.0, + "maxLeverage": 20.0, "info": { "bracket": "4", - "initialLeverage": "10", + "initialLeverage": "20", "notionalCap": "7500000", "notionalFloor": "1000000", "maintMarginRatio": "0.025", @@ -4328,10 +4926,10 @@ "minNotional": 7500000.0, "maxNotional": 40000000.0, "maintenanceMarginRate": 0.05, - "maxLeverage": 6.0, + "maxLeverage": 10.0, "info": { "bracket": "5", - "initialLeverage": "6", + "initialLeverage": "10", "notionalCap": "40000000", "notionalFloor": "7500000", "maintMarginRatio": "0.05", @@ -4458,10 +5056,10 @@ "minNotional": 250000.0, "maxNotional": 1000000.0, "maintenanceMarginRate": 0.01, - "maxLeverage": 25.0, + "maxLeverage": 50.0, "info": { "bracket": "3", - "initialLeverage": "25", + "initialLeverage": "50", "notionalCap": "1000000", "notionalFloor": "250000", "maintMarginRatio": "0.01", @@ -4474,10 +5072,10 @@ "minNotional": 1000000.0, "maxNotional": 10000000.0, "maintenanceMarginRate": 0.025, - "maxLeverage": 15.0, + "maxLeverage": 20.0, "info": { "bracket": "4", - "initialLeverage": "15", + "initialLeverage": "20", "notionalCap": "10000000", "notionalFloor": "1000000", "maintMarginRatio": "0.025", @@ -5290,10 +5888,10 @@ "minNotional": 0.0, "maxNotional": 5000.0, "maintenanceMarginRate": 0.02, - "maxLeverage": 21.0, + "maxLeverage": 25.0, "info": { "bracket": "1", - "initialLeverage": "21", + "initialLeverage": "25", "notionalCap": "5000", "notionalFloor": "0", "maintMarginRatio": "0.02", @@ -5320,13 +5918,13 @@ "tier": 3.0, "currency": "USDT", "minNotional": 25000.0, - "maxNotional": 100000.0, + "maxNotional": 200000.0, "maintenanceMarginRate": 0.05, "maxLeverage": 10.0, "info": { "bracket": "3", "initialLeverage": "10", - "notionalCap": "100000", + "notionalCap": "200000", "notionalFloor": "25000", "maintMarginRatio": "0.05", "cum": "650.0" @@ -5335,23 +5933,23 @@ { "tier": 4.0, "currency": "USDT", - "minNotional": 100000.0, - "maxNotional": 250000.0, + "minNotional": 200000.0, + "maxNotional": 500000.0, "maintenanceMarginRate": 0.1, "maxLeverage": 5.0, "info": { "bracket": "4", "initialLeverage": "5", - "notionalCap": "250000", - "notionalFloor": "100000", + "notionalCap": "500000", + "notionalFloor": "200000", "maintMarginRatio": "0.1", - "cum": "5650.0" + "cum": "10650.0" } }, { "tier": 5.0, "currency": "USDT", - "minNotional": 250000.0, + "minNotional": 500000.0, "maxNotional": 1000000.0, "maintenanceMarginRate": 0.125, "maxLeverage": 2.0, @@ -5359,25 +5957,25 @@ "bracket": "5", "initialLeverage": "2", "notionalCap": "1000000", - "notionalFloor": "250000", + "notionalFloor": "500000", "maintMarginRatio": "0.125", - "cum": "11900.0" + "cum": "23150.0" } }, { "tier": 6.0, "currency": "USDT", "minNotional": 1000000.0, - "maxNotional": 3000000.0, + "maxNotional": 5000000.0, "maintenanceMarginRate": 0.5, "maxLeverage": 1.0, "info": { "bracket": "6", "initialLeverage": "1", - "notionalCap": "3000000", + "notionalCap": "5000000", "notionalFloor": "1000000", "maintMarginRatio": "0.5", - "cum": "386900.0" + "cum": "398150.0" } } ], @@ -5584,10 +6182,10 @@ "minNotional": 0.0, "maxNotional": 5000.0, "maintenanceMarginRate": 0.02, - "maxLeverage": 21.0, + "maxLeverage": 25.0, "info": { "bracket": "1", - "initialLeverage": "21", + "initialLeverage": "25", "notionalCap": "5000", "notionalFloor": "0", "maintMarginRatio": "0.02", @@ -5614,13 +6212,13 @@ "tier": 3.0, "currency": "USDT", "minNotional": 25000.0, - "maxNotional": 100000.0, + "maxNotional": 200000.0, "maintenanceMarginRate": 0.05, "maxLeverage": 10.0, "info": { "bracket": "3", "initialLeverage": "10", - "notionalCap": "100000", + "notionalCap": "200000", "notionalFloor": "25000", "maintMarginRatio": "0.05", "cum": "650.0" @@ -5629,23 +6227,23 @@ { "tier": 4.0, "currency": "USDT", - "minNotional": 100000.0, - "maxNotional": 250000.0, + "minNotional": 200000.0, + "maxNotional": 500000.0, "maintenanceMarginRate": 0.1, "maxLeverage": 5.0, "info": { "bracket": "4", "initialLeverage": "5", - "notionalCap": "250000", - "notionalFloor": "100000", + "notionalCap": "500000", + "notionalFloor": "200000", "maintMarginRatio": "0.1", - "cum": "5650.0" + "cum": "10650.0" } }, { "tier": 5.0, "currency": "USDT", - "minNotional": 250000.0, + "minNotional": 500000.0, "maxNotional": 1000000.0, "maintenanceMarginRate": 0.125, "maxLeverage": 2.0, @@ -5653,25 +6251,25 @@ "bracket": "5", "initialLeverage": "2", "notionalCap": "1000000", - "notionalFloor": "250000", + "notionalFloor": "500000", "maintMarginRatio": "0.125", - "cum": "11900.0" + "cum": "23150.0" } }, { "tier": 6.0, "currency": "USDT", "minNotional": 1000000.0, - "maxNotional": 3000000.0, + "maxNotional": 5000000.0, "maintenanceMarginRate": 0.5, "maxLeverage": 1.0, "info": { "bracket": "6", "initialLeverage": "1", - "notionalCap": "3000000", + "notionalCap": "5000000", "notionalFloor": "1000000", "maintMarginRatio": "0.5", - "cum": "386900.0" + "cum": "398150.0" } } ], @@ -5682,10 +6280,10 @@ "minNotional": 0.0, "maxNotional": 50000.0, "maintenanceMarginRate": 0.01, - "maxLeverage": 21.0, + "maxLeverage": 25.0, "info": { "bracket": "1", - "initialLeverage": "21", + "initialLeverage": "25", "notionalCap": "50000", "notionalFloor": "0", "maintMarginRatio": "0.01", @@ -5760,13 +6358,13 @@ "tier": 6.0, "currency": "USDT", "minNotional": 1000000.0, - "maxNotional": 2000000.0, + "maxNotional": 3000000.0, "maintenanceMarginRate": 0.25, "maxLeverage": 2.0, "info": { "bracket": "6", "initialLeverage": "2", - "notionalCap": "2000000", + "notionalCap": "3000000", "notionalFloor": "1000000", "maintMarginRatio": "0.25", "cum": "154500.0" @@ -5775,17 +6373,17 @@ { "tier": 7.0, "currency": "USDT", - "minNotional": 2000000.0, - "maxNotional": 3000000.0, + "minNotional": 3000000.0, + "maxNotional": 5000000.0, "maintenanceMarginRate": 0.5, "maxLeverage": 1.0, "info": { "bracket": "7", "initialLeverage": "1", - "notionalCap": "3000000", - "notionalFloor": "2000000", + "notionalCap": "5000000", + "notionalFloor": "3000000", "maintMarginRatio": "0.5", - "cum": "654500.0" + "cum": "904500.0" } } ], @@ -6254,10 +6852,10 @@ "minNotional": 0.0, "maxNotional": 5000.0, "maintenanceMarginRate": 0.02, - "maxLeverage": 10.0, + "maxLeverage": 20.0, "info": { "bracket": "1", - "initialLeverage": "10", + "initialLeverage": "20", "notionalCap": "5000", "notionalFloor": "0", "maintMarginRatio": "0.02", @@ -6270,10 +6868,10 @@ "minNotional": 5000.0, "maxNotional": 25000.0, "maintenanceMarginRate": 0.025, - "maxLeverage": 8.0, + "maxLeverage": 10.0, "info": { "bracket": "2", - "initialLeverage": "8", + "initialLeverage": "10", "notionalCap": "25000", "notionalFloor": "5000", "maintMarginRatio": "0.025", @@ -6286,10 +6884,10 @@ "minNotional": 25000.0, "maxNotional": 100000.0, "maintenanceMarginRate": 0.05, - "maxLeverage": 6.0, + "maxLeverage": 8.0, "info": { "bracket": "3", - "initialLeverage": "6", + "initialLeverage": "8", "notionalCap": "100000", "notionalFloor": "25000", "maintMarginRatio": "0.05", @@ -6332,13 +6930,13 @@ "tier": 6.0, "currency": "USDT", "minNotional": 1000000.0, - "maxNotional": 2000000.0, + "maxNotional": 5000000.0, "maintenanceMarginRate": 0.5, "maxLeverage": 1.0, "info": { "bracket": "6", "initialLeverage": "1", - "notionalCap": "2000000", + "notionalCap": "5000000", "notionalFloor": "1000000", "maintMarginRatio": "0.5", "cum": "386900.0" @@ -6548,10 +7146,10 @@ "minNotional": 0.0, "maxNotional": 5000.0, "maintenanceMarginRate": 0.02, - "maxLeverage": 10.0, + "maxLeverage": 15.0, "info": { "bracket": "1", - "initialLeverage": "10", + "initialLeverage": "15", "notionalCap": "5000", "notionalFloor": "0", "maintMarginRatio": "0.02", @@ -6564,10 +7162,10 @@ "minNotional": 5000.0, "maxNotional": 25000.0, "maintenanceMarginRate": 0.025, - "maxLeverage": 8.0, + "maxLeverage": 10.0, "info": { "bracket": "2", - "initialLeverage": "8", + "initialLeverage": "10", "notionalCap": "25000", "notionalFloor": "5000", "maintMarginRatio": "0.025", @@ -6578,13 +7176,13 @@ "tier": 3.0, "currency": "USDT", "minNotional": 25000.0, - "maxNotional": 100000.0, + "maxNotional": 200000.0, "maintenanceMarginRate": 0.05, - "maxLeverage": 6.0, + "maxLeverage": 8.0, "info": { "bracket": "3", - "initialLeverage": "6", - "notionalCap": "100000", + "initialLeverage": "8", + "notionalCap": "200000", "notionalFloor": "25000", "maintMarginRatio": "0.05", "cum": "650.0" @@ -6593,23 +7191,23 @@ { "tier": 4.0, "currency": "USDT", - "minNotional": 100000.0, - "maxNotional": 250000.0, + "minNotional": 200000.0, + "maxNotional": 500000.0, "maintenanceMarginRate": 0.1, "maxLeverage": 5.0, "info": { "bracket": "4", "initialLeverage": "5", - "notionalCap": "250000", - "notionalFloor": "100000", + "notionalCap": "500000", + "notionalFloor": "200000", "maintMarginRatio": "0.1", - "cum": "5650.0" + "cum": "10650.0" } }, { "tier": 5.0, "currency": "USDT", - "minNotional": 250000.0, + "minNotional": 500000.0, "maxNotional": 1000000.0, "maintenanceMarginRate": 0.125, "maxLeverage": 2.0, @@ -6617,9 +7215,9 @@ "bracket": "5", "initialLeverage": "2", "notionalCap": "1000000", - "notionalFloor": "250000", + "notionalFloor": "500000", "maintMarginRatio": "0.125", - "cum": "11900.0" + "cum": "23150.0" } }, { @@ -6635,7 +7233,7 @@ "notionalCap": "5000000", "notionalFloor": "1000000", "maintMarginRatio": "0.5", - "cum": "386900.0" + "cum": "398150.0" } } ], @@ -6938,112 +7536,144 @@ "tier": 1.0, "currency": "USDT", "minNotional": 0.0, - "maxNotional": 50000.0, - "maintenanceMarginRate": 0.01, - "maxLeverage": 25.0, + "maxNotional": 5000.0, + "maintenanceMarginRate": 0.0065, + "maxLeverage": 50.0, "info": { "bracket": "1", - "initialLeverage": "25", - "notionalCap": "50000", + "initialLeverage": "50", + "notionalCap": "5000", "notionalFloor": "0", - "maintMarginRatio": "0.01", + "maintMarginRatio": "0.0065", "cum": "0.0" } }, { "tier": 2.0, "currency": "USDT", + "minNotional": 5000.0, + "maxNotional": 25000.0, + "maintenanceMarginRate": 0.0075, + "maxLeverage": 40.0, + "info": { + "bracket": "2", + "initialLeverage": "40", + "notionalCap": "25000", + "notionalFloor": "5000", + "maintMarginRatio": "0.0075", + "cum": "5.0" + } + }, + { + "tier": 3.0, + "currency": "USDT", + "minNotional": 25000.0, + "maxNotional": 50000.0, + "maintenanceMarginRate": 0.01, + "maxLeverage": 25.0, + "info": { + "bracket": "3", + "initialLeverage": "25", + "notionalCap": "50000", + "notionalFloor": "25000", + "maintMarginRatio": "0.01", + "cum": "67.5" + } + }, + { + "tier": 4.0, + "currency": "USDT", "minNotional": 50000.0, "maxNotional": 250000.0, "maintenanceMarginRate": 0.025, "maxLeverage": 20.0, "info": { - "bracket": "2", + "bracket": "4", "initialLeverage": "20", "notionalCap": "250000", "notionalFloor": "50000", "maintMarginRatio": "0.025", - "cum": "750.0" + "cum": "817.5" } }, { - "tier": 3.0, + "tier": 5.0, "currency": "USDT", "minNotional": 250000.0, "maxNotional": 1000000.0, "maintenanceMarginRate": 0.05, "maxLeverage": 10.0, "info": { - "bracket": "3", + "bracket": "5", "initialLeverage": "10", "notionalCap": "1000000", "notionalFloor": "250000", "maintMarginRatio": "0.05", - "cum": "7000.0" + "cum": "7067.5" } }, { - "tier": 4.0, + "tier": 6.0, "currency": "USDT", "minNotional": 1000000.0, "maxNotional": 2000000.0, "maintenanceMarginRate": 0.1, "maxLeverage": 5.0, "info": { - "bracket": "4", + "bracket": "6", "initialLeverage": "5", "notionalCap": "2000000", "notionalFloor": "1000000", "maintMarginRatio": "0.1", - "cum": "57000.0" + "cum": "57067.5" } }, { - "tier": 5.0, + "tier": 7.0, "currency": "USDT", "minNotional": 2000000.0, "maxNotional": 5000000.0, "maintenanceMarginRate": 0.125, "maxLeverage": 4.0, "info": { - "bracket": "5", + "bracket": "7", "initialLeverage": "4", "notionalCap": "5000000", "notionalFloor": "2000000", "maintMarginRatio": "0.125", - "cum": "107000.0" + "cum": "107067.5" } }, { - "tier": 6.0, + "tier": 8.0, "currency": "USDT", "minNotional": 5000000.0, "maxNotional": 10000000.0, "maintenanceMarginRate": 0.25, "maxLeverage": 2.0, "info": { - "bracket": "6", + "bracket": "8", "initialLeverage": "2", "notionalCap": "10000000", "notionalFloor": "5000000", "maintMarginRatio": "0.25", - "cum": "732000.0" + "cum": "732067.5" } }, { - "tier": 7.0, + "tier": 9.0, "currency": "USDT", "minNotional": 10000000.0, "maxNotional": 20000000.0, "maintenanceMarginRate": 0.5, "maxLeverage": 1.0, "info": { - "bracket": "7", + "bracket": "9", "initialLeverage": "1", "notionalCap": "20000000", "notionalFloor": "10000000", "maintMarginRatio": "0.5", - "cum": "3232000.0" + "cum": "3232067.5" } } ], @@ -7054,10 +7684,10 @@ "minNotional": 0.0, "maxNotional": 5000.0, "maintenanceMarginRate": 0.02, - "maxLeverage": 20.0, + "maxLeverage": 25.0, "info": { "bracket": "1", - "initialLeverage": "20", + "initialLeverage": "25", "notionalCap": "5000", "notionalFloor": "0", "maintMarginRatio": "0.02", @@ -7070,10 +7700,10 @@ "minNotional": 5000.0, "maxNotional": 25000.0, "maintenanceMarginRate": 0.025, - "maxLeverage": 10.0, + "maxLeverage": 15.0, "info": { "bracket": "2", - "initialLeverage": "10", + "initialLeverage": "15", "notionalCap": "25000", "notionalFloor": "5000", "maintMarginRatio": "0.025", @@ -7086,10 +7716,10 @@ "minNotional": 25000.0, "maxNotional": 100000.0, "maintenanceMarginRate": 0.05, - "maxLeverage": 8.0, + "maxLeverage": 10.0, "info": { "bracket": "3", - "initialLeverage": "8", + "initialLeverage": "10", "notionalCap": "100000", "notionalFloor": "25000", "maintMarginRatio": "0.05", @@ -7116,13 +7746,13 @@ "tier": 5.0, "currency": "BUSD", "minNotional": 250000.0, - "maxNotional": 1000000.0, + "maxNotional": 1500000.0, "maintenanceMarginRate": 0.125, - "maxLeverage": 2.0, + "maxLeverage": 4.0, "info": { "bracket": "5", - "initialLeverage": "2", - "notionalCap": "1000000", + "initialLeverage": "4", + "notionalCap": "1500000", "notionalFloor": "250000", "maintMarginRatio": "0.125", "cum": "11900.0" @@ -7131,17 +7761,33 @@ { "tier": 6.0, "currency": "BUSD", - "minNotional": 1000000.0, - "maxNotional": 5000000.0, + "minNotional": 1500000.0, + "maxNotional": 3000000.0, + "maintenanceMarginRate": 0.25, + "maxLeverage": 2.0, + "info": { + "bracket": "6", + "initialLeverage": "2", + "notionalCap": "3000000", + "notionalFloor": "1500000", + "maintMarginRatio": "0.25", + "cum": "199400.0" + } + }, + { + "tier": 7.0, + "currency": "BUSD", + "minNotional": 3000000.0, + "maxNotional": 8000000.0, "maintenanceMarginRate": 0.5, "maxLeverage": 1.0, "info": { - "bracket": "6", + "bracket": "7", "initialLeverage": "1", - "notionalCap": "5000000", - "notionalFloor": "1000000", + "notionalCap": "8000000", + "notionalFloor": "3000000", "maintMarginRatio": "0.5", - "cum": "386900.0" + "cum": "949400.0" } } ], @@ -7490,13 +8136,13 @@ "tier": 7.0, "currency": "USDT", "minNotional": 4000000.0, - "maxNotional": 5000000.0, + "maxNotional": 8000000.0, "maintenanceMarginRate": 0.5, "maxLeverage": 1.0, "info": { "bracket": "7", "initialLeverage": "1", - "notionalCap": "5000000", + "notionalCap": "8000000", "notionalFloor": "4000000", "maintMarginRatio": "0.5", "cum": "1154000.0" @@ -7608,10 +8254,10 @@ "minNotional": 0.0, "maxNotional": 50000.0, "maintenanceMarginRate": 0.01, - "maxLeverage": 20.0, + "maxLeverage": 25.0, "info": { "bracket": "1", - "initialLeverage": "20", + "initialLeverage": "25", "notionalCap": "50000", "notionalFloor": "0", "maintMarginRatio": "0.01", @@ -7622,96 +8268,112 @@ "tier": 2.0, "currency": "USDT", "minNotional": 50000.0, - "maxNotional": 150000.0, - "maintenanceMarginRate": 0.025, - "maxLeverage": 10.0, + "maxNotional": 100000.0, + "maintenanceMarginRate": 0.02, + "maxLeverage": 20.0, "info": { "bracket": "2", - "initialLeverage": "10", - "notionalCap": "150000", + "initialLeverage": "20", + "notionalCap": "100000", "notionalFloor": "50000", - "maintMarginRatio": "0.025", - "cum": "750.0" + "maintMarginRatio": "0.02", + "cum": "500.0" } }, { "tier": 3.0, "currency": "USDT", + "minNotional": 100000.0, + "maxNotional": 150000.0, + "maintenanceMarginRate": 0.025, + "maxLeverage": 10.0, + "info": { + "bracket": "3", + "initialLeverage": "10", + "notionalCap": "150000", + "notionalFloor": "100000", + "maintMarginRatio": "0.025", + "cum": "1000.0" + } + }, + { + "tier": 4.0, + "currency": "USDT", "minNotional": 150000.0, "maxNotional": 250000.0, "maintenanceMarginRate": 0.05, "maxLeverage": 8.0, "info": { - "bracket": "3", + "bracket": "4", "initialLeverage": "8", "notionalCap": "250000", "notionalFloor": "150000", "maintMarginRatio": "0.05", - "cum": "4500.0" + "cum": "4750.0" } }, { - "tier": 4.0, + "tier": 5.0, "currency": "USDT", "minNotional": 250000.0, "maxNotional": 500000.0, "maintenanceMarginRate": 0.1, "maxLeverage": 5.0, "info": { - "bracket": "4", + "bracket": "5", "initialLeverage": "5", "notionalCap": "500000", "notionalFloor": "250000", "maintMarginRatio": "0.1", - "cum": "17000.0" + "cum": "17250.0" } }, { - "tier": 5.0, + "tier": 6.0, "currency": "USDT", "minNotional": 500000.0, "maxNotional": 1000000.0, "maintenanceMarginRate": 0.125, "maxLeverage": 4.0, "info": { - "bracket": "5", + "bracket": "6", "initialLeverage": "4", "notionalCap": "1000000", "notionalFloor": "500000", "maintMarginRatio": "0.125", - "cum": "29500.0" - } - }, - { - "tier": 6.0, - "currency": "USDT", - "minNotional": 1000000.0, - "maxNotional": 2000000.0, - "maintenanceMarginRate": 0.25, - "maxLeverage": 2.0, - "info": { - "bracket": "6", - "initialLeverage": "2", - "notionalCap": "2000000", - "notionalFloor": "1000000", - "maintMarginRatio": "0.25", - "cum": "154500.0" + "cum": "29750.0" } }, { "tier": 7.0, "currency": "USDT", - "minNotional": 2000000.0, - "maxNotional": 3000000.0, + "minNotional": 1000000.0, + "maxNotional": 5000000.0, + "maintenanceMarginRate": 0.25, + "maxLeverage": 2.0, + "info": { + "bracket": "7", + "initialLeverage": "2", + "notionalCap": "5000000", + "notionalFloor": "1000000", + "maintMarginRatio": "0.25", + "cum": "154750.0" + } + }, + { + "tier": 8.0, + "currency": "USDT", + "minNotional": 5000000.0, + "maxNotional": 10000000.0, "maintenanceMarginRate": 0.5, "maxLeverage": 1.0, "info": { - "bracket": "7", + "bracket": "8", "initialLeverage": "1", - "notionalCap": "3000000", - "notionalFloor": "2000000", + "notionalCap": "10000000", + "notionalFloor": "5000000", "maintMarginRatio": "0.5", - "cum": "654500.0" + "cum": "1404750.0" } } ], @@ -7818,144 +8480,160 @@ "tier": 1.0, "currency": "USDT", "minNotional": 0.0, - "maxNotional": 10000.0, - "maintenanceMarginRate": 0.0065, - "maxLeverage": 50.0, + "maxNotional": 5000.0, + "maintenanceMarginRate": 0.005, + "maxLeverage": 75.0, "info": { "bracket": "1", - "initialLeverage": "50", - "notionalCap": "10000", + "initialLeverage": "75", + "notionalCap": "5000", "notionalFloor": "0", - "maintMarginRatio": "0.0065", + "maintMarginRatio": "0.005", "cum": "0.0" } }, { "tier": 2.0, "currency": "USDT", + "minNotional": 5000.0, + "maxNotional": 10000.0, + "maintenanceMarginRate": 0.0065, + "maxLeverage": 50.0, + "info": { + "bracket": "2", + "initialLeverage": "50", + "notionalCap": "10000", + "notionalFloor": "5000", + "maintMarginRatio": "0.0065", + "cum": "7.5" + } + }, + { + "tier": 3.0, + "currency": "USDT", "minNotional": 10000.0, "maxNotional": 50000.0, "maintenanceMarginRate": 0.01, "maxLeverage": 40.0, "info": { - "bracket": "2", + "bracket": "3", "initialLeverage": "40", "notionalCap": "50000", "notionalFloor": "10000", "maintMarginRatio": "0.01", - "cum": "35.0" + "cum": "42.5" } }, { - "tier": 3.0, + "tier": 4.0, "currency": "USDT", "minNotional": 50000.0, "maxNotional": 250000.0, "maintenanceMarginRate": 0.02, "maxLeverage": 25.0, "info": { - "bracket": "3", + "bracket": "4", "initialLeverage": "25", "notionalCap": "250000", "notionalFloor": "50000", "maintMarginRatio": "0.02", - "cum": "535.0" + "cum": "542.5" } }, { - "tier": 4.0, + "tier": 5.0, "currency": "USDT", "minNotional": 250000.0, "maxNotional": 1000000.0, "maintenanceMarginRate": 0.05, "maxLeverage": 10.0, "info": { - "bracket": "4", + "bracket": "5", "initialLeverage": "10", "notionalCap": "1000000", "notionalFloor": "250000", "maintMarginRatio": "0.05", - "cum": "8035.0" + "cum": "8042.5" } }, { - "tier": 5.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": "58035.0" + "cum": "58042.5" } }, { - "tier": 6.0, + "tier": 7.0, "currency": "USDT", "minNotional": 2000000.0, "maxNotional": 5000000.0, "maintenanceMarginRate": 0.125, "maxLeverage": 4.0, "info": { - "bracket": "6", + "bracket": "7", "initialLeverage": "4", "notionalCap": "5000000", "notionalFloor": "2000000", "maintMarginRatio": "0.125", - "cum": "108035.0" + "cum": "108042.5" } }, { - "tier": 7.0, + "tier": 8.0, "currency": "USDT", "minNotional": 5000000.0, "maxNotional": 10000000.0, "maintenanceMarginRate": 0.15, "maxLeverage": 3.0, "info": { - "bracket": "7", + "bracket": "8", "initialLeverage": "3", "notionalCap": "10000000", "notionalFloor": "5000000", "maintMarginRatio": "0.15", - "cum": "233035.0" + "cum": "233042.5" } }, { - "tier": 8.0, + "tier": 9.0, "currency": "USDT", "minNotional": 10000000.0, "maxNotional": 20000000.0, "maintenanceMarginRate": 0.25, "maxLeverage": 2.0, "info": { - "bracket": "8", + "bracket": "9", "initialLeverage": "2", "notionalCap": "20000000", "notionalFloor": "10000000", "maintMarginRatio": "0.25", - "cum": "1233035.0" + "cum": "1233042.5" } }, { - "tier": 9.0, + "tier": 10.0, "currency": "USDT", "minNotional": 20000000.0, "maxNotional": 50000000.0, "maintenanceMarginRate": 0.5, "maxLeverage": 1.0, "info": { - "bracket": "9", + "bracket": "10", "initialLeverage": "1", "notionalCap": "50000000", "notionalFloor": "20000000", "maintMarginRatio": "0.5", - "cum": "6233035.0" + "cum": "6233042.5" } } ], @@ -8062,144 +8740,160 @@ "tier": 1.0, "currency": "USDT", "minNotional": 0.0, - "maxNotional": 10000.0, - "maintenanceMarginRate": 0.0065, - "maxLeverage": 50.0, + "maxNotional": 5000.0, + "maintenanceMarginRate": 0.005, + "maxLeverage": 75.0, "info": { "bracket": "1", - "initialLeverage": "50", - "notionalCap": "10000", + "initialLeverage": "75", + "notionalCap": "5000", "notionalFloor": "0", - "maintMarginRatio": "0.0065", + "maintMarginRatio": "0.005", "cum": "0.0" } }, { "tier": 2.0, "currency": "USDT", + "minNotional": 5000.0, + "maxNotional": 10000.0, + "maintenanceMarginRate": 0.0065, + "maxLeverage": 50.0, + "info": { + "bracket": "2", + "initialLeverage": "50", + "notionalCap": "10000", + "notionalFloor": "5000", + "maintMarginRatio": "0.0065", + "cum": "7.5" + } + }, + { + "tier": 3.0, + "currency": "USDT", "minNotional": 10000.0, "maxNotional": 50000.0, "maintenanceMarginRate": 0.01, "maxLeverage": 40.0, "info": { - "bracket": "2", + "bracket": "3", "initialLeverage": "40", "notionalCap": "50000", "notionalFloor": "10000", "maintMarginRatio": "0.01", - "cum": "35.0" + "cum": "42.5" } }, { - "tier": 3.0, + "tier": 4.0, "currency": "USDT", "minNotional": 50000.0, "maxNotional": 250000.0, "maintenanceMarginRate": 0.02, "maxLeverage": 25.0, "info": { - "bracket": "3", + "bracket": "4", "initialLeverage": "25", "notionalCap": "250000", "notionalFloor": "50000", "maintMarginRatio": "0.02", - "cum": "535.0" + "cum": "542.5" } }, { - "tier": 4.0, + "tier": 5.0, "currency": "USDT", "minNotional": 250000.0, "maxNotional": 1000000.0, "maintenanceMarginRate": 0.05, "maxLeverage": 10.0, "info": { - "bracket": "4", + "bracket": "5", "initialLeverage": "10", "notionalCap": "1000000", "notionalFloor": "250000", "maintMarginRatio": "0.05", - "cum": "8035.0" + "cum": "8042.5" } }, { - "tier": 5.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": "58035.0" + "cum": "58042.5" } }, { - "tier": 6.0, + "tier": 7.0, "currency": "USDT", "minNotional": 2000000.0, "maxNotional": 5000000.0, "maintenanceMarginRate": 0.125, "maxLeverage": 4.0, "info": { - "bracket": "6", + "bracket": "7", "initialLeverage": "4", "notionalCap": "5000000", "notionalFloor": "2000000", "maintMarginRatio": "0.125", - "cum": "108035.0" + "cum": "108042.5" } }, { - "tier": 7.0, + "tier": 8.0, "currency": "USDT", "minNotional": 5000000.0, "maxNotional": 10000000.0, "maintenanceMarginRate": 0.15, "maxLeverage": 3.0, "info": { - "bracket": "7", + "bracket": "8", "initialLeverage": "3", "notionalCap": "10000000", "notionalFloor": "5000000", "maintMarginRatio": "0.15", - "cum": "233035.0" + "cum": "233042.5" } }, { - "tier": 8.0, + "tier": 9.0, "currency": "USDT", "minNotional": 10000000.0, "maxNotional": 20000000.0, "maintenanceMarginRate": 0.25, "maxLeverage": 2.0, "info": { - "bracket": "8", + "bracket": "9", "initialLeverage": "2", "notionalCap": "20000000", "notionalFloor": "10000000", "maintMarginRatio": "0.25", - "cum": "1233035.0" + "cum": "1233042.5" } }, { - "tier": 9.0, + "tier": 10.0, "currency": "USDT", "minNotional": 20000000.0, "maxNotional": 50000000.0, "maintenanceMarginRate": 0.5, "maxLeverage": 1.0, "info": { - "bracket": "9", + "bracket": "10", "initialLeverage": "1", "notionalCap": "50000000", "notionalFloor": "20000000", "maintMarginRatio": "0.5", - "cum": "6233035.0" + "cum": "6233042.5" } } ], @@ -8210,10 +8904,10 @@ "minNotional": 0.0, "maxNotional": 50000.0, "maintenanceMarginRate": 0.004, - "maxLeverage": 50.0, + "maxLeverage": 75.0, "info": { "bracket": "1", - "initialLeverage": "50", + "initialLeverage": "75", "notionalCap": "50000", "notionalFloor": "0", "maintMarginRatio": "0.004", @@ -8226,10 +8920,10 @@ "minNotional": 50000.0, "maxNotional": 100000.0, "maintenanceMarginRate": 0.005, - "maxLeverage": 25.0, + "maxLeverage": 50.0, "info": { "bracket": "2", - "initialLeverage": "25", + "initialLeverage": "50", "notionalCap": "100000", "notionalFloor": "50000", "maintMarginRatio": "0.005", @@ -8242,10 +8936,10 @@ "minNotional": 100000.0, "maxNotional": 1000000.0, "maintenanceMarginRate": 0.01, - "maxLeverage": 20.0, + "maxLeverage": 25.0, "info": { "bracket": "3", - "initialLeverage": "20", + "initialLeverage": "25", "notionalCap": "1000000", "notionalFloor": "100000", "maintMarginRatio": "0.01", @@ -8258,10 +8952,10 @@ "minNotional": 1000000.0, "maxNotional": 5000000.0, "maintenanceMarginRate": 0.025, - "maxLeverage": 10.0, + "maxLeverage": 20.0, "info": { "bracket": "4", - "initialLeverage": "10", + "initialLeverage": "20", "notionalCap": "5000000", "notionalFloor": "1000000", "maintMarginRatio": "0.025", @@ -8274,10 +8968,10 @@ "minNotional": 5000000.0, "maxNotional": 10000000.0, "maintenanceMarginRate": 0.05, - "maxLeverage": 6.0, + "maxLeverage": 10.0, "info": { "bracket": "5", - "initialLeverage": "6", + "initialLeverage": "10", "notionalCap": "10000000", "notionalFloor": "5000000", "maintMarginRatio": "0.05", @@ -8404,10 +9098,10 @@ "minNotional": 250000.0, "maxNotional": 1000000.0, "maintenanceMarginRate": 0.01, - "maxLeverage": 25.0, + "maxLeverage": 50.0, "info": { "bracket": "3", - "initialLeverage": "25", + "initialLeverage": "50", "notionalCap": "1000000", "notionalFloor": "250000", "maintMarginRatio": "0.01", @@ -8420,10 +9114,10 @@ "minNotional": 1000000.0, "maxNotional": 5000000.0, "maintenanceMarginRate": 0.02, - "maxLeverage": 15.0, + "maxLeverage": 20.0, "info": { "bracket": "4", - "initialLeverage": "15", + "initialLeverage": "20", "notionalCap": "5000000", "notionalFloor": "1000000", "maintMarginRatio": "0.02", @@ -8641,6 +9335,104 @@ } } ], + "FET/USDT:USDT": [ + { + "tier": 1.0, + "currency": "USDT", + "minNotional": 0.0, + "maxNotional": 5000.0, + "maintenanceMarginRate": 0.02, + "maxLeverage": 20.0, + "info": { + "bracket": "1", + "initialLeverage": "20", + "notionalCap": "5000", + "notionalFloor": "0", + "maintMarginRatio": "0.02", + "cum": "0.0" + } + }, + { + "tier": 2.0, + "currency": "USDT", + "minNotional": 5000.0, + "maxNotional": 25000.0, + "maintenanceMarginRate": 0.025, + "maxLeverage": 15.0, + "info": { + "bracket": "2", + "initialLeverage": "15", + "notionalCap": "25000", + "notionalFloor": "5000", + "maintMarginRatio": "0.025", + "cum": "25.0" + } + }, + { + "tier": 3.0, + "currency": "USDT", + "minNotional": 25000.0, + "maxNotional": 100000.0, + "maintenanceMarginRate": 0.05, + "maxLeverage": 10.0, + "info": { + "bracket": "3", + "initialLeverage": "10", + "notionalCap": "100000", + "notionalFloor": "25000", + "maintMarginRatio": "0.05", + "cum": "650.0" + } + }, + { + "tier": 4.0, + "currency": "USDT", + "minNotional": 100000.0, + "maxNotional": 250000.0, + "maintenanceMarginRate": 0.1, + "maxLeverage": 5.0, + "info": { + "bracket": "4", + "initialLeverage": "5", + "notionalCap": "250000", + "notionalFloor": "100000", + "maintMarginRatio": "0.1", + "cum": "5650.0" + } + }, + { + "tier": 5.0, + "currency": "USDT", + "minNotional": 250000.0, + "maxNotional": 1000000.0, + "maintenanceMarginRate": 0.125, + "maxLeverage": 2.0, + "info": { + "bracket": "5", + "initialLeverage": "2", + "notionalCap": "1000000", + "notionalFloor": "250000", + "maintMarginRatio": "0.125", + "cum": "11900.0" + } + }, + { + "tier": 6.0, + "currency": "USDT", + "minNotional": 1000000.0, + "maxNotional": 5000000.0, + "maintenanceMarginRate": 0.5, + "maxLeverage": 1.0, + "info": { + "bracket": "6", + "initialLeverage": "1", + "notionalCap": "5000000", + "notionalFloor": "1000000", + "maintMarginRatio": "0.5", + "cum": "386900.0" + } + } + ], "FIL/BUSD:BUSD": [ { "tier": 1.0, @@ -9170,10 +9962,10 @@ "minNotional": 0.0, "maxNotional": 5000.0, "maintenanceMarginRate": 0.02, - "maxLeverage": 20.0, + "maxLeverage": 25.0, "info": { "bracket": "1", - "initialLeverage": "20", + "initialLeverage": "25", "notionalCap": "5000", "notionalFloor": "0", "maintMarginRatio": "0.02", @@ -9186,10 +9978,10 @@ "minNotional": 5000.0, "maxNotional": 25000.0, "maintenanceMarginRate": 0.025, - "maxLeverage": 10.0, + "maxLeverage": 15.0, "info": { "bracket": "2", - "initialLeverage": "10", + "initialLeverage": "15", "notionalCap": "25000", "notionalFloor": "5000", "maintMarginRatio": "0.025", @@ -9202,10 +9994,10 @@ "minNotional": 25000.0, "maxNotional": 100000.0, "maintenanceMarginRate": 0.05, - "maxLeverage": 8.0, + "maxLeverage": 10.0, "info": { "bracket": "3", - "initialLeverage": "8", + "initialLeverage": "10", "notionalCap": "100000", "notionalFloor": "25000", "maintMarginRatio": "0.05", @@ -9232,13 +10024,13 @@ "tier": 5.0, "currency": "BUSD", "minNotional": 250000.0, - "maxNotional": 1000000.0, + "maxNotional": 1500000.0, "maintenanceMarginRate": 0.125, - "maxLeverage": 2.0, + "maxLeverage": 4.0, "info": { "bracket": "5", - "initialLeverage": "2", - "notionalCap": "1000000", + "initialLeverage": "4", + "notionalCap": "1500000", "notionalFloor": "250000", "maintMarginRatio": "0.125", "cum": "11900.0" @@ -9247,17 +10039,33 @@ { "tier": 6.0, "currency": "BUSD", - "minNotional": 1000000.0, - "maxNotional": 5000000.0, + "minNotional": 1500000.0, + "maxNotional": 3000000.0, + "maintenanceMarginRate": 0.25, + "maxLeverage": 2.0, + "info": { + "bracket": "6", + "initialLeverage": "2", + "notionalCap": "3000000", + "notionalFloor": "1500000", + "maintMarginRatio": "0.25", + "cum": "199400.0" + } + }, + { + "tier": 7.0, + "currency": "BUSD", + "minNotional": 3000000.0, + "maxNotional": 8000000.0, "maintenanceMarginRate": 0.5, "maxLeverage": 1.0, "info": { - "bracket": "6", + "bracket": "7", "initialLeverage": "1", - "notionalCap": "5000000", - "notionalFloor": "1000000", + "notionalCap": "8000000", + "notionalFloor": "3000000", "maintMarginRatio": "0.5", - "cum": "386900.0" + "cum": "949400.0" } } ], @@ -9266,112 +10074,128 @@ "tier": 1.0, "currency": "USDT", "minNotional": 0.0, - "maxNotional": 50000.0, - "maintenanceMarginRate": 0.01, - "maxLeverage": 25.0, + "maxNotional": 5000.0, + "maintenanceMarginRate": 0.0075, + "maxLeverage": 50.0, "info": { "bracket": "1", - "initialLeverage": "25", - "notionalCap": "50000", + "initialLeverage": "50", + "notionalCap": "5000", "notionalFloor": "0", - "maintMarginRatio": "0.01", + "maintMarginRatio": "0.0075", "cum": "0.0" } }, { "tier": 2.0, "currency": "USDT", + "minNotional": 5000.0, + "maxNotional": 50000.0, + "maintenanceMarginRate": 0.01, + "maxLeverage": 25.0, + "info": { + "bracket": "2", + "initialLeverage": "25", + "notionalCap": "50000", + "notionalFloor": "5000", + "maintMarginRatio": "0.01", + "cum": "12.5" + } + }, + { + "tier": 3.0, + "currency": "USDT", "minNotional": 50000.0, "maxNotional": 150000.0, "maintenanceMarginRate": 0.025, "maxLeverage": 20.0, "info": { - "bracket": "2", + "bracket": "3", "initialLeverage": "20", "notionalCap": "150000", "notionalFloor": "50000", "maintMarginRatio": "0.025", - "cum": "750.0" + "cum": "762.5" } }, { - "tier": 3.0, + "tier": 4.0, "currency": "USDT", "minNotional": 150000.0, "maxNotional": 250000.0, "maintenanceMarginRate": 0.05, "maxLeverage": 10.0, "info": { - "bracket": "3", + "bracket": "4", "initialLeverage": "10", "notionalCap": "250000", "notionalFloor": "150000", "maintMarginRatio": "0.05", - "cum": "4500.0" + "cum": "4512.5" } }, { - "tier": 4.0, + "tier": 5.0, "currency": "USDT", "minNotional": 250000.0, "maxNotional": 500000.0, "maintenanceMarginRate": 0.1, "maxLeverage": 5.0, "info": { - "bracket": "4", + "bracket": "5", "initialLeverage": "5", "notionalCap": "500000", "notionalFloor": "250000", "maintMarginRatio": "0.1", - "cum": "17000.0" + "cum": "17012.5" } }, { - "tier": 5.0, + "tier": 6.0, "currency": "USDT", "minNotional": 500000.0, "maxNotional": 1000000.0, "maintenanceMarginRate": 0.125, "maxLeverage": 4.0, "info": { - "bracket": "5", + "bracket": "6", "initialLeverage": "4", "notionalCap": "1000000", "notionalFloor": "500000", "maintMarginRatio": "0.125", - "cum": "29500.0" + "cum": "29512.5" } }, { - "tier": 6.0, + "tier": 7.0, "currency": "USDT", "minNotional": 1000000.0, "maxNotional": 4000000.0, "maintenanceMarginRate": 0.25, "maxLeverage": 2.0, "info": { - "bracket": "6", + "bracket": "7", "initialLeverage": "2", "notionalCap": "4000000", "notionalFloor": "1000000", "maintMarginRatio": "0.25", - "cum": "154500.0" + "cum": "154512.5" } }, { - "tier": 7.0, + "tier": 8.0, "currency": "USDT", "minNotional": 4000000.0, - "maxNotional": 8000000.0, + "maxNotional": 10000000.0, "maintenanceMarginRate": 0.5, "maxLeverage": 1.0, "info": { - "bracket": "7", + "bracket": "8", "initialLeverage": "1", - "notionalCap": "8000000", + "notionalCap": "10000000", "notionalFloor": "4000000", "maintMarginRatio": "0.5", - "cum": "1154500.0" + "cum": "1154512.5" } } ], @@ -9555,6 +10379,104 @@ } } ], + "FXS/USDT:USDT": [ + { + "tier": 1.0, + "currency": "USDT", + "minNotional": 0.0, + "maxNotional": 5000.0, + "maintenanceMarginRate": 0.02, + "maxLeverage": 20.0, + "info": { + "bracket": "1", + "initialLeverage": "20", + "notionalCap": "5000", + "notionalFloor": "0", + "maintMarginRatio": "0.02", + "cum": "0.0" + } + }, + { + "tier": 2.0, + "currency": "USDT", + "minNotional": 5000.0, + "maxNotional": 25000.0, + "maintenanceMarginRate": 0.025, + "maxLeverage": 15.0, + "info": { + "bracket": "2", + "initialLeverage": "15", + "notionalCap": "25000", + "notionalFloor": "5000", + "maintMarginRatio": "0.025", + "cum": "25.0" + } + }, + { + "tier": 3.0, + "currency": "USDT", + "minNotional": 25000.0, + "maxNotional": 100000.0, + "maintenanceMarginRate": 0.05, + "maxLeverage": 10.0, + "info": { + "bracket": "3", + "initialLeverage": "10", + "notionalCap": "100000", + "notionalFloor": "25000", + "maintMarginRatio": "0.05", + "cum": "650.0" + } + }, + { + "tier": 4.0, + "currency": "USDT", + "minNotional": 100000.0, + "maxNotional": 250000.0, + "maintenanceMarginRate": 0.1, + "maxLeverage": 5.0, + "info": { + "bracket": "4", + "initialLeverage": "5", + "notionalCap": "250000", + "notionalFloor": "100000", + "maintMarginRatio": "0.1", + "cum": "5650.0" + } + }, + { + "tier": 5.0, + "currency": "USDT", + "minNotional": 250000.0, + "maxNotional": 1000000.0, + "maintenanceMarginRate": 0.125, + "maxLeverage": 2.0, + "info": { + "bracket": "5", + "initialLeverage": "2", + "notionalCap": "1000000", + "notionalFloor": "250000", + "maintMarginRatio": "0.125", + "cum": "11900.0" + } + }, + { + "tier": 6.0, + "currency": "USDT", + "minNotional": 1000000.0, + "maxNotional": 5000000.0, + "maintenanceMarginRate": 0.5, + "maxLeverage": 1.0, + "info": { + "bracket": "6", + "initialLeverage": "1", + "notionalCap": "5000000", + "notionalFloor": "1000000", + "maintMarginRatio": "0.5", + "cum": "386900.0" + } + } + ], "GAL/BUSD:BUSD": [ { "tier": 1.0, @@ -9758,10 +10680,10 @@ "minNotional": 0.0, "maxNotional": 5000.0, "maintenanceMarginRate": 0.02, - "maxLeverage": 11.0, + "maxLeverage": 20.0, "info": { "bracket": "1", - "initialLeverage": "11", + "initialLeverage": "20", "notionalCap": "5000", "notionalFloor": "0", "maintMarginRatio": "0.02", @@ -9855,14 +10777,14 @@ "currency": "USDT", "minNotional": 0.0, "maxNotional": 5000.0, - "maintenanceMarginRate": 0.01, - "maxLeverage": 11.0, + "maintenanceMarginRate": 0.0065, + "maxLeverage": 50.0, "info": { "bracket": "1", - "initialLeverage": "11", + "initialLeverage": "50", "notionalCap": "5000", "notionalFloor": "0", - "maintMarginRatio": "0.01", + "maintMarginRatio": "0.0065", "cum": "0.0" } }, @@ -9870,80 +10792,112 @@ "tier": 2.0, "currency": "USDT", "minNotional": 5000.0, - "maxNotional": 25000.0, - "maintenanceMarginRate": 0.025, - "maxLeverage": 10.0, + "maxNotional": 50000.0, + "maintenanceMarginRate": 0.01, + "maxLeverage": 25.0, "info": { "bracket": "2", - "initialLeverage": "10", - "notionalCap": "25000", + "initialLeverage": "25", + "notionalCap": "50000", "notionalFloor": "5000", - "maintMarginRatio": "0.025", - "cum": "75.0" + "maintMarginRatio": "0.01", + "cum": "17.5" } }, { "tier": 3.0, "currency": "USDT", - "minNotional": 25000.0, - "maxNotional": 100000.0, - "maintenanceMarginRate": 0.05, - "maxLeverage": 8.0, + "minNotional": 50000.0, + "maxNotional": 200000.0, + "maintenanceMarginRate": 0.025, + "maxLeverage": 20.0, "info": { "bracket": "3", - "initialLeverage": "8", - "notionalCap": "100000", - "notionalFloor": "25000", - "maintMarginRatio": "0.05", - "cum": "700.0" + "initialLeverage": "20", + "notionalCap": "200000", + "notionalFloor": "50000", + "maintMarginRatio": "0.025", + "cum": "767.5" } }, { "tier": 4.0, "currency": "USDT", - "minNotional": 100000.0, - "maxNotional": 250000.0, - "maintenanceMarginRate": 0.1, - "maxLeverage": 5.0, + "minNotional": 200000.0, + "maxNotional": 400000.0, + "maintenanceMarginRate": 0.05, + "maxLeverage": 10.0, "info": { "bracket": "4", - "initialLeverage": "5", - "notionalCap": "250000", - "notionalFloor": "100000", - "maintMarginRatio": "0.1", - "cum": "5700.0" + "initialLeverage": "10", + "notionalCap": "400000", + "notionalFloor": "200000", + "maintMarginRatio": "0.05", + "cum": "5767.5" } }, { "tier": 5.0, "currency": "USDT", - "minNotional": 250000.0, + "minNotional": 400000.0, "maxNotional": 1000000.0, - "maintenanceMarginRate": 0.125, - "maxLeverage": 2.0, + "maintenanceMarginRate": 0.1, + "maxLeverage": 5.0, "info": { "bracket": "5", - "initialLeverage": "2", + "initialLeverage": "5", "notionalCap": "1000000", - "notionalFloor": "250000", - "maintMarginRatio": "0.125", - "cum": "11950.0" + "notionalFloor": "400000", + "maintMarginRatio": "0.1", + "cum": "25767.5" } }, { "tier": 6.0, "currency": "USDT", "minNotional": 1000000.0, - "maxNotional": 3000000.0, + "maxNotional": 5000000.0, + "maintenanceMarginRate": 0.125, + "maxLeverage": 4.0, + "info": { + "bracket": "6", + "initialLeverage": "4", + "notionalCap": "5000000", + "notionalFloor": "1000000", + "maintMarginRatio": "0.125", + "cum": "50767.5" + } + }, + { + "tier": 7.0, + "currency": "USDT", + "minNotional": 5000000.0, + "maxNotional": 6000000.0, + "maintenanceMarginRate": 0.25, + "maxLeverage": 2.0, + "info": { + "bracket": "7", + "initialLeverage": "2", + "notionalCap": "6000000", + "notionalFloor": "5000000", + "maintMarginRatio": "0.25", + "cum": "675767.5" + } + }, + { + "tier": 8.0, + "currency": "USDT", + "minNotional": 6000000.0, + "maxNotional": 10000000.0, "maintenanceMarginRate": 0.5, "maxLeverage": 1.0, "info": { - "bracket": "6", + "bracket": "8", "initialLeverage": "1", - "notionalCap": "3000000", - "notionalFloor": "1000000", + "notionalCap": "10000000", + "notionalFloor": "6000000", "maintMarginRatio": "0.5", - "cum": "386950.0" + "cum": "2175767.5" } } ], @@ -10453,17 +11407,17 @@ } } ], - "HNT/USDT:USDT": [ + "HIGH/USDT:USDT": [ { "tier": 1.0, "currency": "USDT", "minNotional": 0.0, "maxNotional": 5000.0, "maintenanceMarginRate": 0.02, - "maxLeverage": 11.0, + "maxLeverage": 20.0, "info": { "bracket": "1", - "initialLeverage": "11", + "initialLeverage": "20", "notionalCap": "5000", "notionalFloor": "0", "maintMarginRatio": "0.02", @@ -10476,10 +11430,10 @@ "minNotional": 5000.0, "maxNotional": 25000.0, "maintenanceMarginRate": 0.025, - "maxLeverage": 10.0, + "maxLeverage": 15.0, "info": { "bracket": "2", - "initialLeverage": "10", + "initialLeverage": "15", "notionalCap": "25000", "notionalFloor": "5000", "maintMarginRatio": "0.025", @@ -10492,10 +11446,10 @@ "minNotional": 25000.0, "maxNotional": 100000.0, "maintenanceMarginRate": 0.05, - "maxLeverage": 8.0, + "maxLeverage": 10.0, "info": { "bracket": "3", - "initialLeverage": "8", + "initialLeverage": "10", "notionalCap": "100000", "notionalFloor": "25000", "maintMarginRatio": "0.05", @@ -10534,6 +11488,104 @@ "cum": "11900.0" } }, + { + "tier": 6.0, + "currency": "USDT", + "minNotional": 1000000.0, + "maxNotional": 5000000.0, + "maintenanceMarginRate": 0.5, + "maxLeverage": 1.0, + "info": { + "bracket": "6", + "initialLeverage": "1", + "notionalCap": "5000000", + "notionalFloor": "1000000", + "maintMarginRatio": "0.5", + "cum": "386900.0" + } + } + ], + "HNT/USDT:USDT": [ + { + "tier": 1.0, + "currency": "USDT", + "minNotional": 0.0, + "maxNotional": 5000.0, + "maintenanceMarginRate": 0.02, + "maxLeverage": 15.0, + "info": { + "bracket": "1", + "initialLeverage": "15", + "notionalCap": "5000", + "notionalFloor": "0", + "maintMarginRatio": "0.02", + "cum": "0.0" + } + }, + { + "tier": 2.0, + "currency": "USDT", + "minNotional": 5000.0, + "maxNotional": 25000.0, + "maintenanceMarginRate": 0.025, + "maxLeverage": 10.0, + "info": { + "bracket": "2", + "initialLeverage": "10", + "notionalCap": "25000", + "notionalFloor": "5000", + "maintMarginRatio": "0.025", + "cum": "25.0" + } + }, + { + "tier": 3.0, + "currency": "USDT", + "minNotional": 25000.0, + "maxNotional": 120000.0, + "maintenanceMarginRate": 0.05, + "maxLeverage": 8.0, + "info": { + "bracket": "3", + "initialLeverage": "8", + "notionalCap": "120000", + "notionalFloor": "25000", + "maintMarginRatio": "0.05", + "cum": "650.0" + } + }, + { + "tier": 4.0, + "currency": "USDT", + "minNotional": 120000.0, + "maxNotional": 300000.0, + "maintenanceMarginRate": 0.1, + "maxLeverage": 5.0, + "info": { + "bracket": "4", + "initialLeverage": "5", + "notionalCap": "300000", + "notionalFloor": "120000", + "maintMarginRatio": "0.1", + "cum": "6650.0" + } + }, + { + "tier": 5.0, + "currency": "USDT", + "minNotional": 300000.0, + "maxNotional": 1000000.0, + "maintenanceMarginRate": 0.125, + "maxLeverage": 2.0, + "info": { + "bracket": "5", + "initialLeverage": "2", + "notionalCap": "1000000", + "notionalFloor": "300000", + "maintMarginRatio": "0.125", + "cum": "14150.0" + } + }, { "tier": 6.0, "currency": "USDT", @@ -10547,6 +11599,104 @@ "notionalCap": "3000000", "notionalFloor": "1000000", "maintMarginRatio": "0.5", + "cum": "389150.0" + } + } + ], + "HOOK/USDT:USDT": [ + { + "tier": 1.0, + "currency": "USDT", + "minNotional": 0.0, + "maxNotional": 5000.0, + "maintenanceMarginRate": 0.02, + "maxLeverage": 20.0, + "info": { + "bracket": "1", + "initialLeverage": "20", + "notionalCap": "5000", + "notionalFloor": "0", + "maintMarginRatio": "0.02", + "cum": "0.0" + } + }, + { + "tier": 2.0, + "currency": "USDT", + "minNotional": 5000.0, + "maxNotional": 25000.0, + "maintenanceMarginRate": 0.025, + "maxLeverage": 15.0, + "info": { + "bracket": "2", + "initialLeverage": "15", + "notionalCap": "25000", + "notionalFloor": "5000", + "maintMarginRatio": "0.025", + "cum": "25.0" + } + }, + { + "tier": 3.0, + "currency": "USDT", + "minNotional": 25000.0, + "maxNotional": 100000.0, + "maintenanceMarginRate": 0.05, + "maxLeverage": 10.0, + "info": { + "bracket": "3", + "initialLeverage": "10", + "notionalCap": "100000", + "notionalFloor": "25000", + "maintMarginRatio": "0.05", + "cum": "650.0" + } + }, + { + "tier": 4.0, + "currency": "USDT", + "minNotional": 100000.0, + "maxNotional": 250000.0, + "maintenanceMarginRate": 0.1, + "maxLeverage": 5.0, + "info": { + "bracket": "4", + "initialLeverage": "5", + "notionalCap": "250000", + "notionalFloor": "100000", + "maintMarginRatio": "0.1", + "cum": "5650.0" + } + }, + { + "tier": 5.0, + "currency": "USDT", + "minNotional": 250000.0, + "maxNotional": 1000000.0, + "maintenanceMarginRate": 0.125, + "maxLeverage": 2.0, + "info": { + "bracket": "5", + "initialLeverage": "2", + "notionalCap": "1000000", + "notionalFloor": "250000", + "maintMarginRatio": "0.125", + "cum": "11900.0" + } + }, + { + "tier": 6.0, + "currency": "USDT", + "minNotional": 1000000.0, + "maxNotional": 5000000.0, + "maintenanceMarginRate": 0.5, + "maxLeverage": 1.0, + "info": { + "bracket": "6", + "initialLeverage": "1", + "notionalCap": "5000000", + "notionalFloor": "1000000", + "maintMarginRatio": "0.5", "cum": "386900.0" } } @@ -11538,10 +12688,10 @@ "minNotional": 0.0, "maxNotional": 5000.0, "maintenanceMarginRate": 0.01, - "maxLeverage": 20.0, + "maxLeverage": 25.0, "info": { "bracket": "1", - "initialLeverage": "20", + "initialLeverage": "25", "notionalCap": "5000", "notionalFloor": "0", "maintMarginRatio": "0.01", @@ -11552,80 +12702,96 @@ "tier": 2.0, "currency": "USDT", "minNotional": 5000.0, - "maxNotional": 25000.0, - "maintenanceMarginRate": 0.025, - "maxLeverage": 10.0, + "maxNotional": 10000.0, + "maintenanceMarginRate": 0.02, + "maxLeverage": 20.0, "info": { "bracket": "2", - "initialLeverage": "10", - "notionalCap": "25000", + "initialLeverage": "20", + "notionalCap": "10000", "notionalFloor": "5000", - "maintMarginRatio": "0.025", - "cum": "75.0" + "maintMarginRatio": "0.02", + "cum": "50.0" } }, { "tier": 3.0, "currency": "USDT", + "minNotional": 10000.0, + "maxNotional": 25000.0, + "maintenanceMarginRate": 0.025, + "maxLeverage": 10.0, + "info": { + "bracket": "3", + "initialLeverage": "10", + "notionalCap": "25000", + "notionalFloor": "10000", + "maintMarginRatio": "0.025", + "cum": "100.0" + } + }, + { + "tier": 4.0, + "currency": "USDT", "minNotional": 25000.0, "maxNotional": 100000.0, "maintenanceMarginRate": 0.05, "maxLeverage": 8.0, "info": { - "bracket": "3", + "bracket": "4", "initialLeverage": "8", "notionalCap": "100000", "notionalFloor": "25000", "maintMarginRatio": "0.05", - "cum": "700.0" + "cum": "725.0" } }, { - "tier": 4.0, + "tier": 5.0, "currency": "USDT", "minNotional": 100000.0, "maxNotional": 250000.0, "maintenanceMarginRate": 0.1, "maxLeverage": 5.0, "info": { - "bracket": "4", + "bracket": "5", "initialLeverage": "5", "notionalCap": "250000", "notionalFloor": "100000", "maintMarginRatio": "0.1", - "cum": "5700.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": "11950.0" + "cum": "5725.0" } }, { "tier": 6.0, "currency": "USDT", - "minNotional": 1000000.0, - "maxNotional": 5000000.0, + "minNotional": 250000.0, + "maxNotional": 3000000.0, + "maintenanceMarginRate": 0.125, + "maxLeverage": 2.0, + "info": { + "bracket": "6", + "initialLeverage": "2", + "notionalCap": "3000000", + "notionalFloor": "250000", + "maintMarginRatio": "0.125", + "cum": "11975.0" + } + }, + { + "tier": 7.0, + "currency": "USDT", + "minNotional": 3000000.0, + "maxNotional": 8000000.0, "maintenanceMarginRate": 0.5, "maxLeverage": 1.0, "info": { - "bracket": "6", + "bracket": "7", "initialLeverage": "1", - "notionalCap": "5000000", - "notionalFloor": "1000000", + "notionalCap": "8000000", + "notionalFloor": "3000000", "maintMarginRatio": "0.5", - "cum": "386950.0" + "cum": "1136975.0" } } ], @@ -11914,10 +13080,10 @@ "minNotional": 0.0, "maxNotional": 5000.0, "maintenanceMarginRate": 0.02, - "maxLeverage": 11.0, + "maxLeverage": 20.0, "info": { "bracket": "1", - "initialLeverage": "11", + "initialLeverage": "20", "notionalCap": "5000", "notionalFloor": "0", "maintMarginRatio": "0.02", @@ -12012,10 +13178,10 @@ "minNotional": 0.0, "maxNotional": 5000.0, "maintenanceMarginRate": 0.01, - "maxLeverage": 11.0, + "maxLeverage": 20.0, "info": { "bracket": "1", - "initialLeverage": "11", + "initialLeverage": "20", "notionalCap": "5000", "notionalFloor": "0", "maintMarginRatio": "0.01", @@ -12306,10 +13472,10 @@ "minNotional": 0.0, "maxNotional": 5000.0, "maintenanceMarginRate": 0.02, - "maxLeverage": 20.0, + "maxLeverage": 25.0, "info": { "bracket": "1", - "initialLeverage": "20", + "initialLeverage": "25", "notionalCap": "5000", "notionalFloor": "0", "maintMarginRatio": "0.02", @@ -12322,10 +13488,10 @@ "minNotional": 5000.0, "maxNotional": 25000.0, "maintenanceMarginRate": 0.025, - "maxLeverage": 10.0, + "maxLeverage": 15.0, "info": { "bracket": "2", - "initialLeverage": "10", + "initialLeverage": "15", "notionalCap": "25000", "notionalFloor": "5000", "maintMarginRatio": "0.025", @@ -12338,10 +13504,10 @@ "minNotional": 25000.0, "maxNotional": 100000.0, "maintenanceMarginRate": 0.05, - "maxLeverage": 8.0, + "maxLeverage": 10.0, "info": { "bracket": "3", - "initialLeverage": "8", + "initialLeverage": "10", "notionalCap": "100000", "notionalFloor": "25000", "maintMarginRatio": "0.05", @@ -12368,13 +13534,13 @@ "tier": 5.0, "currency": "BUSD", "minNotional": 250000.0, - "maxNotional": 1000000.0, + "maxNotional": 1500000.0, "maintenanceMarginRate": 0.125, - "maxLeverage": 2.0, + "maxLeverage": 4.0, "info": { "bracket": "5", - "initialLeverage": "2", - "notionalCap": "1000000", + "initialLeverage": "4", + "notionalCap": "1500000", "notionalFloor": "250000", "maintMarginRatio": "0.125", "cum": "11900.0" @@ -12383,17 +13549,33 @@ { "tier": 6.0, "currency": "BUSD", - "minNotional": 1000000.0, - "maxNotional": 5000000.0, + "minNotional": 1500000.0, + "maxNotional": 3000000.0, + "maintenanceMarginRate": 0.25, + "maxLeverage": 2.0, + "info": { + "bracket": "6", + "initialLeverage": "2", + "notionalCap": "3000000", + "notionalFloor": "1500000", + "maintMarginRatio": "0.25", + "cum": "199400.0" + } + }, + { + "tier": 7.0, + "currency": "BUSD", + "minNotional": 3000000.0, + "maxNotional": 8000000.0, "maintenanceMarginRate": 0.5, "maxLeverage": 1.0, "info": { - "bracket": "6", + "bracket": "7", "initialLeverage": "1", - "notionalCap": "5000000", - "notionalFloor": "1000000", + "notionalCap": "8000000", + "notionalFloor": "3000000", "maintMarginRatio": "0.5", - "cum": "386900.0" + "cum": "949400.0" } } ], @@ -12402,144 +13584,160 @@ "tier": 1.0, "currency": "USDT", "minNotional": 0.0, - "maxNotional": 10000.0, - "maintenanceMarginRate": 0.0065, - "maxLeverage": 50.0, + "maxNotional": 5000.0, + "maintenanceMarginRate": 0.005, + "maxLeverage": 75.0, "info": { "bracket": "1", - "initialLeverage": "50", - "notionalCap": "10000", + "initialLeverage": "75", + "notionalCap": "5000", "notionalFloor": "0", - "maintMarginRatio": "0.0065", + "maintMarginRatio": "0.005", "cum": "0.0" } }, { "tier": 2.0, "currency": "USDT", + "minNotional": 5000.0, + "maxNotional": 10000.0, + "maintenanceMarginRate": 0.0065, + "maxLeverage": 50.0, + "info": { + "bracket": "2", + "initialLeverage": "50", + "notionalCap": "10000", + "notionalFloor": "5000", + "maintMarginRatio": "0.0065", + "cum": "7.5" + } + }, + { + "tier": 3.0, + "currency": "USDT", "minNotional": 10000.0, "maxNotional": 50000.0, "maintenanceMarginRate": 0.01, "maxLeverage": 40.0, "info": { - "bracket": "2", + "bracket": "3", "initialLeverage": "40", "notionalCap": "50000", "notionalFloor": "10000", "maintMarginRatio": "0.01", - "cum": "35.0" + "cum": "42.5" } }, { - "tier": 3.0, + "tier": 4.0, "currency": "USDT", "minNotional": 50000.0, "maxNotional": 250000.0, "maintenanceMarginRate": 0.02, "maxLeverage": 25.0, "info": { - "bracket": "3", + "bracket": "4", "initialLeverage": "25", "notionalCap": "250000", "notionalFloor": "50000", "maintMarginRatio": "0.02", - "cum": "535.0" + "cum": "542.5" } }, { - "tier": 4.0, + "tier": 5.0, "currency": "USDT", "minNotional": 250000.0, "maxNotional": 1000000.0, "maintenanceMarginRate": 0.05, "maxLeverage": 10.0, "info": { - "bracket": "4", + "bracket": "5", "initialLeverage": "10", "notionalCap": "1000000", "notionalFloor": "250000", "maintMarginRatio": "0.05", - "cum": "8035.0" + "cum": "8042.5" } }, { - "tier": 5.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": "58035.0" + "cum": "58042.5" } }, { - "tier": 6.0, + "tier": 7.0, "currency": "USDT", "minNotional": 2000000.0, "maxNotional": 5000000.0, "maintenanceMarginRate": 0.125, "maxLeverage": 4.0, "info": { - "bracket": "6", + "bracket": "7", "initialLeverage": "4", "notionalCap": "5000000", "notionalFloor": "2000000", "maintMarginRatio": "0.125", - "cum": "108035.0" + "cum": "108042.5" } }, { - "tier": 7.0, + "tier": 8.0, "currency": "USDT", "minNotional": 5000000.0, "maxNotional": 10000000.0, "maintenanceMarginRate": 0.15, "maxLeverage": 3.0, "info": { - "bracket": "7", + "bracket": "8", "initialLeverage": "3", "notionalCap": "10000000", "notionalFloor": "5000000", "maintMarginRatio": "0.15", - "cum": "233035.0" + "cum": "233042.5" } }, { - "tier": 8.0, + "tier": 9.0, "currency": "USDT", "minNotional": 10000000.0, "maxNotional": 20000000.0, "maintenanceMarginRate": 0.25, "maxLeverage": 2.0, "info": { - "bracket": "8", + "bracket": "9", "initialLeverage": "2", "notionalCap": "20000000", "notionalFloor": "10000000", "maintMarginRatio": "0.25", - "cum": "1233035.0" + "cum": "1233042.5" } }, { - "tier": 9.0, + "tier": 10.0, "currency": "USDT", "minNotional": 20000000.0, "maxNotional": 50000000.0, "maintenanceMarginRate": 0.5, "maxLeverage": 1.0, "info": { - "bracket": "9", + "bracket": "10", "initialLeverage": "1", "notionalCap": "50000000", "notionalFloor": "20000000", "maintMarginRatio": "0.5", - "cum": "6233035.0" + "cum": "6233042.5" } } ], @@ -12828,10 +14026,10 @@ "minNotional": 0.0, "maxNotional": 5000.0, "maintenanceMarginRate": 0.02, - "maxLeverage": 20.0, + "maxLeverage": 25.0, "info": { "bracket": "1", - "initialLeverage": "20", + "initialLeverage": "25", "notionalCap": "5000", "notionalFloor": "0", "maintMarginRatio": "0.02", @@ -12844,10 +14042,10 @@ "minNotional": 5000.0, "maxNotional": 25000.0, "maintenanceMarginRate": 0.025, - "maxLeverage": 10.0, + "maxLeverage": 15.0, "info": { "bracket": "2", - "initialLeverage": "10", + "initialLeverage": "15", "notionalCap": "25000", "notionalFloor": "5000", "maintMarginRatio": "0.025", @@ -12860,10 +14058,10 @@ "minNotional": 25000.0, "maxNotional": 100000.0, "maintenanceMarginRate": 0.05, - "maxLeverage": 8.0, + "maxLeverage": 10.0, "info": { "bracket": "3", - "initialLeverage": "8", + "initialLeverage": "10", "notionalCap": "100000", "notionalFloor": "25000", "maintMarginRatio": "0.05", @@ -12890,13 +14088,13 @@ "tier": 5.0, "currency": "BUSD", "minNotional": 250000.0, - "maxNotional": 1000000.0, + "maxNotional": 1500000.0, "maintenanceMarginRate": 0.125, - "maxLeverage": 2.0, + "maxLeverage": 4.0, "info": { "bracket": "5", - "initialLeverage": "2", - "notionalCap": "1000000", + "initialLeverage": "4", + "notionalCap": "1500000", "notionalFloor": "250000", "maintMarginRatio": "0.125", "cum": "11900.0" @@ -12905,17 +14103,33 @@ { "tier": 6.0, "currency": "BUSD", - "minNotional": 1000000.0, - "maxNotional": 5000000.0, + "minNotional": 1500000.0, + "maxNotional": 3000000.0, + "maintenanceMarginRate": 0.25, + "maxLeverage": 2.0, + "info": { + "bracket": "6", + "initialLeverage": "2", + "notionalCap": "3000000", + "notionalFloor": "1500000", + "maintMarginRatio": "0.25", + "cum": "199400.0" + } + }, + { + "tier": 7.0, + "currency": "BUSD", + "minNotional": 3000000.0, + "maxNotional": 8000000.0, "maintenanceMarginRate": 0.5, "maxLeverage": 1.0, "info": { - "bracket": "6", + "bracket": "7", "initialLeverage": "1", - "notionalCap": "5000000", - "notionalFloor": "1000000", + "notionalCap": "8000000", + "notionalFloor": "3000000", "maintMarginRatio": "0.5", - "cum": "386900.0" + "cum": "949400.0" } } ], @@ -12924,144 +14138,160 @@ "tier": 1.0, "currency": "USDT", "minNotional": 0.0, - "maxNotional": 10000.0, - "maintenanceMarginRate": 0.0065, - "maxLeverage": 25.0, + "maxNotional": 5000.0, + "maintenanceMarginRate": 0.005, + "maxLeverage": 75.0, "info": { "bracket": "1", - "initialLeverage": "25", - "notionalCap": "10000", + "initialLeverage": "75", + "notionalCap": "5000", "notionalFloor": "0", - "maintMarginRatio": "0.0065", + "maintMarginRatio": "0.005", "cum": "0.0" } }, { "tier": 2.0, "currency": "USDT", - "minNotional": 10000.0, - "maxNotional": 50000.0, - "maintenanceMarginRate": 0.01, - "maxLeverage": 20.0, + "minNotional": 5000.0, + "maxNotional": 10000.0, + "maintenanceMarginRate": 0.0065, + "maxLeverage": 50.0, "info": { "bracket": "2", - "initialLeverage": "20", - "notionalCap": "50000", - "notionalFloor": "10000", - "maintMarginRatio": "0.01", - "cum": "35.0" + "initialLeverage": "50", + "notionalCap": "10000", + "notionalFloor": "5000", + "maintMarginRatio": "0.0065", + "cum": "7.5" } }, { "tier": 3.0, "currency": "USDT", - "minNotional": 50000.0, - "maxNotional": 250000.0, - "maintenanceMarginRate": 0.02, - "maxLeverage": 15.0, + "minNotional": 10000.0, + "maxNotional": 50000.0, + "maintenanceMarginRate": 0.01, + "maxLeverage": 40.0, "info": { "bracket": "3", - "initialLeverage": "15", - "notionalCap": "250000", - "notionalFloor": "50000", - "maintMarginRatio": "0.02", - "cum": "535.0" + "initialLeverage": "40", + "notionalCap": "50000", + "notionalFloor": "10000", + "maintMarginRatio": "0.01", + "cum": "42.5" } }, { "tier": 4.0, "currency": "USDT", + "minNotional": 50000.0, + "maxNotional": 250000.0, + "maintenanceMarginRate": 0.02, + "maxLeverage": 25.0, + "info": { + "bracket": "4", + "initialLeverage": "25", + "notionalCap": "250000", + "notionalFloor": "50000", + "maintMarginRatio": "0.02", + "cum": "542.5" + } + }, + { + "tier": 5.0, + "currency": "USDT", "minNotional": 250000.0, "maxNotional": 1000000.0, "maintenanceMarginRate": 0.05, "maxLeverage": 10.0, "info": { - "bracket": "4", + "bracket": "5", "initialLeverage": "10", "notionalCap": "1000000", "notionalFloor": "250000", "maintMarginRatio": "0.05", - "cum": "8035.0" + "cum": "8042.5" } }, { - "tier": 5.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": "58035.0" + "cum": "58042.5" } }, { - "tier": 6.0, + "tier": 7.0, "currency": "USDT", "minNotional": 2000000.0, "maxNotional": 5000000.0, "maintenanceMarginRate": 0.125, "maxLeverage": 4.0, "info": { - "bracket": "6", + "bracket": "7", "initialLeverage": "4", "notionalCap": "5000000", "notionalFloor": "2000000", "maintMarginRatio": "0.125", - "cum": "108035.0" + "cum": "108042.5" } }, { - "tier": 7.0, + "tier": 8.0, "currency": "USDT", "minNotional": 5000000.0, "maxNotional": 10000000.0, "maintenanceMarginRate": 0.15, "maxLeverage": 3.0, "info": { - "bracket": "7", + "bracket": "8", "initialLeverage": "3", "notionalCap": "10000000", "notionalFloor": "5000000", "maintMarginRatio": "0.15", - "cum": "233035.0" + "cum": "233042.5" } }, { - "tier": 8.0, + "tier": 9.0, "currency": "USDT", "minNotional": 10000000.0, "maxNotional": 20000000.0, "maintenanceMarginRate": 0.25, "maxLeverage": 2.0, "info": { - "bracket": "8", + "bracket": "9", "initialLeverage": "2", "notionalCap": "20000000", "notionalFloor": "10000000", "maintMarginRatio": "0.25", - "cum": "1233035.0" + "cum": "1233042.5" } }, { - "tier": 9.0, + "tier": 10.0, "currency": "USDT", "minNotional": 20000000.0, - "maxNotional": 32000000.0, + "maxNotional": 50000000.0, "maintenanceMarginRate": 0.5, "maxLeverage": 1.0, "info": { - "bracket": "9", + "bracket": "10", "initialLeverage": "1", - "notionalCap": "32000000", + "notionalCap": "50000000", "notionalFloor": "20000000", "maintMarginRatio": "0.5", - "cum": "6233035.0" + "cum": "6233042.5" } } ], @@ -13261,207 +14491,11 @@ } } ], - "MANA/USDT:USDT": [ + "MAGIC/USDT:USDT": [ { "tier": 1.0, "currency": "USDT", "minNotional": 0.0, - "maxNotional": 50000.0, - "maintenanceMarginRate": 0.01, - "maxLeverage": 25.0, - "info": { - "bracket": "1", - "initialLeverage": "25", - "notionalCap": "50000", - "notionalFloor": "0", - "maintMarginRatio": "0.01", - "cum": "0.0" - } - }, - { - "tier": 2.0, - "currency": "USDT", - "minNotional": 50000.0, - "maxNotional": 150000.0, - "maintenanceMarginRate": 0.025, - "maxLeverage": 20.0, - "info": { - "bracket": "2", - "initialLeverage": "20", - "notionalCap": "150000", - "notionalFloor": "50000", - "maintMarginRatio": "0.025", - "cum": "750.0" - } - }, - { - "tier": 3.0, - "currency": "USDT", - "minNotional": 150000.0, - "maxNotional": 250000.0, - "maintenanceMarginRate": 0.05, - "maxLeverage": 10.0, - "info": { - "bracket": "3", - "initialLeverage": "10", - "notionalCap": "250000", - "notionalFloor": "150000", - "maintMarginRatio": "0.05", - "cum": "4500.0" - } - }, - { - "tier": 4.0, - "currency": "USDT", - "minNotional": 250000.0, - "maxNotional": 500000.0, - "maintenanceMarginRate": 0.1, - "maxLeverage": 5.0, - "info": { - "bracket": "4", - "initialLeverage": "5", - "notionalCap": "500000", - "notionalFloor": "250000", - "maintMarginRatio": "0.1", - "cum": "17000.0" - } - }, - { - "tier": 5.0, - "currency": "USDT", - "minNotional": 500000.0, - "maxNotional": 1000000.0, - "maintenanceMarginRate": 0.125, - "maxLeverage": 4.0, - "info": { - "bracket": "5", - "initialLeverage": "4", - "notionalCap": "1000000", - "notionalFloor": "500000", - "maintMarginRatio": "0.125", - "cum": "29500.0" - } - }, - { - "tier": 6.0, - "currency": "USDT", - "minNotional": 1000000.0, - "maxNotional": 2000000.0, - "maintenanceMarginRate": 0.25, - "maxLeverage": 2.0, - "info": { - "bracket": "6", - "initialLeverage": "2", - "notionalCap": "2000000", - "notionalFloor": "1000000", - "maintMarginRatio": "0.25", - "cum": "154500.0" - } - }, - { - "tier": 7.0, - "currency": "USDT", - "minNotional": 2000000.0, - "maxNotional": 5000000.0, - "maintenanceMarginRate": 0.5, - "maxLeverage": 1.0, - "info": { - "bracket": "7", - "initialLeverage": "1", - "notionalCap": "5000000", - "notionalFloor": "2000000", - "maintMarginRatio": "0.5", - "cum": "654500.0" - } - } - ], - "MASK/USDT:USDT": [ - { - "tier": 1.0, - "currency": "USDT", - "minNotional": 0.0, - "maxNotional": 25000.0, - "maintenanceMarginRate": 0.025, - "maxLeverage": 20.0, - "info": { - "bracket": "1", - "initialLeverage": "20", - "notionalCap": "25000", - "notionalFloor": "0", - "maintMarginRatio": "0.025", - "cum": "0.0" - } - }, - { - "tier": 2.0, - "currency": "USDT", - "minNotional": 25000.0, - "maxNotional": 100000.0, - "maintenanceMarginRate": 0.05, - "maxLeverage": 10.0, - "info": { - "bracket": "2", - "initialLeverage": "10", - "notionalCap": "100000", - "notionalFloor": "25000", - "maintMarginRatio": "0.05", - "cum": "625.0" - } - }, - { - "tier": 3.0, - "currency": "USDT", - "minNotional": 100000.0, - "maxNotional": 250000.0, - "maintenanceMarginRate": 0.1, - "maxLeverage": 5.0, - "info": { - "bracket": "3", - "initialLeverage": "5", - "notionalCap": "250000", - "notionalFloor": "100000", - "maintMarginRatio": "0.1", - "cum": "5625.0" - } - }, - { - "tier": 4.0, - "currency": "USDT", - "minNotional": 250000.0, - "maxNotional": 1000000.0, - "maintenanceMarginRate": 0.125, - "maxLeverage": 2.0, - "info": { - "bracket": "4", - "initialLeverage": "2", - "notionalCap": "1000000", - "notionalFloor": "250000", - "maintMarginRatio": "0.125", - "cum": "11875.0" - } - }, - { - "tier": 5.0, - "currency": "USDT", - "minNotional": 1000000.0, - "maxNotional": 5000000.0, - "maintenanceMarginRate": 0.5, - "maxLeverage": 1.0, - "info": { - "bracket": "5", - "initialLeverage": "1", - "notionalCap": "5000000", - "notionalFloor": "1000000", - "maintMarginRatio": "0.5", - "cum": "386875.0" - } - } - ], - "MATIC/BUSD:BUSD": [ - { - "tier": 1.0, - "currency": "BUSD", - "minNotional": 0.0, "maxNotional": 5000.0, "maintenanceMarginRate": 0.02, "maxLeverage": 20.0, @@ -13474,16 +14508,358 @@ "cum": "0.0" } }, + { + "tier": 2.0, + "currency": "USDT", + "minNotional": 5000.0, + "maxNotional": 25000.0, + "maintenanceMarginRate": 0.025, + "maxLeverage": 15.0, + "info": { + "bracket": "2", + "initialLeverage": "15", + "notionalCap": "25000", + "notionalFloor": "5000", + "maintMarginRatio": "0.025", + "cum": "25.0" + } + }, + { + "tier": 3.0, + "currency": "USDT", + "minNotional": 25000.0, + "maxNotional": 100000.0, + "maintenanceMarginRate": 0.05, + "maxLeverage": 10.0, + "info": { + "bracket": "3", + "initialLeverage": "10", + "notionalCap": "100000", + "notionalFloor": "25000", + "maintMarginRatio": "0.05", + "cum": "650.0" + } + }, + { + "tier": 4.0, + "currency": "USDT", + "minNotional": 100000.0, + "maxNotional": 250000.0, + "maintenanceMarginRate": 0.1, + "maxLeverage": 5.0, + "info": { + "bracket": "4", + "initialLeverage": "5", + "notionalCap": "250000", + "notionalFloor": "100000", + "maintMarginRatio": "0.1", + "cum": "5650.0" + } + }, + { + "tier": 5.0, + "currency": "USDT", + "minNotional": 250000.0, + "maxNotional": 1000000.0, + "maintenanceMarginRate": 0.125, + "maxLeverage": 2.0, + "info": { + "bracket": "5", + "initialLeverage": "2", + "notionalCap": "1000000", + "notionalFloor": "250000", + "maintMarginRatio": "0.125", + "cum": "11900.0" + } + }, + { + "tier": 6.0, + "currency": "USDT", + "minNotional": 1000000.0, + "maxNotional": 5000000.0, + "maintenanceMarginRate": 0.5, + "maxLeverage": 1.0, + "info": { + "bracket": "6", + "initialLeverage": "1", + "notionalCap": "5000000", + "notionalFloor": "1000000", + "maintMarginRatio": "0.5", + "cum": "386900.0" + } + } + ], + "MANA/USDT:USDT": [ + { + "tier": 1.0, + "currency": "USDT", + "minNotional": 0.0, + "maxNotional": 5000.0, + "maintenanceMarginRate": 0.0065, + "maxLeverage": 50.0, + "info": { + "bracket": "1", + "initialLeverage": "50", + "notionalCap": "5000", + "notionalFloor": "0", + "maintMarginRatio": "0.0065", + "cum": "0.0" + } + }, + { + "tier": 2.0, + "currency": "USDT", + "minNotional": 5000.0, + "maxNotional": 25000.0, + "maintenanceMarginRate": 0.0075, + "maxLeverage": 40.0, + "info": { + "bracket": "2", + "initialLeverage": "40", + "notionalCap": "25000", + "notionalFloor": "5000", + "maintMarginRatio": "0.0075", + "cum": "5.0" + } + }, + { + "tier": 3.0, + "currency": "USDT", + "minNotional": 25000.0, + "maxNotional": 50000.0, + "maintenanceMarginRate": 0.01, + "maxLeverage": 25.0, + "info": { + "bracket": "3", + "initialLeverage": "25", + "notionalCap": "50000", + "notionalFloor": "25000", + "maintMarginRatio": "0.01", + "cum": "67.5" + } + }, + { + "tier": 4.0, + "currency": "USDT", + "minNotional": 50000.0, + "maxNotional": 150000.0, + "maintenanceMarginRate": 0.025, + "maxLeverage": 20.0, + "info": { + "bracket": "4", + "initialLeverage": "20", + "notionalCap": "150000", + "notionalFloor": "50000", + "maintMarginRatio": "0.025", + "cum": "817.5" + } + }, + { + "tier": 5.0, + "currency": "USDT", + "minNotional": 150000.0, + "maxNotional": 250000.0, + "maintenanceMarginRate": 0.05, + "maxLeverage": 10.0, + "info": { + "bracket": "5", + "initialLeverage": "10", + "notionalCap": "250000", + "notionalFloor": "150000", + "maintMarginRatio": "0.05", + "cum": "4567.5" + } + }, + { + "tier": 6.0, + "currency": "USDT", + "minNotional": 250000.0, + "maxNotional": 500000.0, + "maintenanceMarginRate": 0.1, + "maxLeverage": 5.0, + "info": { + "bracket": "6", + "initialLeverage": "5", + "notionalCap": "500000", + "notionalFloor": "250000", + "maintMarginRatio": "0.1", + "cum": "17067.5" + } + }, + { + "tier": 7.0, + "currency": "USDT", + "minNotional": 500000.0, + "maxNotional": 1000000.0, + "maintenanceMarginRate": 0.125, + "maxLeverage": 4.0, + "info": { + "bracket": "7", + "initialLeverage": "4", + "notionalCap": "1000000", + "notionalFloor": "500000", + "maintMarginRatio": "0.125", + "cum": "29567.5" + } + }, + { + "tier": 8.0, + "currency": "USDT", + "minNotional": 1000000.0, + "maxNotional": 5000000.0, + "maintenanceMarginRate": 0.25, + "maxLeverage": 2.0, + "info": { + "bracket": "8", + "initialLeverage": "2", + "notionalCap": "5000000", + "notionalFloor": "1000000", + "maintMarginRatio": "0.25", + "cum": "154567.5" + } + }, + { + "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": "1404567.5" + } + } + ], + "MASK/USDT:USDT": [ + { + "tier": 1.0, + "currency": "USDT", + "minNotional": 0.0, + "maxNotional": 5000.0, + "maintenanceMarginRate": 0.02, + "maxLeverage": 25.0, + "info": { + "bracket": "1", + "initialLeverage": "25", + "notionalCap": "5000", + "notionalFloor": "0", + "maintMarginRatio": "0.02", + "cum": "0.0" + } + }, + { + "tier": 2.0, + "currency": "USDT", + "minNotional": 5000.0, + "maxNotional": 25000.0, + "maintenanceMarginRate": 0.025, + "maxLeverage": 20.0, + "info": { + "bracket": "2", + "initialLeverage": "20", + "notionalCap": "25000", + "notionalFloor": "5000", + "maintMarginRatio": "0.025", + "cum": "25.0" + } + }, + { + "tier": 3.0, + "currency": "USDT", + "minNotional": 25000.0, + "maxNotional": 100000.0, + "maintenanceMarginRate": 0.05, + "maxLeverage": 10.0, + "info": { + "bracket": "3", + "initialLeverage": "10", + "notionalCap": "100000", + "notionalFloor": "25000", + "maintMarginRatio": "0.05", + "cum": "650.0" + } + }, + { + "tier": 4.0, + "currency": "USDT", + "minNotional": 100000.0, + "maxNotional": 250000.0, + "maintenanceMarginRate": 0.1, + "maxLeverage": 5.0, + "info": { + "bracket": "4", + "initialLeverage": "5", + "notionalCap": "250000", + "notionalFloor": "100000", + "maintMarginRatio": "0.1", + "cum": "5650.0" + } + }, + { + "tier": 5.0, + "currency": "USDT", + "minNotional": 250000.0, + "maxNotional": 3000000.0, + "maintenanceMarginRate": 0.125, + "maxLeverage": 2.0, + "info": { + "bracket": "5", + "initialLeverage": "2", + "notionalCap": "3000000", + "notionalFloor": "250000", + "maintMarginRatio": "0.125", + "cum": "11900.0" + } + }, + { + "tier": 6.0, + "currency": "USDT", + "minNotional": 3000000.0, + "maxNotional": 8000000.0, + "maintenanceMarginRate": 0.5, + "maxLeverage": 1.0, + "info": { + "bracket": "6", + "initialLeverage": "1", + "notionalCap": "8000000", + "notionalFloor": "3000000", + "maintMarginRatio": "0.5", + "cum": "1136900.0" + } + } + ], + "MATIC/BUSD:BUSD": [ + { + "tier": 1.0, + "currency": "BUSD", + "minNotional": 0.0, + "maxNotional": 5000.0, + "maintenanceMarginRate": 0.02, + "maxLeverage": 25.0, + "info": { + "bracket": "1", + "initialLeverage": "25", + "notionalCap": "5000", + "notionalFloor": "0", + "maintMarginRatio": "0.02", + "cum": "0.0" + } + }, { "tier": 2.0, "currency": "BUSD", "minNotional": 5000.0, "maxNotional": 25000.0, "maintenanceMarginRate": 0.025, - "maxLeverage": 10.0, + "maxLeverage": 15.0, "info": { "bracket": "2", - "initialLeverage": "10", + "initialLeverage": "15", "notionalCap": "25000", "notionalFloor": "5000", "maintMarginRatio": "0.025", @@ -13496,10 +14872,10 @@ "minNotional": 25000.0, "maxNotional": 100000.0, "maintenanceMarginRate": 0.05, - "maxLeverage": 8.0, + "maxLeverage": 10.0, "info": { "bracket": "3", - "initialLeverage": "8", + "initialLeverage": "10", "notionalCap": "100000", "notionalFloor": "25000", "maintMarginRatio": "0.05", @@ -13526,6 +14902,266 @@ "tier": 5.0, "currency": "BUSD", "minNotional": 250000.0, + "maxNotional": 1500000.0, + "maintenanceMarginRate": 0.125, + "maxLeverage": 4.0, + "info": { + "bracket": "5", + "initialLeverage": "4", + "notionalCap": "1500000", + "notionalFloor": "250000", + "maintMarginRatio": "0.125", + "cum": "11900.0" + } + }, + { + "tier": 6.0, + "currency": "BUSD", + "minNotional": 1500000.0, + "maxNotional": 3000000.0, + "maintenanceMarginRate": 0.25, + "maxLeverage": 2.0, + "info": { + "bracket": "6", + "initialLeverage": "2", + "notionalCap": "3000000", + "notionalFloor": "1500000", + "maintMarginRatio": "0.25", + "cum": "199400.0" + } + }, + { + "tier": 7.0, + "currency": "BUSD", + "minNotional": 3000000.0, + "maxNotional": 8000000.0, + "maintenanceMarginRate": 0.5, + "maxLeverage": 1.0, + "info": { + "bracket": "7", + "initialLeverage": "1", + "notionalCap": "8000000", + "notionalFloor": "3000000", + "maintMarginRatio": "0.5", + "cum": "949400.0" + } + } + ], + "MATIC/USDT:USDT": [ + { + "tier": 1.0, + "currency": "USDT", + "minNotional": 0.0, + "maxNotional": 5000.0, + "maintenanceMarginRate": 0.0065, + "maxLeverage": 50.0, + "info": { + "bracket": "1", + "initialLeverage": "50", + "notionalCap": "5000", + "notionalFloor": "0", + "maintMarginRatio": "0.0065", + "cum": "0.0" + } + }, + { + "tier": 2.0, + "currency": "USDT", + "minNotional": 5000.0, + "maxNotional": 25000.0, + "maintenanceMarginRate": 0.0075, + "maxLeverage": 40.0, + "info": { + "bracket": "2", + "initialLeverage": "40", + "notionalCap": "25000", + "notionalFloor": "5000", + "maintMarginRatio": "0.0075", + "cum": "5.0" + } + }, + { + "tier": 3.0, + "currency": "USDT", + "minNotional": 25000.0, + "maxNotional": 50000.0, + "maintenanceMarginRate": 0.01, + "maxLeverage": 25.0, + "info": { + "bracket": "3", + "initialLeverage": "25", + "notionalCap": "50000", + "notionalFloor": "25000", + "maintMarginRatio": "0.01", + "cum": "67.5" + } + }, + { + "tier": 4.0, + "currency": "USDT", + "minNotional": 50000.0, + "maxNotional": 150000.0, + "maintenanceMarginRate": 0.025, + "maxLeverage": 20.0, + "info": { + "bracket": "4", + "initialLeverage": "20", + "notionalCap": "150000", + "notionalFloor": "50000", + "maintMarginRatio": "0.025", + "cum": "817.5" + } + }, + { + "tier": 5.0, + "currency": "USDT", + "minNotional": 150000.0, + "maxNotional": 250000.0, + "maintenanceMarginRate": 0.05, + "maxLeverage": 10.0, + "info": { + "bracket": "5", + "initialLeverage": "10", + "notionalCap": "250000", + "notionalFloor": "150000", + "maintMarginRatio": "0.05", + "cum": "4567.5" + } + }, + { + "tier": 6.0, + "currency": "USDT", + "minNotional": 250000.0, + "maxNotional": 500000.0, + "maintenanceMarginRate": 0.1, + "maxLeverage": 5.0, + "info": { + "bracket": "6", + "initialLeverage": "5", + "notionalCap": "500000", + "notionalFloor": "250000", + "maintMarginRatio": "0.1", + "cum": "17067.5" + } + }, + { + "tier": 7.0, + "currency": "USDT", + "minNotional": 500000.0, + "maxNotional": 750000.0, + "maintenanceMarginRate": 0.125, + "maxLeverage": 4.0, + "info": { + "bracket": "7", + "initialLeverage": "4", + "notionalCap": "750000", + "notionalFloor": "500000", + "maintMarginRatio": "0.125", + "cum": "29567.5" + } + }, + { + "tier": 8.0, + "currency": "USDT", + "minNotional": 750000.0, + "maxNotional": 3000000.0, + "maintenanceMarginRate": 0.25, + "maxLeverage": 2.0, + "info": { + "bracket": "8", + "initialLeverage": "2", + "notionalCap": "3000000", + "notionalFloor": "750000", + "maintMarginRatio": "0.25", + "cum": "123317.5" + } + }, + { + "tier": 9.0, + "currency": "USDT", + "minNotional": 3000000.0, + "maxNotional": 10000000.0, + "maintenanceMarginRate": 0.5, + "maxLeverage": 1.0, + "info": { + "bracket": "9", + "initialLeverage": "1", + "notionalCap": "10000000", + "notionalFloor": "3000000", + "maintMarginRatio": "0.5", + "cum": "873317.5" + } + } + ], + "MINA/USDT:USDT": [ + { + "tier": 1.0, + "currency": "USDT", + "minNotional": 0.0, + "maxNotional": 5000.0, + "maintenanceMarginRate": 0.02, + "maxLeverage": 20.0, + "info": { + "bracket": "1", + "initialLeverage": "20", + "notionalCap": "5000", + "notionalFloor": "0", + "maintMarginRatio": "0.02", + "cum": "0.0" + } + }, + { + "tier": 2.0, + "currency": "USDT", + "minNotional": 5000.0, + "maxNotional": 25000.0, + "maintenanceMarginRate": 0.025, + "maxLeverage": 15.0, + "info": { + "bracket": "2", + "initialLeverage": "15", + "notionalCap": "25000", + "notionalFloor": "5000", + "maintMarginRatio": "0.025", + "cum": "25.0" + } + }, + { + "tier": 3.0, + "currency": "USDT", + "minNotional": 25000.0, + "maxNotional": 100000.0, + "maintenanceMarginRate": 0.05, + "maxLeverage": 10.0, + "info": { + "bracket": "3", + "initialLeverage": "10", + "notionalCap": "100000", + "notionalFloor": "25000", + "maintMarginRatio": "0.05", + "cum": "650.0" + } + }, + { + "tier": 4.0, + "currency": "USDT", + "minNotional": 100000.0, + "maxNotional": 250000.0, + "maintenanceMarginRate": 0.1, + "maxLeverage": 5.0, + "info": { + "bracket": "4", + "initialLeverage": "5", + "notionalCap": "250000", + "notionalFloor": "100000", + "maintMarginRatio": "0.1", + "cum": "5650.0" + } + }, + { + "tier": 5.0, + "currency": "USDT", + "minNotional": 250000.0, "maxNotional": 1000000.0, "maintenanceMarginRate": 0.125, "maxLeverage": 2.0, @@ -13540,7 +15176,7 @@ }, { "tier": 6.0, - "currency": "BUSD", + "currency": "USDT", "minNotional": 1000000.0, "maxNotional": 5000000.0, "maintenanceMarginRate": 0.5, @@ -13555,120 +15191,6 @@ } } ], - "MATIC/USDT:USDT": [ - { - "tier": 1.0, - "currency": "USDT", - "minNotional": 0.0, - "maxNotional": 50000.0, - "maintenanceMarginRate": 0.01, - "maxLeverage": 25.0, - "info": { - "bracket": "1", - "initialLeverage": "25", - "notionalCap": "50000", - "notionalFloor": "0", - "maintMarginRatio": "0.01", - "cum": "0.0" - } - }, - { - "tier": 2.0, - "currency": "USDT", - "minNotional": 50000.0, - "maxNotional": 150000.0, - "maintenanceMarginRate": 0.025, - "maxLeverage": 20.0, - "info": { - "bracket": "2", - "initialLeverage": "20", - "notionalCap": "150000", - "notionalFloor": "50000", - "maintMarginRatio": "0.025", - "cum": "750.0" - } - }, - { - "tier": 3.0, - "currency": "USDT", - "minNotional": 150000.0, - "maxNotional": 250000.0, - "maintenanceMarginRate": 0.05, - "maxLeverage": 10.0, - "info": { - "bracket": "3", - "initialLeverage": "10", - "notionalCap": "250000", - "notionalFloor": "150000", - "maintMarginRatio": "0.05", - "cum": "4500.0" - } - }, - { - "tier": 4.0, - "currency": "USDT", - "minNotional": 250000.0, - "maxNotional": 500000.0, - "maintenanceMarginRate": 0.1, - "maxLeverage": 5.0, - "info": { - "bracket": "4", - "initialLeverage": "5", - "notionalCap": "500000", - "notionalFloor": "250000", - "maintMarginRatio": "0.1", - "cum": "17000.0" - } - }, - { - "tier": 5.0, - "currency": "USDT", - "minNotional": 500000.0, - "maxNotional": 750000.0, - "maintenanceMarginRate": 0.125, - "maxLeverage": 4.0, - "info": { - "bracket": "5", - "initialLeverage": "4", - "notionalCap": "750000", - "notionalFloor": "500000", - "maintMarginRatio": "0.125", - "cum": "29500.0" - } - }, - { - "tier": 6.0, - "currency": "USDT", - "minNotional": 750000.0, - "maxNotional": 1000000.0, - "maintenanceMarginRate": 0.25, - "maxLeverage": 2.0, - "info": { - "bracket": "6", - "initialLeverage": "2", - "notionalCap": "1000000", - "notionalFloor": "750000", - "maintMarginRatio": "0.25", - "cum": "123250.0" - } - }, - { - "tier": 7.0, - "currency": "USDT", - "minNotional": 1000000.0, - "maxNotional": 8000000.0, - "maintenanceMarginRate": 0.5, - "maxLeverage": 1.0, - "info": { - "bracket": "7", - "initialLeverage": "1", - "notionalCap": "8000000", - "notionalFloor": "1000000", - "maintMarginRatio": "0.5", - "cum": "373250.0" - } - } - ], "MKR/USDT:USDT": [ { "tier": 1.0, @@ -14280,10 +15802,10 @@ "minNotional": 0.0, "maxNotional": 5000.0, "maintenanceMarginRate": 0.02, - "maxLeverage": 21.0, + "maxLeverage": 25.0, "info": { "bracket": "1", - "initialLeverage": "21", + "initialLeverage": "25", "notionalCap": "5000", "notionalFloor": "0", "maintMarginRatio": "0.02", @@ -14310,13 +15832,13 @@ "tier": 3.0, "currency": "USDT", "minNotional": 25000.0, - "maxNotional": 100000.0, + "maxNotional": 200000.0, "maintenanceMarginRate": 0.05, "maxLeverage": 10.0, "info": { "bracket": "3", "initialLeverage": "10", - "notionalCap": "100000", + "notionalCap": "200000", "notionalFloor": "25000", "maintMarginRatio": "0.05", "cum": "650.0" @@ -14325,23 +15847,23 @@ { "tier": 4.0, "currency": "USDT", - "minNotional": 100000.0, - "maxNotional": 250000.0, + "minNotional": 200000.0, + "maxNotional": 500000.0, "maintenanceMarginRate": 0.1, "maxLeverage": 5.0, "info": { "bracket": "4", "initialLeverage": "5", - "notionalCap": "250000", - "notionalFloor": "100000", + "notionalCap": "500000", + "notionalFloor": "200000", "maintMarginRatio": "0.1", - "cum": "5650.0" + "cum": "10650.0" } }, { "tier": 5.0, "currency": "USDT", - "minNotional": 250000.0, + "minNotional": 500000.0, "maxNotional": 1000000.0, "maintenanceMarginRate": 0.125, "maxLeverage": 2.0, @@ -14349,25 +15871,25 @@ "bracket": "5", "initialLeverage": "2", "notionalCap": "1000000", - "notionalFloor": "250000", + "notionalFloor": "500000", "maintMarginRatio": "0.125", - "cum": "11900.0" + "cum": "23150.0" } }, { "tier": 6.0, "currency": "USDT", "minNotional": 1000000.0, - "maxNotional": 3000000.0, + "maxNotional": 5000000.0, "maintenanceMarginRate": 0.5, "maxLeverage": 1.0, "info": { "bracket": "6", "initialLeverage": "1", - "notionalCap": "3000000", + "notionalCap": "5000000", "notionalFloor": "1000000", "maintMarginRatio": "0.5", - "cum": "386900.0" + "cum": "398150.0" } } ], @@ -14769,14 +16291,14 @@ "currency": "USDT", "minNotional": 0.0, "maxNotional": 5000.0, - "maintenanceMarginRate": 0.01, - "maxLeverage": 20.0, + "maintenanceMarginRate": 0.0065, + "maxLeverage": 50.0, "info": { "bracket": "1", - "initialLeverage": "20", + "initialLeverage": "50", "notionalCap": "5000", "notionalFloor": "0", - "maintMarginRatio": "0.01", + "maintMarginRatio": "0.0065", "cum": "0.0" } }, @@ -14784,80 +16306,112 @@ "tier": 2.0, "currency": "USDT", "minNotional": 5000.0, - "maxNotional": 25000.0, - "maintenanceMarginRate": 0.025, - "maxLeverage": 10.0, + "maxNotional": 50000.0, + "maintenanceMarginRate": 0.01, + "maxLeverage": 25.0, "info": { "bracket": "2", - "initialLeverage": "10", - "notionalCap": "25000", + "initialLeverage": "25", + "notionalCap": "50000", "notionalFloor": "5000", - "maintMarginRatio": "0.025", - "cum": "75.0" + "maintMarginRatio": "0.01", + "cum": "17.5" } }, { "tier": 3.0, "currency": "USDT", - "minNotional": 25000.0, - "maxNotional": 100000.0, - "maintenanceMarginRate": 0.05, - "maxLeverage": 8.0, + "minNotional": 50000.0, + "maxNotional": 200000.0, + "maintenanceMarginRate": 0.025, + "maxLeverage": 20.0, "info": { "bracket": "3", - "initialLeverage": "8", - "notionalCap": "100000", - "notionalFloor": "25000", - "maintMarginRatio": "0.05", - "cum": "700.0" + "initialLeverage": "20", + "notionalCap": "200000", + "notionalFloor": "50000", + "maintMarginRatio": "0.025", + "cum": "767.5" } }, { "tier": 4.0, "currency": "USDT", - "minNotional": 100000.0, - "maxNotional": 250000.0, - "maintenanceMarginRate": 0.1, - "maxLeverage": 5.0, + "minNotional": 200000.0, + "maxNotional": 400000.0, + "maintenanceMarginRate": 0.05, + "maxLeverage": 10.0, "info": { "bracket": "4", - "initialLeverage": "5", - "notionalCap": "250000", - "notionalFloor": "100000", - "maintMarginRatio": "0.1", - "cum": "5700.0" + "initialLeverage": "10", + "notionalCap": "400000", + "notionalFloor": "200000", + "maintMarginRatio": "0.05", + "cum": "5767.5" } }, { "tier": 5.0, "currency": "USDT", - "minNotional": 250000.0, + "minNotional": 400000.0, "maxNotional": 1000000.0, - "maintenanceMarginRate": 0.125, - "maxLeverage": 2.0, + "maintenanceMarginRate": 0.1, + "maxLeverage": 5.0, "info": { "bracket": "5", - "initialLeverage": "2", + "initialLeverage": "5", "notionalCap": "1000000", - "notionalFloor": "250000", - "maintMarginRatio": "0.125", - "cum": "11950.0" + "notionalFloor": "400000", + "maintMarginRatio": "0.1", + "cum": "25767.5" } }, { "tier": 6.0, "currency": "USDT", "minNotional": 1000000.0, - "maxNotional": 8000000.0, + "maxNotional": 5000000.0, + "maintenanceMarginRate": 0.125, + "maxLeverage": 4.0, + "info": { + "bracket": "6", + "initialLeverage": "4", + "notionalCap": "5000000", + "notionalFloor": "1000000", + "maintMarginRatio": "0.125", + "cum": "50767.5" + } + }, + { + "tier": 7.0, + "currency": "USDT", + "minNotional": 5000000.0, + "maxNotional": 6000000.0, + "maintenanceMarginRate": 0.25, + "maxLeverage": 2.0, + "info": { + "bracket": "7", + "initialLeverage": "2", + "notionalCap": "6000000", + "notionalFloor": "5000000", + "maintMarginRatio": "0.25", + "cum": "675767.5" + } + }, + { + "tier": 8.0, + "currency": "USDT", + "minNotional": 6000000.0, + "maxNotional": 10000000.0, "maintenanceMarginRate": 0.5, "maxLeverage": 1.0, "info": { - "bracket": "6", + "bracket": "8", "initialLeverage": "1", - "notionalCap": "8000000", - "notionalFloor": "1000000", + "notionalCap": "10000000", + "notionalFloor": "6000000", "maintMarginRatio": "0.5", - "cum": "386950.0" + "cum": "2175767.5" } } ], @@ -15057,6 +16611,104 @@ } } ], + "PHB/USDT:USDT": [ + { + "tier": 1.0, + "currency": "USDT", + "minNotional": 0.0, + "maxNotional": 5000.0, + "maintenanceMarginRate": 0.02, + "maxLeverage": 20.0, + "info": { + "bracket": "1", + "initialLeverage": "20", + "notionalCap": "5000", + "notionalFloor": "0", + "maintMarginRatio": "0.02", + "cum": "0.0" + } + }, + { + "tier": 2.0, + "currency": "USDT", + "minNotional": 5000.0, + "maxNotional": 25000.0, + "maintenanceMarginRate": 0.025, + "maxLeverage": 15.0, + "info": { + "bracket": "2", + "initialLeverage": "15", + "notionalCap": "25000", + "notionalFloor": "5000", + "maintMarginRatio": "0.025", + "cum": "25.0" + } + }, + { + "tier": 3.0, + "currency": "USDT", + "minNotional": 25000.0, + "maxNotional": 100000.0, + "maintenanceMarginRate": 0.05, + "maxLeverage": 10.0, + "info": { + "bracket": "3", + "initialLeverage": "10", + "notionalCap": "100000", + "notionalFloor": "25000", + "maintMarginRatio": "0.05", + "cum": "650.0" + } + }, + { + "tier": 4.0, + "currency": "USDT", + "minNotional": 100000.0, + "maxNotional": 250000.0, + "maintenanceMarginRate": 0.1, + "maxLeverage": 5.0, + "info": { + "bracket": "4", + "initialLeverage": "5", + "notionalCap": "250000", + "notionalFloor": "100000", + "maintMarginRatio": "0.1", + "cum": "5650.0" + } + }, + { + "tier": 5.0, + "currency": "USDT", + "minNotional": 250000.0, + "maxNotional": 1000000.0, + "maintenanceMarginRate": 0.125, + "maxLeverage": 2.0, + "info": { + "bracket": "5", + "initialLeverage": "2", + "notionalCap": "1000000", + "notionalFloor": "250000", + "maintMarginRatio": "0.125", + "cum": "11900.0" + } + }, + { + "tier": 6.0, + "currency": "USDT", + "minNotional": 1000000.0, + "maxNotional": 5000000.0, + "maintenanceMarginRate": 0.5, + "maxLeverage": 1.0, + "info": { + "bracket": "6", + "initialLeverage": "1", + "notionalCap": "5000000", + "notionalFloor": "1000000", + "maintMarginRatio": "0.5", + "cum": "386900.0" + } + } + ], "QNT/USDT:USDT": [ { "tier": 1.0, @@ -15456,10 +17108,10 @@ "minNotional": 0.0, "maxNotional": 5000.0, "maintenanceMarginRate": 0.02, - "maxLeverage": 10.0, + "maxLeverage": 15.0, "info": { "bracket": "1", - "initialLeverage": "10", + "initialLeverage": "15", "notionalCap": "5000", "notionalFloor": "0", "maintMarginRatio": "0.02", @@ -15472,10 +17124,10 @@ "minNotional": 5000.0, "maxNotional": 25000.0, "maintenanceMarginRate": 0.025, - "maxLeverage": 8.0, + "maxLeverage": 10.0, "info": { "bracket": "2", - "initialLeverage": "8", + "initialLeverage": "10", "notionalCap": "25000", "notionalFloor": "5000", "maintMarginRatio": "0.025", @@ -15488,10 +17140,10 @@ "minNotional": 25000.0, "maxNotional": 100000.0, "maintenanceMarginRate": 0.05, - "maxLeverage": 6.0, + "maxLeverage": 8.0, "info": { "bracket": "3", - "initialLeverage": "6", + "initialLeverage": "8", "notionalCap": "100000", "notionalFloor": "25000", "maintMarginRatio": "0.05", @@ -15629,6 +17281,104 @@ } } ], + "RNDR/USDT:USDT": [ + { + "tier": 1.0, + "currency": "USDT", + "minNotional": 0.0, + "maxNotional": 5000.0, + "maintenanceMarginRate": 0.02, + "maxLeverage": 20.0, + "info": { + "bracket": "1", + "initialLeverage": "20", + "notionalCap": "5000", + "notionalFloor": "0", + "maintMarginRatio": "0.02", + "cum": "0.0" + } + }, + { + "tier": 2.0, + "currency": "USDT", + "minNotional": 5000.0, + "maxNotional": 25000.0, + "maintenanceMarginRate": 0.025, + "maxLeverage": 15.0, + "info": { + "bracket": "2", + "initialLeverage": "15", + "notionalCap": "25000", + "notionalFloor": "5000", + "maintMarginRatio": "0.025", + "cum": "25.0" + } + }, + { + "tier": 3.0, + "currency": "USDT", + "minNotional": 25000.0, + "maxNotional": 100000.0, + "maintenanceMarginRate": 0.05, + "maxLeverage": 10.0, + "info": { + "bracket": "3", + "initialLeverage": "10", + "notionalCap": "100000", + "notionalFloor": "25000", + "maintMarginRatio": "0.05", + "cum": "650.0" + } + }, + { + "tier": 4.0, + "currency": "USDT", + "minNotional": 100000.0, + "maxNotional": 250000.0, + "maintenanceMarginRate": 0.1, + "maxLeverage": 5.0, + "info": { + "bracket": "4", + "initialLeverage": "5", + "notionalCap": "250000", + "notionalFloor": "100000", + "maintMarginRatio": "0.1", + "cum": "5650.0" + } + }, + { + "tier": 5.0, + "currency": "USDT", + "minNotional": 250000.0, + "maxNotional": 1000000.0, + "maintenanceMarginRate": 0.125, + "maxLeverage": 2.0, + "info": { + "bracket": "5", + "initialLeverage": "2", + "notionalCap": "1000000", + "notionalFloor": "250000", + "maintMarginRatio": "0.125", + "cum": "11900.0" + } + }, + { + "tier": 6.0, + "currency": "USDT", + "minNotional": 1000000.0, + "maxNotional": 5000000.0, + "maintenanceMarginRate": 0.5, + "maxLeverage": 1.0, + "info": { + "bracket": "6", + "initialLeverage": "1", + "notionalCap": "5000000", + "notionalFloor": "1000000", + "maintMarginRatio": "0.5", + "cum": "386900.0" + } + } + ], "ROSE/USDT:USDT": [ { "tier": 1.0, @@ -16124,112 +17874,144 @@ "tier": 1.0, "currency": "USDT", "minNotional": 0.0, - "maxNotional": 50000.0, - "maintenanceMarginRate": 0.01, - "maxLeverage": 25.0, + "maxNotional": 5000.0, + "maintenanceMarginRate": 0.0065, + "maxLeverage": 50.0, "info": { "bracket": "1", - "initialLeverage": "25", - "notionalCap": "50000", + "initialLeverage": "50", + "notionalCap": "5000", "notionalFloor": "0", - "maintMarginRatio": "0.01", + "maintMarginRatio": "0.0065", "cum": "0.0" } }, { "tier": 2.0, "currency": "USDT", + "minNotional": 5000.0, + "maxNotional": 25000.0, + "maintenanceMarginRate": 0.0075, + "maxLeverage": 40.0, + "info": { + "bracket": "2", + "initialLeverage": "40", + "notionalCap": "25000", + "notionalFloor": "5000", + "maintMarginRatio": "0.0075", + "cum": "5.0" + } + }, + { + "tier": 3.0, + "currency": "USDT", + "minNotional": 25000.0, + "maxNotional": 50000.0, + "maintenanceMarginRate": 0.01, + "maxLeverage": 25.0, + "info": { + "bracket": "3", + "initialLeverage": "25", + "notionalCap": "50000", + "notionalFloor": "25000", + "maintMarginRatio": "0.01", + "cum": "67.5" + } + }, + { + "tier": 4.0, + "currency": "USDT", "minNotional": 50000.0, "maxNotional": 150000.0, "maintenanceMarginRate": 0.025, "maxLeverage": 20.0, "info": { - "bracket": "2", + "bracket": "4", "initialLeverage": "20", "notionalCap": "150000", "notionalFloor": "50000", "maintMarginRatio": "0.025", - "cum": "750.0" + "cum": "817.5" } }, { - "tier": 3.0, + "tier": 5.0, "currency": "USDT", "minNotional": 150000.0, "maxNotional": 250000.0, "maintenanceMarginRate": 0.05, "maxLeverage": 10.0, "info": { - "bracket": "3", + "bracket": "5", "initialLeverage": "10", "notionalCap": "250000", "notionalFloor": "150000", "maintMarginRatio": "0.05", - "cum": "4500.0" + "cum": "4567.5" } }, { - "tier": 4.0, + "tier": 6.0, "currency": "USDT", "minNotional": 250000.0, "maxNotional": 500000.0, "maintenanceMarginRate": 0.1, "maxLeverage": 5.0, "info": { - "bracket": "4", + "bracket": "6", "initialLeverage": "5", "notionalCap": "500000", "notionalFloor": "250000", "maintMarginRatio": "0.1", - "cum": "17000.0" + "cum": "17067.5" } }, { - "tier": 5.0, + "tier": 7.0, "currency": "USDT", "minNotional": 500000.0, "maxNotional": 1000000.0, "maintenanceMarginRate": 0.125, "maxLeverage": 4.0, "info": { - "bracket": "5", + "bracket": "7", "initialLeverage": "4", "notionalCap": "1000000", "notionalFloor": "500000", "maintMarginRatio": "0.125", - "cum": "29500.0" + "cum": "29567.5" } }, { - "tier": 6.0, + "tier": 8.0, "currency": "USDT", "minNotional": 1000000.0, - "maxNotional": 2000000.0, + "maxNotional": 5000000.0, "maintenanceMarginRate": 0.25, "maxLeverage": 2.0, "info": { - "bracket": "6", + "bracket": "8", "initialLeverage": "2", - "notionalCap": "2000000", + "notionalCap": "5000000", "notionalFloor": "1000000", "maintMarginRatio": "0.25", - "cum": "154500.0" + "cum": "154567.5" } }, { - "tier": 7.0, + "tier": 9.0, "currency": "USDT", - "minNotional": 2000000.0, - "maxNotional": 5000000.0, + "minNotional": 5000000.0, + "maxNotional": 10000000.0, "maintenanceMarginRate": 0.5, "maxLeverage": 1.0, "info": { - "bracket": "7", + "bracket": "9", "initialLeverage": "1", - "notionalCap": "5000000", - "notionalFloor": "2000000", + "notionalCap": "10000000", + "notionalFloor": "5000000", "maintMarginRatio": "0.5", - "cum": "654500.0" + "cum": "1404567.5" } } ], @@ -16338,10 +18120,10 @@ "minNotional": 0.0, "maxNotional": 5000.0, "maintenanceMarginRate": 0.02, - "maxLeverage": 20.0, + "maxLeverage": 25.0, "info": { "bracket": "1", - "initialLeverage": "20", + "initialLeverage": "25", "notionalCap": "5000", "notionalFloor": "0", "maintMarginRatio": "0.02", @@ -16354,10 +18136,10 @@ "minNotional": 5000.0, "maxNotional": 15000.0, "maintenanceMarginRate": 0.025, - "maxLeverage": 10.0, + "maxLeverage": 20.0, "info": { "bracket": "2", - "initialLeverage": "10", + "initialLeverage": "20", "notionalCap": "15000", "notionalFloor": "5000", "maintMarginRatio": "0.025", @@ -16370,10 +18152,10 @@ "minNotional": 15000.0, "maxNotional": 100000.0, "maintenanceMarginRate": 0.05, - "maxLeverage": 8.0, + "maxLeverage": 10.0, "info": { "bracket": "3", - "initialLeverage": "8", + "initialLeverage": "10", "notionalCap": "100000", "notionalFloor": "15000", "maintMarginRatio": "0.05", @@ -16400,13 +18182,13 @@ "tier": 5.0, "currency": "USDT", "minNotional": 250000.0, - "maxNotional": 1000000.0, + "maxNotional": 3000000.0, "maintenanceMarginRate": 0.125, "maxLeverage": 2.0, "info": { "bracket": "5", "initialLeverage": "2", - "notionalCap": "1000000", + "notionalCap": "3000000", "notionalFloor": "250000", "maintMarginRatio": "0.125", "cum": "11650.0" @@ -16415,17 +18197,17 @@ { "tier": 6.0, "currency": "USDT", - "minNotional": 1000000.0, - "maxNotional": 3000000.0, + "minNotional": 3000000.0, + "maxNotional": 8000000.0, "maintenanceMarginRate": 0.5, "maxLeverage": 1.0, "info": { "bracket": "6", "initialLeverage": "1", - "notionalCap": "3000000", - "notionalFloor": "1000000", + "notionalCap": "8000000", + "notionalFloor": "3000000", "maintMarginRatio": "0.5", - "cum": "386650.0" + "cum": "1136650.0" } } ], @@ -16630,96 +18412,112 @@ "tier": 1.0, "currency": "BUSD", "minNotional": 0.0, - "maxNotional": 100000.0, - "maintenanceMarginRate": 0.025, - "maxLeverage": 20.0, + "maxNotional": 50000.0, + "maintenanceMarginRate": 0.02, + "maxLeverage": 25.0, "info": { "bracket": "1", - "initialLeverage": "20", - "notionalCap": "100000", + "initialLeverage": "25", + "notionalCap": "50000", "notionalFloor": "0", - "maintMarginRatio": "0.025", + "maintMarginRatio": "0.02", "cum": "0.0" } }, { "tier": 2.0, "currency": "BUSD", + "minNotional": 50000.0, + "maxNotional": 100000.0, + "maintenanceMarginRate": 0.025, + "maxLeverage": 20.0, + "info": { + "bracket": "2", + "initialLeverage": "20", + "notionalCap": "100000", + "notionalFloor": "50000", + "maintMarginRatio": "0.025", + "cum": "250.0" + } + }, + { + "tier": 3.0, + "currency": "BUSD", "minNotional": 100000.0, "maxNotional": 500000.0, "maintenanceMarginRate": 0.05, "maxLeverage": 10.0, "info": { - "bracket": "2", + "bracket": "3", "initialLeverage": "10", "notionalCap": "500000", "notionalFloor": "100000", "maintMarginRatio": "0.05", - "cum": "2500.0" + "cum": "2750.0" } }, { - "tier": 3.0, + "tier": 4.0, "currency": "BUSD", "minNotional": 500000.0, "maxNotional": 1000000.0, "maintenanceMarginRate": 0.1, "maxLeverage": 5.0, "info": { - "bracket": "3", + "bracket": "4", "initialLeverage": "5", "notionalCap": "1000000", "notionalFloor": "500000", "maintMarginRatio": "0.1", - "cum": "27500.0" + "cum": "27750.0" } }, { - "tier": 4.0, + "tier": 5.0, "currency": "BUSD", "minNotional": 1000000.0, "maxNotional": 2000000.0, "maintenanceMarginRate": 0.15, "maxLeverage": 3.0, "info": { - "bracket": "4", + "bracket": "5", "initialLeverage": "3", "notionalCap": "2000000", "notionalFloor": "1000000", "maintMarginRatio": "0.15", - "cum": "77500.0" + "cum": "77750.0" } }, { - "tier": 5.0, + "tier": 6.0, "currency": "BUSD", "minNotional": 2000000.0, "maxNotional": 5000000.0, "maintenanceMarginRate": 0.25, "maxLeverage": 2.0, "info": { - "bracket": "5", + "bracket": "6", "initialLeverage": "2", "notionalCap": "5000000", "notionalFloor": "2000000", "maintMarginRatio": "0.25", - "cum": "277500.0" + "cum": "277750.0" } }, { - "tier": 6.0, + "tier": 7.0, "currency": "BUSD", "minNotional": 5000000.0, "maxNotional": 8000000.0, "maintenanceMarginRate": 0.5, "maxLeverage": 1.0, "info": { - "bracket": "6", + "bracket": "7", "initialLeverage": "1", "notionalCap": "8000000", "notionalFloor": "5000000", "maintMarginRatio": "0.5", - "cum": "1527500.0" + "cum": "1527750.0" } } ], @@ -16728,112 +18526,128 @@ "tier": 1.0, "currency": "USDT", "minNotional": 0.0, - "maxNotional": 150000.0, - "maintenanceMarginRate": 0.02, - "maxLeverage": 20.0, + "maxNotional": 50000.0, + "maintenanceMarginRate": 0.01, + "maxLeverage": 50.0, "info": { "bracket": "1", - "initialLeverage": "20", - "notionalCap": "150000", + "initialLeverage": "50", + "notionalCap": "50000", "notionalFloor": "0", - "maintMarginRatio": "0.02", + "maintMarginRatio": "0.01", "cum": "0.0" } }, { "tier": 2.0, "currency": "USDT", + "minNotional": 50000.0, + "maxNotional": 150000.0, + "maintenanceMarginRate": 0.02, + "maxLeverage": 20.0, + "info": { + "bracket": "2", + "initialLeverage": "20", + "notionalCap": "150000", + "notionalFloor": "50000", + "maintMarginRatio": "0.02", + "cum": "500.0" + } + }, + { + "tier": 3.0, + "currency": "USDT", "minNotional": 150000.0, "maxNotional": 250000.0, "maintenanceMarginRate": 0.025, "maxLeverage": 15.0, "info": { - "bracket": "2", + "bracket": "3", "initialLeverage": "15", "notionalCap": "250000", "notionalFloor": "150000", "maintMarginRatio": "0.025", - "cum": "750.0" + "cum": "1250.0" } }, { - "tier": 3.0, + "tier": 4.0, "currency": "USDT", "minNotional": 250000.0, "maxNotional": 1000000.0, "maintenanceMarginRate": 0.05, "maxLeverage": 10.0, "info": { - "bracket": "3", + "bracket": "4", "initialLeverage": "10", "notionalCap": "1000000", "notionalFloor": "250000", "maintMarginRatio": "0.05", - "cum": "7000.0" + "cum": "7500.0" } }, { - "tier": 4.0, + "tier": 5.0, "currency": "USDT", "minNotional": 1000000.0, "maxNotional": 2000000.0, "maintenanceMarginRate": 0.1, "maxLeverage": 5.0, "info": { - "bracket": "4", + "bracket": "5", "initialLeverage": "5", "notionalCap": "2000000", "notionalFloor": "1000000", "maintMarginRatio": "0.1", - "cum": "57000.0" + "cum": "57500.0" } }, { - "tier": 5.0, + "tier": 6.0, "currency": "USDT", "minNotional": 2000000.0, "maxNotional": 5000000.0, "maintenanceMarginRate": 0.125, "maxLeverage": 4.0, "info": { - "bracket": "5", + "bracket": "6", "initialLeverage": "4", "notionalCap": "5000000", "notionalFloor": "2000000", "maintMarginRatio": "0.125", - "cum": "107000.0" + "cum": "107500.0" } }, { - "tier": 6.0, + "tier": 7.0, "currency": "USDT", "minNotional": 5000000.0, "maxNotional": 10000000.0, "maintenanceMarginRate": 0.25, "maxLeverage": 2.0, "info": { - "bracket": "6", + "bracket": "7", "initialLeverage": "2", "notionalCap": "10000000", "notionalFloor": "5000000", "maintMarginRatio": "0.25", - "cum": "732000.0" + "cum": "732500.0" } }, { - "tier": 7.0, + "tier": 8.0, "currency": "USDT", "minNotional": 10000000.0, - "maxNotional": 11000000.0, + "maxNotional": 20000000.0, "maintenanceMarginRate": 0.5, "maxLeverage": 1.0, "info": { - "bracket": "7", + "bracket": "8", "initialLeverage": "1", - "notionalCap": "11000000", + "notionalCap": "20000000", "notionalFloor": "10000000", "maintMarginRatio": "0.5", - "cum": "3232000.0" + "cum": "3232500.0" } } ], @@ -17507,6 +19321,104 @@ } } ], + "T/USDT:USDT": [ + { + "tier": 1.0, + "currency": "USDT", + "minNotional": 0.0, + "maxNotional": 5000.0, + "maintenanceMarginRate": 0.02, + "maxLeverage": 20.0, + "info": { + "bracket": "1", + "initialLeverage": "20", + "notionalCap": "5000", + "notionalFloor": "0", + "maintMarginRatio": "0.02", + "cum": "0.0" + } + }, + { + "tier": 2.0, + "currency": "USDT", + "minNotional": 5000.0, + "maxNotional": 25000.0, + "maintenanceMarginRate": 0.025, + "maxLeverage": 15.0, + "info": { + "bracket": "2", + "initialLeverage": "15", + "notionalCap": "25000", + "notionalFloor": "5000", + "maintMarginRatio": "0.025", + "cum": "25.0" + } + }, + { + "tier": 3.0, + "currency": "USDT", + "minNotional": 25000.0, + "maxNotional": 100000.0, + "maintenanceMarginRate": 0.05, + "maxLeverage": 10.0, + "info": { + "bracket": "3", + "initialLeverage": "10", + "notionalCap": "100000", + "notionalFloor": "25000", + "maintMarginRatio": "0.05", + "cum": "650.0" + } + }, + { + "tier": 4.0, + "currency": "USDT", + "minNotional": 100000.0, + "maxNotional": 250000.0, + "maintenanceMarginRate": 0.1, + "maxLeverage": 5.0, + "info": { + "bracket": "4", + "initialLeverage": "5", + "notionalCap": "250000", + "notionalFloor": "100000", + "maintMarginRatio": "0.1", + "cum": "5650.0" + } + }, + { + "tier": 5.0, + "currency": "USDT", + "minNotional": 250000.0, + "maxNotional": 1000000.0, + "maintenanceMarginRate": 0.125, + "maxLeverage": 2.0, + "info": { + "bracket": "5", + "initialLeverage": "2", + "notionalCap": "1000000", + "notionalFloor": "250000", + "maintMarginRatio": "0.125", + "cum": "11900.0" + } + }, + { + "tier": 6.0, + "currency": "USDT", + "minNotional": 1000000.0, + "maxNotional": 5000000.0, + "maintenanceMarginRate": 0.5, + "maxLeverage": 1.0, + "info": { + "bracket": "6", + "initialLeverage": "1", + "notionalCap": "5000000", + "notionalFloor": "1000000", + "maintMarginRatio": "0.5", + "cum": "386900.0" + } + } + ], "THETA/USDT:USDT": [ { "tier": 1.0, @@ -18036,10 +19948,10 @@ "minNotional": 0.0, "maxNotional": 5000.0, "maintenanceMarginRate": 0.02, - "maxLeverage": 20.0, + "maxLeverage": 25.0, "info": { "bracket": "1", - "initialLeverage": "20", + "initialLeverage": "25", "notionalCap": "5000", "notionalFloor": "0", "maintMarginRatio": "0.02", @@ -18052,10 +19964,10 @@ "minNotional": 5000.0, "maxNotional": 25000.0, "maintenanceMarginRate": 0.025, - "maxLeverage": 10.0, + "maxLeverage": 15.0, "info": { "bracket": "2", - "initialLeverage": "10", + "initialLeverage": "15", "notionalCap": "25000", "notionalFloor": "5000", "maintMarginRatio": "0.025", @@ -18068,10 +19980,10 @@ "minNotional": 25000.0, "maxNotional": 100000.0, "maintenanceMarginRate": 0.05, - "maxLeverage": 8.0, + "maxLeverage": 10.0, "info": { "bracket": "3", - "initialLeverage": "8", + "initialLeverage": "10", "notionalCap": "100000", "notionalFloor": "25000", "maintMarginRatio": "0.05", @@ -18098,13 +20010,13 @@ "tier": 5.0, "currency": "BUSD", "minNotional": 250000.0, - "maxNotional": 1000000.0, + "maxNotional": 1500000.0, "maintenanceMarginRate": 0.125, - "maxLeverage": 2.0, + "maxLeverage": 4.0, "info": { "bracket": "5", - "initialLeverage": "2", - "notionalCap": "1000000", + "initialLeverage": "4", + "notionalCap": "1500000", "notionalFloor": "250000", "maintMarginRatio": "0.125", "cum": "11900.0" @@ -18113,17 +20025,33 @@ { "tier": 6.0, "currency": "BUSD", - "minNotional": 1000000.0, - "maxNotional": 5000000.0, + "minNotional": 1500000.0, + "maxNotional": 3000000.0, + "maintenanceMarginRate": 0.25, + "maxLeverage": 2.0, + "info": { + "bracket": "6", + "initialLeverage": "2", + "notionalCap": "3000000", + "notionalFloor": "1500000", + "maintMarginRatio": "0.25", + "cum": "199400.0" + } + }, + { + "tier": 7.0, + "currency": "BUSD", + "minNotional": 3000000.0, + "maxNotional": 8000000.0, "maintenanceMarginRate": 0.5, "maxLeverage": 1.0, "info": { - "bracket": "6", + "bracket": "7", "initialLeverage": "1", - "notionalCap": "5000000", - "notionalFloor": "1000000", + "notionalCap": "8000000", + "notionalFloor": "3000000", "maintMarginRatio": "0.5", - "cum": "386900.0" + "cum": "949400.0" } } ], @@ -18134,10 +20062,10 @@ "minNotional": 0.0, "maxNotional": 10000.0, "maintenanceMarginRate": 0.0065, - "maxLeverage": 30.0, + "maxLeverage": 50.0, "info": { "bracket": "1", - "initialLeverage": "30", + "initialLeverage": "50", "notionalCap": "10000", "notionalFloor": "0", "maintMarginRatio": "0.0065", @@ -18148,13 +20076,13 @@ "tier": 2.0, "currency": "USDT", "minNotional": 10000.0, - "maxNotional": 50000.0, + "maxNotional": 90000.0, "maintenanceMarginRate": 0.01, "maxLeverage": 25.0, "info": { "bracket": "2", "initialLeverage": "25", - "notionalCap": "50000", + "notionalCap": "90000", "notionalFloor": "10000", "maintMarginRatio": "0.01", "cum": "35.0" @@ -18163,87 +20091,87 @@ { "tier": 3.0, "currency": "USDT", - "minNotional": 50000.0, - "maxNotional": 250000.0, + "minNotional": 90000.0, + "maxNotional": 645000.0, "maintenanceMarginRate": 0.02, "maxLeverage": 20.0, "info": { "bracket": "3", "initialLeverage": "20", - "notionalCap": "250000", - "notionalFloor": "50000", + "notionalCap": "645000", + "notionalFloor": "90000", "maintMarginRatio": "0.02", - "cum": "535.0" + "cum": "935.0" } }, { "tier": 4.0, "currency": "USDT", - "minNotional": 250000.0, - "maxNotional": 1000000.0, + "minNotional": 645000.0, + "maxNotional": 1200000.0, "maintenanceMarginRate": 0.05, "maxLeverage": 10.0, "info": { "bracket": "4", "initialLeverage": "10", - "notionalCap": "1000000", - "notionalFloor": "250000", + "notionalCap": "1200000", + "notionalFloor": "645000", "maintMarginRatio": "0.05", - "cum": "8035.0" + "cum": "20285.0" } }, { "tier": 5.0, "currency": "USDT", - "minNotional": 1000000.0, - "maxNotional": 2000000.0, + "minNotional": 1200000.0, + "maxNotional": 3000000.0, "maintenanceMarginRate": 0.1, "maxLeverage": 5.0, "info": { "bracket": "5", "initialLeverage": "5", - "notionalCap": "2000000", - "notionalFloor": "1000000", + "notionalCap": "3000000", + "notionalFloor": "1200000", "maintMarginRatio": "0.1", - "cum": "58035.0" + "cum": "80285.0" } }, { "tier": 6.0, "currency": "USDT", - "minNotional": 2000000.0, - "maxNotional": 5000000.0, + "minNotional": 3000000.0, + "maxNotional": 6000000.0, "maintenanceMarginRate": 0.125, "maxLeverage": 4.0, "info": { "bracket": "6", "initialLeverage": "4", - "notionalCap": "5000000", - "notionalFloor": "2000000", + "notionalCap": "6000000", + "notionalFloor": "3000000", "maintMarginRatio": "0.125", - "cum": "108035.0" + "cum": "155285.0" } }, { "tier": 7.0, "currency": "USDT", - "minNotional": 5000000.0, - "maxNotional": 10000000.0, + "minNotional": 6000000.0, + "maxNotional": 12000000.0, "maintenanceMarginRate": 0.15, "maxLeverage": 3.0, "info": { "bracket": "7", "initialLeverage": "3", - "notionalCap": "10000000", - "notionalFloor": "5000000", + "notionalCap": "12000000", + "notionalFloor": "6000000", "maintMarginRatio": "0.15", - "cum": "233035.0" + "cum": "305285.0" } }, { "tier": 8.0, "currency": "USDT", - "minNotional": 10000000.0, + "minNotional": 12000000.0, "maxNotional": 20000000.0, "maintenanceMarginRate": 0.25, "maxLeverage": 2.0, @@ -18251,9 +20179,9 @@ "bracket": "8", "initialLeverage": "2", "notionalCap": "20000000", - "notionalFloor": "10000000", + "notionalFloor": "12000000", "maintMarginRatio": "0.25", - "cum": "1233035.0" + "cum": "1505285.0" } }, { @@ -18269,7 +20197,7 @@ "notionalCap": "30000000", "notionalFloor": "20000000", "maintMarginRatio": "0.5", - "cum": "6233035.0" + "cum": "6505285.0" } } ], @@ -18475,14 +20403,14 @@ "currency": "USDT", "minNotional": 0.0, "maxNotional": 5000.0, - "maintenanceMarginRate": 0.01, - "maxLeverage": 20.0, + "maintenanceMarginRate": 0.0065, + "maxLeverage": 25.0, "info": { "bracket": "1", - "initialLeverage": "20", + "initialLeverage": "25", "notionalCap": "5000", "notionalFloor": "0", - "maintMarginRatio": "0.01", + "maintMarginRatio": "0.0065", "cum": "0.0" } }, @@ -18490,80 +20418,96 @@ "tier": 2.0, "currency": "USDT", "minNotional": 5000.0, - "maxNotional": 25000.0, - "maintenanceMarginRate": 0.025, - "maxLeverage": 10.0, + "maxNotional": 10000.0, + "maintenanceMarginRate": 0.01, + "maxLeverage": 20.0, "info": { "bracket": "2", - "initialLeverage": "10", - "notionalCap": "25000", + "initialLeverage": "20", + "notionalCap": "10000", "notionalFloor": "5000", - "maintMarginRatio": "0.025", - "cum": "75.0" + "maintMarginRatio": "0.01", + "cum": "17.5" } }, { "tier": 3.0, "currency": "USDT", + "minNotional": 10000.0, + "maxNotional": 25000.0, + "maintenanceMarginRate": 0.025, + "maxLeverage": 10.0, + "info": { + "bracket": "3", + "initialLeverage": "10", + "notionalCap": "25000", + "notionalFloor": "10000", + "maintMarginRatio": "0.025", + "cum": "167.5" + } + }, + { + "tier": 4.0, + "currency": "USDT", "minNotional": 25000.0, "maxNotional": 100000.0, "maintenanceMarginRate": 0.05, "maxLeverage": 8.0, "info": { - "bracket": "3", + "bracket": "4", "initialLeverage": "8", "notionalCap": "100000", "notionalFloor": "25000", "maintMarginRatio": "0.05", - "cum": "700.0" + "cum": "792.5" } }, { - "tier": 4.0, + "tier": 5.0, "currency": "USDT", "minNotional": 100000.0, "maxNotional": 250000.0, "maintenanceMarginRate": 0.1, "maxLeverage": 5.0, "info": { - "bracket": "4", + "bracket": "5", "initialLeverage": "5", "notionalCap": "250000", "notionalFloor": "100000", "maintMarginRatio": "0.1", - "cum": "5700.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": "11950.0" + "cum": "5792.5" } }, { "tier": 6.0, "currency": "USDT", - "minNotional": 1000000.0, + "minNotional": 250000.0, "maxNotional": 5000000.0, + "maintenanceMarginRate": 0.125, + "maxLeverage": 2.0, + "info": { + "bracket": "6", + "initialLeverage": "2", + "notionalCap": "5000000", + "notionalFloor": "250000", + "maintMarginRatio": "0.125", + "cum": "12042.5" + } + }, + { + "tier": 7.0, + "currency": "USDT", + "minNotional": 5000000.0, + "maxNotional": 8000000.0, "maintenanceMarginRate": 0.5, "maxLeverage": 1.0, "info": { - "bracket": "6", + "bracket": "7", "initialLeverage": "1", - "notionalCap": "5000000", - "notionalFloor": "1000000", + "notionalCap": "8000000", + "notionalFloor": "5000000", "maintMarginRatio": "0.5", - "cum": "386950.0" + "cum": "1887042.5" } } ], @@ -18770,10 +20714,10 @@ "minNotional": 0.0, "maxNotional": 5000.0, "maintenanceMarginRate": 0.02, - "maxLeverage": 10.0, + "maxLeverage": 15.0, "info": { "bracket": "1", - "initialLeverage": "10", + "initialLeverage": "15", "notionalCap": "5000", "notionalFloor": "0", "maintMarginRatio": "0.02", @@ -18786,10 +20730,10 @@ "minNotional": 5000.0, "maxNotional": 25000.0, "maintenanceMarginRate": 0.025, - "maxLeverage": 8.0, + "maxLeverage": 10.0, "info": { "bracket": "2", - "initialLeverage": "8", + "initialLeverage": "10", "notionalCap": "25000", "notionalFloor": "5000", "maintMarginRatio": "0.025", @@ -18800,13 +20744,13 @@ "tier": 3.0, "currency": "USDT", "minNotional": 25000.0, - "maxNotional": 100000.0, + "maxNotional": 120000.0, "maintenanceMarginRate": 0.05, - "maxLeverage": 6.0, + "maxLeverage": 8.0, "info": { "bracket": "3", - "initialLeverage": "6", - "notionalCap": "100000", + "initialLeverage": "8", + "notionalCap": "120000", "notionalFloor": "25000", "maintMarginRatio": "0.05", "cum": "650.0" @@ -18815,23 +20759,23 @@ { "tier": 4.0, "currency": "USDT", - "minNotional": 100000.0, - "maxNotional": 250000.0, + "minNotional": 120000.0, + "maxNotional": 300000.0, "maintenanceMarginRate": 0.1, "maxLeverage": 5.0, "info": { "bracket": "4", "initialLeverage": "5", - "notionalCap": "250000", - "notionalFloor": "100000", + "notionalCap": "300000", + "notionalFloor": "120000", "maintMarginRatio": "0.1", - "cum": "5650.0" + "cum": "6650.0" } }, { "tier": 5.0, "currency": "USDT", - "minNotional": 250000.0, + "minNotional": 300000.0, "maxNotional": 1000000.0, "maintenanceMarginRate": 0.125, "maxLeverage": 2.0, @@ -18839,9 +20783,9 @@ "bracket": "5", "initialLeverage": "2", "notionalCap": "1000000", - "notionalFloor": "250000", + "notionalFloor": "300000", "maintMarginRatio": "0.125", - "cum": "11900.0" + "cum": "14150.0" } }, { @@ -18857,7 +20801,7 @@ "notionalCap": "3000000", "notionalFloor": "1000000", "maintMarginRatio": "0.5", - "cum": "386900.0" + "cum": "389150.0" } } ], @@ -19388,144 +21332,160 @@ "tier": 1.0, "currency": "USDT", "minNotional": 0.0, - "maxNotional": 10000.0, - "maintenanceMarginRate": 0.0065, - "maxLeverage": 50.0, + "maxNotional": 5000.0, + "maintenanceMarginRate": 0.005, + "maxLeverage": 75.0, "info": { "bracket": "1", - "initialLeverage": "50", - "notionalCap": "10000", + "initialLeverage": "75", + "notionalCap": "5000", "notionalFloor": "0", - "maintMarginRatio": "0.0065", + "maintMarginRatio": "0.005", "cum": "0.0" } }, { "tier": 2.0, "currency": "USDT", + "minNotional": 5000.0, + "maxNotional": 10000.0, + "maintenanceMarginRate": 0.0065, + "maxLeverage": 50.0, + "info": { + "bracket": "2", + "initialLeverage": "50", + "notionalCap": "10000", + "notionalFloor": "5000", + "maintMarginRatio": "0.0065", + "cum": "7.5" + } + }, + { + "tier": 3.0, + "currency": "USDT", "minNotional": 10000.0, "maxNotional": 50000.0, "maintenanceMarginRate": 0.01, "maxLeverage": 40.0, "info": { - "bracket": "2", + "bracket": "3", "initialLeverage": "40", "notionalCap": "50000", "notionalFloor": "10000", "maintMarginRatio": "0.01", - "cum": "35.0" + "cum": "42.5" } }, { - "tier": 3.0, + "tier": 4.0, "currency": "USDT", "minNotional": 50000.0, "maxNotional": 250000.0, "maintenanceMarginRate": 0.02, "maxLeverage": 25.0, "info": { - "bracket": "3", + "bracket": "4", "initialLeverage": "25", "notionalCap": "250000", "notionalFloor": "50000", "maintMarginRatio": "0.02", - "cum": "535.0" + "cum": "542.5" } }, { - "tier": 4.0, + "tier": 5.0, "currency": "USDT", "minNotional": 250000.0, "maxNotional": 1000000.0, "maintenanceMarginRate": 0.05, "maxLeverage": 10.0, "info": { - "bracket": "4", + "bracket": "5", "initialLeverage": "10", "notionalCap": "1000000", "notionalFloor": "250000", "maintMarginRatio": "0.05", - "cum": "8035.0" + "cum": "8042.5" } }, { - "tier": 5.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": "58035.0" + "cum": "58042.5" } }, { - "tier": 6.0, + "tier": 7.0, "currency": "USDT", "minNotional": 2000000.0, "maxNotional": 5000000.0, "maintenanceMarginRate": 0.125, "maxLeverage": 4.0, "info": { - "bracket": "6", + "bracket": "7", "initialLeverage": "4", "notionalCap": "5000000", "notionalFloor": "2000000", "maintMarginRatio": "0.125", - "cum": "108035.0" + "cum": "108042.5" } }, { - "tier": 7.0, + "tier": 8.0, "currency": "USDT", "minNotional": 5000000.0, "maxNotional": 10000000.0, "maintenanceMarginRate": 0.15, "maxLeverage": 3.0, "info": { - "bracket": "7", + "bracket": "8", "initialLeverage": "3", "notionalCap": "10000000", "notionalFloor": "5000000", "maintMarginRatio": "0.15", - "cum": "233035.0" + "cum": "233042.5" } }, { - "tier": 8.0, + "tier": 9.0, "currency": "USDT", "minNotional": 10000000.0, "maxNotional": 20000000.0, "maintenanceMarginRate": 0.25, "maxLeverage": 2.0, "info": { - "bracket": "8", + "bracket": "9", "initialLeverage": "2", "notionalCap": "20000000", "notionalFloor": "10000000", "maintMarginRatio": "0.25", - "cum": "1233035.0" + "cum": "1233042.5" } }, { - "tier": 9.0, + "tier": 10.0, "currency": "USDT", "minNotional": 20000000.0, "maxNotional": 50000000.0, "maintenanceMarginRate": 0.5, "maxLeverage": 1.0, "info": { - "bracket": "9", + "bracket": "10", "initialLeverage": "1", "notionalCap": "50000000", "notionalFloor": "20000000", "maintMarginRatio": "0.5", - "cum": "6233035.0" + "cum": "6233042.5" } } ], diff --git a/freqtrade/exchange/bybit.py b/freqtrade/exchange/bybit.py index d14c7c192..c565b891f 100644 --- a/freqtrade/exchange/bybit.py +++ b/freqtrade/exchange/bybit.py @@ -1,9 +1,16 @@ """ Bybit exchange subclass """ import logging -from typing import Dict, List, Tuple +from datetime import datetime +from typing import Any, Dict, List, Optional, Tuple -from freqtrade.enums import MarginMode, TradingMode +import ccxt + +from freqtrade.constants import BuySell +from freqtrade.enums import MarginMode, PriceType, TradingMode +from freqtrade.exceptions import DDosProtection, OperationalException, TemporaryError from freqtrade.exchange import Exchange +from freqtrade.exchange.common import retrier +from freqtrade.exchange.exchange_utils import timeframe_to_msecs logger = logging.getLogger(__name__) @@ -21,17 +28,27 @@ class Bybit(Exchange): _ft_has: Dict = { "ohlcv_candle_limit": 1000, - "ccxt_futures_name": "linear", "ohlcv_has_history": False, } _ft_has_futures: Dict = { + "ohlcv_candle_limit": 200, "ohlcv_has_history": True, + "mark_ohlcv_timeframe": "4h", + "funding_fee_timeframe": "8h", + "stoploss_on_exchange": True, + "stoploss_order_types": {"limit": "limit", "market": "market"}, + "stop_price_type_field": "triggerBy", + "stop_price_type_value_mapping": { + PriceType.LAST: "LastPrice", + PriceType.MARK: "MarkPrice", + PriceType.INDEX: "IndexPrice", + }, } _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) + (TradingMode.FUTURES, MarginMode.ISOLATED) ] @property @@ -47,3 +64,158 @@ class Bybit(Exchange): }) config.update(super()._ccxt_config) return config + + 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' + ) + + @retrier + def additional_exchange_init(self) -> None: + """ + Additional exchange initialization logic. + .api will be available at this point. + Must be overridden in child methods if required. + """ + try: + if self.trading_mode == TradingMode.FUTURES and not self._config['dry_run']: + position_mode = self._api.set_position_mode(False) + self._log_exchange_response('set_position_mode', position_mode) + except ccxt.DDoSProtection as e: + raise DDosProtection(e) from e + except (ccxt.NetworkError, 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 + + async def _fetch_funding_rate_history( + self, + pair: str, + timeframe: str, + limit: int, + since_ms: Optional[int] = None, + ) -> List[List]: + """ + Fetch funding rate history + Necessary workaround until https://github.com/ccxt/ccxt/issues/15990 is fixed. + """ + params = {} + if since_ms: + until = since_ms + (timeframe_to_msecs(timeframe) * self._ft_has['ohlcv_candle_limit']) + params.update({'until': until}) + # Funding rate + data = await self._api_async.fetch_funding_rate_history( + pair, since=since_ms, + params=params) + # Convert funding rate to candle pattern + data = [[x['timestamp'], x['fundingRate'], 0, 0, 0, 0] for x in data] + return data + + def _lev_prep(self, pair: str, leverage: float, side: BuySell): + if self.trading_mode != TradingMode.SPOT: + params = {'leverage': leverage} + self.set_margin_mode(pair, self.margin_mode, accept_fail=True, params=params) + self._set_leverage(leverage, pair, accept_fail=True) + + def _get_params( + self, + side: BuySell, + ordertype: str, + leverage: float, + reduceOnly: bool, + time_in_force: str = 'GTC', + ) -> Dict: + params = super()._get_params( + side=side, + ordertype=ordertype, + leverage=leverage, + reduceOnly=reduceOnly, + time_in_force=time_in_force, + ) + if self.trading_mode == TradingMode.FUTURES and self.margin_mode: + params['position_idx'] = 0 + return params + + def dry_run_liquidation_price( + self, + pair: str, + open_rate: float, # Entry price of position + is_short: bool, + amount: float, + 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 + ) -> Optional[float]: + """ + Important: Must be fetching data from cached values as this is used by backtesting! + PERPETUAL: + bybit: + https://www.bybithelp.com/HelpCenterKnowledge/bybitHC_Article?language=en_US&id=000001067 + + Long: + Liquidation Price = ( + Entry Price * (1 - Initial Margin Rate + Maintenance Margin Rate) + - Extra Margin Added/ Contract) + Short: + Liquidation Price = ( + Entry Price * (1 + Initial Margin Rate - Maintenance Margin Rate) + + Extra Margin Added/ Contract) + + Implementation Note: Extra margin is currently not used. + + :param pair: Pair to calculate liquidation price for + :param open_rate: Entry price of position + :param is_short: True if the trade is a short, false otherwise + :param amount: Absolute value of position size incl. leverage (in base currency) + :param stake_amount: Stake amount - Collateral in settle currency. + :param leverage: Leverage used for this position. + :param trading_mode: SPOT, MARGIN, FUTURES, etc. + :param margin_mode: Either ISOLATED or CROSS + :param wallet_balance: Amount of margin_mode in the wallet being used to trade + Cross-Margin Mode: crossWalletBalance + Isolated-Margin Mode: isolatedWalletBalance + """ + + market = self.markets[pair] + mm_ratio, _ = self.get_maintenance_ratio_and_amt(pair, stake_amount) + + if self.trading_mode == TradingMode.FUTURES and self.margin_mode == MarginMode.ISOLATED: + + if market['inverse']: + raise OperationalException( + "Freqtrade does not yet support inverse contracts") + initial_margin_rate = 1 / leverage + + # See docstring - ignores extra margin! + if is_short: + return open_rate * (1 + initial_margin_rate - mm_ratio) + else: + return open_rate * (1 - initial_margin_rate + mm_ratio) + + else: + raise OperationalException( + "Freqtrade only supports isolated futures for leverage trading") + + def get_funding_fees( + self, pair: str, amount: float, is_short: bool, open_date: datetime) -> float: + """ + Fetch funding fees, either from the exchange (live) or calculates them + based on funding rate/mark price history + :param pair: The quote/base pair of the trade + :param is_short: trade direction + :param amount: Trade amount + :param open_date: Open date of the trade + :return: funding fee since open_date + :raises: ExchangeError if something goes wrong. + """ + # Bybit does not provide "applied" funding fees per position. + if self.trading_mode == TradingMode.FUTURES: + return self._fetch_and_calculate_funding_fees( + pair, amount, is_short, open_date) + return 0.0 diff --git a/freqtrade/exchange/common.py b/freqtrade/exchange/common.py index 6d09c4f95..42a7094ba 100644 --- a/freqtrade/exchange/common.py +++ b/freqtrade/exchange/common.py @@ -46,13 +46,13 @@ MAP_EXCHANGE_CHILDCLASS = { 'binanceje': 'binance', 'binanceusdm': 'binance', 'okex': 'okx', - 'gate': 'gateio', + 'gateio': 'gate', } SUPPORTED_EXCHANGES = [ 'binance', 'bittrex', - 'gateio', + 'gate', 'huobi', 'kraken', 'okx', diff --git a/freqtrade/exchange/exchange.py b/freqtrade/exchange/exchange.py index f0bcee702..0cac411c7 100644 --- a/freqtrade/exchange/exchange.py +++ b/freqtrade/exchange/exchange.py @@ -7,6 +7,7 @@ import inspect import logging from copy import deepcopy from datetime import datetime, timedelta, timezone +from math import floor from threading import Lock from typing import Any, Coroutine, Dict, List, Literal, Optional, Tuple, Union @@ -20,9 +21,10 @@ from pandas import DataFrame, concat from freqtrade.constants import (DEFAULT_AMOUNT_RESERVE_PERCENT, NON_OPEN_EXCHANGE_STATES, BidAsk, BuySell, Config, EntryExit, ListPairsWithTimeframes, MakerTaker, - PairWithTimeframe) + OBLiteral, PairWithTimeframe) from freqtrade.data.converter import clean_ohlcv_dataframe, ohlcv_to_dataframe, trades_dict_to_list from freqtrade.enums import OPTIMIZE_MODES, CandleType, MarginMode, TradingMode +from freqtrade.enums.pricetype import PriceType from freqtrade.exceptions import (DDosProtection, ExchangeError, InsufficientFundsError, InvalidOrderException, OperationalException, PricingError, RetryableOrderError, TemporaryError) @@ -35,7 +37,7 @@ from freqtrade.exchange.exchange_utils import (CcxtModuleType, amount_to_contrac price_to_precision, timeframe_to_minutes, timeframe_to_msecs, timeframe_to_next_date, timeframe_to_prev_date, timeframe_to_seconds) -from freqtrade.exchange.types import OHLCVResponse, Ticker, Tickers +from freqtrade.exchange.types import OHLCVResponse, OrderBook, Ticker, Tickers from freqtrade.misc import (chunks, deep_merge_dicts, file_dump_json, file_load_json, safe_value_fallback2) from freqtrade.plugins.pairlist.pairlist_helpers import expand_pairlist @@ -599,12 +601,27 @@ class Exchange: if not self.exchange_has('createMarketOrder'): raise OperationalException( f'Exchange {self.name} does not support market orders.') + self.validate_stop_ordertypes(order_types) + def validate_stop_ordertypes(self, order_types: Dict) -> None: + """ + Validate stoploss order types + """ if (order_types.get("stoploss_on_exchange") and not self._ft_has.get("stoploss_on_exchange", False)): raise OperationalException( f'On exchange stoploss is not supported for {self.name}.' ) + if self.trading_mode == TradingMode.FUTURES: + price_mapping = self._ft_has.get('stop_price_type_value_mapping', {}).keys() + if ( + order_types.get("stoploss_on_exchange", False) is True + and 'stoploss_price_type' in order_types + and order_types['stoploss_price_type'] not in price_mapping + ): + raise OperationalException( + f'On exchange stoploss price type is not supported for {self.name}.' + ) def validate_pricing(self, pricing: Dict) -> None: if pricing.get('use_order_book', False) and not self.exchange_has('fetchL2OrderBook'): @@ -833,7 +850,7 @@ class Exchange: 'remaining': _amount, 'datetime': arrow.utcnow().strftime('%Y-%m-%dT%H:%M:%S.%fZ'), 'timestamp': arrow.utcnow().int_timestamp * 1000, - 'status': "closed" if ordertype == "market" and not stop_loss else "open", + 'status': "open", 'fee': None, 'info': {}, 'leverage': leverage @@ -843,20 +860,33 @@ class Exchange: dry_order["stopPrice"] = dry_order["price"] # Workaround to avoid filling stoploss orders immediately dry_order["ft_order_type"] = "stoploss" + orderbook: Optional[OrderBook] = None + if self.exchange_has('fetchL2OrderBook'): + orderbook = self.fetch_l2_order_book(pair, 20) + if ordertype == "limit" and orderbook: + # Allow a 3% price difference + allowed_diff = 0.03 + if self._dry_is_price_crossed(pair, side, rate, orderbook, allowed_diff): + logger.info( + f"Converted order {pair} to market order due to price {rate} crossing spread " + f"by more than {allowed_diff:.2%}.") + dry_order["type"] = "market" if dry_order["type"] == "market" and not dry_order.get("ft_order_type"): # Update market order pricing - average = self.get_dry_market_fill_price(pair, side, amount, rate) + average = self.get_dry_market_fill_price(pair, side, amount, rate, orderbook) dry_order.update({ 'average': average, 'filled': _amount, 'remaining': 0.0, + 'status': "closed", 'cost': (dry_order['amount'] * average) / leverage }) # market orders will always incurr taker fees dry_order = self.add_dry_order_fee(pair, dry_order, 'taker') - dry_order = self.check_dry_limit_order_filled(dry_order, immediate=True) + dry_order = self.check_dry_limit_order_filled( + dry_order, immediate=True, orderbook=orderbook) self._dry_run_open_orders[dry_order["id"]] = dry_order # Copy order and close it - so the returned order is open unless it's a market order @@ -878,20 +908,22 @@ class Exchange: }) return dry_order - def get_dry_market_fill_price(self, pair: str, side: str, amount: float, rate: float) -> float: + def get_dry_market_fill_price(self, pair: str, side: str, amount: float, rate: float, + orderbook: Optional[OrderBook]) -> float: """ Get the market order fill price based on orderbook interpolation """ if self.exchange_has('fetchL2OrderBook'): - ob = self.fetch_l2_order_book(pair, 20) - ob_type = 'asks' if side == 'buy' else 'bids' + if not orderbook: + orderbook = self.fetch_l2_order_book(pair, 20) + ob_type: OBLiteral = 'asks' if side == 'buy' else 'bids' slippage = 0.05 max_slippage_val = rate * ((1 + slippage) if side == 'buy' else (1 - slippage)) remaining_amount = amount filled_amount = 0.0 book_entry_price = 0.0 - for book_entry in ob[ob_type]: + for book_entry in orderbook[ob_type]: book_entry_price = book_entry[0] book_entry_coin_volume = book_entry[1] if remaining_amount > 0: @@ -919,20 +951,20 @@ class Exchange: return rate - def _is_dry_limit_order_filled(self, pair: str, side: str, limit: float) -> bool: + def _dry_is_price_crossed(self, pair: str, side: str, limit: float, + orderbook: Optional[OrderBook] = None, offset: float = 0.0) -> bool: if not self.exchange_has('fetchL2OrderBook'): return True - ob = self.fetch_l2_order_book(pair, 1) + if not orderbook: + orderbook = self.fetch_l2_order_book(pair, 1) try: if side == 'buy': - price = ob['asks'][0][0] - logger.debug(f"{pair} checking dry buy-order: price={price}, limit={limit}") - if limit >= price: + price = orderbook['asks'][0][0] + if limit * (1 - offset) >= price: return True else: - price = ob['bids'][0][0] - logger.debug(f"{pair} checking dry sell-order: price={price}, limit={limit}") - if limit <= price: + price = orderbook['bids'][0][0] + if limit * (1 + offset) <= price: return True except IndexError: # Ignore empty orderbooks when filling - can be filled with the next iteration. @@ -940,7 +972,8 @@ class Exchange: return False def check_dry_limit_order_filled( - self, order: Dict[str, Any], immediate: bool = False) -> 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). """ @@ -948,7 +981,7 @@ class Exchange: and order['type'] in ["limit"] and not order.get('ft_order_type')): pair = order['symbol'] - if self._is_dry_limit_order_filled(pair, order['side'], order['price']): + if self._dry_is_price_crossed(pair, order['side'], order['price'], orderbook): order.update({ 'status': 'closed', 'filled': order['amount'], @@ -1114,8 +1147,8 @@ class Exchange: return params @retrier(retries=0) - def stoploss(self, pair: str, amount: float, stop_price: float, order_types: Dict, - side: BuySell, leverage: float) -> Dict: + def create_stoploss(self, pair: str, amount: float, stop_price: float, order_types: Dict, + side: BuySell, leverage: float) -> Dict: """ creates a stoploss order. requires `_ft_has['stoploss_order_types']` to be set as a dict mapping limit and market @@ -1160,6 +1193,10 @@ class Exchange: stop_price=stop_price_norm) if self.trading_mode == TradingMode.FUTURES: params['reduceOnly'] = True + if 'stoploss_price_type' in order_types and 'stop_price_type_field' in self._ft_has: + price_type = self._ft_has['stop_price_type_value_mapping'][ + order_types.get('stoploss_price_type', PriceType.LAST)] + params[self._ft_has['stop_price_type_field']] = price_type amount = self.amount_to_precision(pair, self._amount_to_contracts(pair, amount)) @@ -1490,7 +1527,7 @@ class Exchange: return result @retrier - def fetch_l2_order_book(self, pair: str, limit: int = 100) -> dict: + def fetch_l2_order_book(self, pair: str, limit: int = 100) -> OrderBook: """ Get L2 order book from exchange. Can be limited to a certain amount (if supported). @@ -1533,7 +1570,7 @@ class Exchange: def get_rate(self, pair: str, refresh: bool, side: EntryExit, is_short: bool, - order_book: Optional[dict] = None, ticker: Optional[Ticker] = None) -> float: + order_book: Optional[OrderBook] = None, ticker: Optional[Ticker] = None) -> float: """ Calculates bid/ask target bid rate - between current ask price and last price @@ -1571,7 +1608,8 @@ class Exchange: logger.debug('order_book %s', order_book) # top 1 = index 0 try: - rate = order_book[f"{price_side}s"][order_book_top - 1][0] + obside: OBLiteral = 'bids' if price_side == 'bid' else 'asks' + rate = order_book[obside][order_book_top - 1][0] except (IndexError, KeyError) as e: logger.warning( f"{pair} - {name} Price at location {order_book_top} from orderbook " @@ -1977,9 +2015,9 @@ class Exchange: continue # Deconstruct tuple (has 5 elements) pair, timeframe, c_type, ticks, drop_hint = res - drop_incomplete = drop_hint if drop_incomplete is None else drop_incomplete + drop_incomplete_ = drop_hint if drop_incomplete is None else drop_incomplete ohlcv_df = self._process_ohlcv_df( - pair, timeframe, c_type, ticks, cache, drop_incomplete) + pair, timeframe, c_type, ticks, cache, drop_incomplete_) results_df[(pair, timeframe, c_type)] = ohlcv_df @@ -2484,7 +2522,8 @@ class Exchange: self, leverage: float, pair: Optional[str] = None, - trading_mode: Optional[TradingMode] = None + trading_mode: Optional[TradingMode] = None, + accept_fail: bool = False, ): """ Set's the leverage before making a trade, in order to not @@ -2493,12 +2532,18 @@ class Exchange: if self._config['dry_run'] or not self.exchange_has("setLeverage"): # Some exchanges only support one margin_mode type return - + if self._ft_has.get('floor_leverage', False) is True: + # Rounding for binance ... + leverage = floor(leverage) try: res = self._api.set_leverage(symbol=pair, leverage=leverage) self._log_exchange_response('set_leverage', res) except ccxt.DDoSProtection as e: raise DDosProtection(e) from e + except ccxt.BadRequest as e: + if not accept_fail: + raise TemporaryError( + f'Could not set leverage due to {e.__class__.__name__}. Message: {e}') from e except (ccxt.NetworkError, ccxt.ExchangeError) as e: raise TemporaryError( f'Could not set leverage due to {e.__class__.__name__}. Message: {e}') from e @@ -2520,7 +2565,8 @@ class Exchange: return open_date.minute > 0 or open_date.second > 0 @retrier - def set_margin_mode(self, pair: str, margin_mode: MarginMode, params: dict = {}): + def set_margin_mode(self, pair: str, margin_mode: MarginMode, accept_fail: bool = False, + params: dict = {}): """ Set's the margin mode on the exchange to cross or isolated for a specific pair :param pair: base/quote currency pair (e.g. "ADA/USDT") @@ -2534,6 +2580,10 @@ class Exchange: self._log_exchange_response('set_margin_mode', res) except ccxt.DDoSProtection as e: raise DDosProtection(e) from e + except ccxt.BadRequest as e: + if not accept_fail: + raise TemporaryError( + f'Could not set margin mode due to {e.__class__.__name__}. Message: {e}') from e except (ccxt.NetworkError, ccxt.ExchangeError) as e: raise TemporaryError( f'Could not set margin mode due to {e.__class__.__name__}. Message: {e}') from e @@ -2687,6 +2737,7 @@ class Exchange: is_short: bool, amount: float, # Absolute value of position size 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 @@ -2708,6 +2759,7 @@ class Exchange: open_rate=open_rate, is_short=is_short, amount=amount, + leverage=leverage, stake_amount=stake_amount, wallet_balance=wallet_balance, mm_ex_1=mm_ex_1, @@ -2719,7 +2771,7 @@ class Exchange: pos = positions[0] isolated_liq = pos['liquidationPrice'] - if isolated_liq: + if isolated_liq is not None: buffer_amount = abs(open_rate - isolated_liq) * self.liquidation_buffer isolated_liq = ( isolated_liq - buffer_amount @@ -2737,6 +2789,7 @@ class Exchange: is_short: bool, amount: float, 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 @@ -2744,7 +2797,7 @@ class Exchange: """ Important: Must be fetching data from cached values as this is used by backtesting! PERPETUAL: - gateio: https://www.gate.io/help/futures/futures/27724/liquidation-price-bankruptcy-price + gate: https://www.gate.io/help/futures/futures/27724/liquidation-price-bankruptcy-price > Liquidation Price = (Entry Price ± Margin / Contract Multiplier / Size) / [ 1 ± (Maintenance Margin Ratio + Taker Rate)] Wherein, "+" or "-" depends on whether the contract goes long or short: @@ -2758,13 +2811,14 @@ class Exchange: :param is_short: True if the trade is a short, false otherwise :param amount: Absolute value of position size incl. leverage (in base currency) :param stake_amount: Stake amount - Collateral in settle currency. + :param leverage: Leverage used for this position. :param trading_mode: SPOT, MARGIN, FUTURES, etc. :param margin_mode: Either ISOLATED or CROSS :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 Gateio or OKX + # * Not required by Gate or OKX :param mm_ex_1: :param upnl_ex_1: """ diff --git a/freqtrade/exchange/gateio.py b/freqtrade/exchange/gate.py similarity index 92% rename from freqtrade/exchange/gateio.py rename to freqtrade/exchange/gate.py index de178af02..80ed4088a 100644 --- a/freqtrade/exchange/gateio.py +++ b/freqtrade/exchange/gate.py @@ -4,7 +4,7 @@ from datetime import datetime from typing import Any, Dict, List, Optional, Tuple from freqtrade.constants import BuySell -from freqtrade.enums import MarginMode, TradingMode +from freqtrade.enums import MarginMode, PriceType, TradingMode from freqtrade.exceptions import OperationalException from freqtrade.exchange import Exchange from freqtrade.misc import safe_value_fallback2 @@ -13,7 +13,7 @@ from freqtrade.misc import safe_value_fallback2 logger = logging.getLogger(__name__) -class Gateio(Exchange): +class Gate(Exchange): """ Gate.io exchange class. Contains adjustments needed for Freqtrade to work with this exchange. @@ -34,6 +34,12 @@ class Gateio(Exchange): "needs_trading_fees": True, "fee_cost_in_contracts": False, # Set explicitly to false for clarity "order_props_in_contracts": ['amount', 'filled', 'remaining'], + "stop_price_type_field": "price_type", + "stop_price_type_value_mapping": { + PriceType.LAST: 0, + PriceType.MARK: 1, + PriceType.INDEX: 2, + }, } _supported_trading_mode_margin_pairs: List[Tuple[TradingMode, MarginMode]] = [ @@ -49,6 +55,7 @@ class Gateio(Exchange): if any(v == 'market' for k, v in order_types.items()): raise OperationalException( f'Exchange {self.name} does not support market orders.') + super().validate_stop_ordertypes(order_types) def _get_params( self, @@ -77,7 +84,7 @@ class Gateio(Exchange): if self.trading_mode == TradingMode.FUTURES: # Futures usually don't contain fees in the response. - # As such, futures orders on gateio will not contain a fee, which causes + # As such, futures orders on gate will not contain a fee, which causes # a repeated "update fee" cycle and wrong calculations. # Therefore we patch the response with fees if it's not available. # An alternative also contianing fees would be diff --git a/freqtrade/exchange/hitbtc.py b/freqtrade/exchange/hitbtc.py index a48c9a198..bc4c7aa81 100644 --- a/freqtrade/exchange/hitbtc.py +++ b/freqtrade/exchange/hitbtc.py @@ -19,5 +19,4 @@ class Hitbtc(Exchange): _ft_has: Dict = { "ohlcv_candle_limit": 1000, - "ohlcv_params": {"sort": "DESC"} } diff --git a/freqtrade/exchange/kraken.py b/freqtrade/exchange/kraken.py index 5d8c1ad29..8a4f7f7e0 100644 --- a/freqtrade/exchange/kraken.py +++ b/freqtrade/exchange/kraken.py @@ -97,8 +97,8 @@ class Kraken(Exchange): )) @retrier(retries=0) - def stoploss(self, pair: str, amount: float, stop_price: float, - order_types: Dict, side: BuySell, leverage: float) -> Dict: + def create_stoploss(self, pair: str, amount: float, stop_price: float, + order_types: Dict, side: BuySell, leverage: float) -> Dict: """ Creates a stoploss market order. Stoploss market orders is the only stoploss type supported by kraken. @@ -158,7 +158,8 @@ class Kraken(Exchange): self, leverage: float, pair: Optional[str] = None, - trading_mode: Optional[TradingMode] = None + trading_mode: Optional[TradingMode] = None, + accept_fail: bool = False, ): """ Kraken set's the leverage as an option in the order object, so we need to diff --git a/freqtrade/exchange/kucoin.py b/freqtrade/exchange/kucoin.py index 6c7d7acfc..797d9fbd2 100644 --- a/freqtrade/exchange/kucoin.py +++ b/freqtrade/exchange/kucoin.py @@ -36,3 +36,34 @@ class Kucoin(Exchange): 'stop': 'loss' }) return params + + def create_order( + self, + *, + pair: str, + ordertype: str, + side: BuySell, + amount: float, + rate: float, + leverage: float, + reduceOnly: bool = False, + time_in_force: str = 'GTC', + ) -> Dict: + + res = super().create_order( + pair=pair, + ordertype=ordertype, + side=side, + amount=amount, + rate=rate, + leverage=leverage, + reduceOnly=reduceOnly, + time_in_force=time_in_force, + ) + # Kucoin returns only the order-id. + # ccxt returns status = 'closed' at the moment - which is information ccxt invented. + # Since we rely on status heavily, we must set it to 'open' here. + # ref: https://github.com/ccxt/ccxt/pull/16674, (https://github.com/ccxt/ccxt/pull/16553) + res['type'] = ordertype + res['status'] = 'open' + return res diff --git a/freqtrade/exchange/okx.py b/freqtrade/exchange/okx.py index 6792c2cba..e7d658d24 100644 --- a/freqtrade/exchange/okx.py +++ b/freqtrade/exchange/okx.py @@ -5,6 +5,7 @@ import ccxt from freqtrade.constants import BuySell from freqtrade.enums import CandleType, MarginMode, TradingMode +from freqtrade.enums.pricetype import PriceType from freqtrade.exceptions import DDosProtection, OperationalException, TemporaryError from freqtrade.exchange import Exchange, date_minus_candles from freqtrade.exchange.common import retrier @@ -27,6 +28,12 @@ class Okx(Exchange): _ft_has_futures: Dict = { "tickers_have_quoteVolume": False, "fee_cost_in_contracts": True, + "stop_price_type_field": "tpTriggerPxType", + "stop_price_type_value_mapping": { + PriceType.LAST: "last", + PriceType.MARK: "index", + PriceType.INDEX: "mark", + }, } _supported_trading_mode_margin_pairs: List[Tuple[TradingMode, MarginMode]] = [ @@ -118,13 +125,15 @@ class Okx(Exchange): if self.trading_mode != TradingMode.SPOT and self.margin_mode is not None: try: # TODO-lev: Test me properly (check mgnMode passed) - self._api.set_leverage( + res = self._api.set_leverage( leverage=leverage, symbol=pair, params={ "mgnMode": self.margin_mode.value, "posSide": self._get_posSide(side, False), }) + self._log_exchange_response('set_leverage', res) + except ccxt.DDoSProtection as e: raise DDosProtection(e) from e except (ccxt.NetworkError, ccxt.ExchangeError) as e: diff --git a/freqtrade/exchange/types.py b/freqtrade/exchange/types.py index 813b09297..5568e4336 100644 --- a/freqtrade/exchange/types.py +++ b/freqtrade/exchange/types.py @@ -15,6 +15,15 @@ class Ticker(TypedDict): # Several more - only listing required. +class OrderBook(TypedDict): + symbol: str + bids: List[Tuple[float, float]] + asks: List[Tuple[float, float]] + timestamp: Optional[int] + datetime: Optional[str] + nonce: Optional[int] + + Tickers = Dict[str, Ticker] # pair, timeframe, candleType, OHLCV, drop last?, diff --git a/freqtrade/freqai/RL/BaseEnvironment.py b/freqtrade/freqai/RL/BaseEnvironment.py index ef1c02a3b..7a4467bf7 100644 --- a/freqtrade/freqai/RL/BaseEnvironment.py +++ b/freqtrade/freqai/RL/BaseEnvironment.py @@ -45,7 +45,8 @@ class BaseEnvironment(gym.Env): def __init__(self, df: DataFrame = DataFrame(), prices: DataFrame = DataFrame(), reward_kwargs: dict = {}, window_size=10, starting_point=True, id: str = 'baseenv-1', seed: int = 1, config: dict = {}, live: bool = False, - fee: float = 0.0015, can_short: bool = False): + fee: float = 0.0015, can_short: bool = False, pair: str = "", + df_raw: DataFrame = DataFrame()): """ Initializes the training/eval environment. :param df: dataframe of features @@ -60,12 +61,14 @@ class BaseEnvironment(gym.Env): :param fee: The fee to use for environmental interactions. :param can_short: Whether or not the environment can short """ - self.config = config - self.rl_config = config['freqai']['rl_config'] - self.add_state_info = self.rl_config.get('add_state_info', False) - self.id = id - self.max_drawdown = 1 - self.rl_config.get('max_training_drawdown_pct', 0.8) - self.compound_trades = config['stake_amount'] == 'unlimited' + self.config: dict = config + self.rl_config: dict = config['freqai']['rl_config'] + self.add_state_info: bool = self.rl_config.get('add_state_info', False) + self.id: str = id + self.max_drawdown: float = 1 - self.rl_config.get('max_training_drawdown_pct', 0.8) + self.compound_trades: bool = config['stake_amount'] == 'unlimited' + self.pair: str = pair + self.raw_features: DataFrame = df_raw if self.config.get('fee', None) is not None: self.fee = self.config['fee'] else: @@ -74,8 +77,8 @@ class BaseEnvironment(gym.Env): # set here to default 5Ac, but all children envs can override this self.actions: Type[Enum] = BaseActions self.tensorboard_metrics: dict = {} - self.can_short = can_short - self.live = live + self.can_short: bool = can_short + self.live: bool = live if not self.live and self.add_state_info: self.add_state_info = False logger.warning("add_state_info is not available in backtesting. Deactivating.") @@ -93,13 +96,12 @@ class BaseEnvironment(gym.Env): :param reward_kwargs: extra config settings assigned by user in `rl_config` :param starting_point: start at edge of window or not """ - self.df = df - self.signal_features = self.df - self.prices = prices - self.window_size = window_size - self.starting_point = starting_point - self.rr = reward_kwargs["rr"] - self.profit_aim = reward_kwargs["profit_aim"] + self.signal_features: DataFrame = df + self.prices: DataFrame = prices + self.window_size: int = window_size + self.starting_point: bool = starting_point + self.rr: float = reward_kwargs["rr"] + self.profit_aim: float = reward_kwargs["profit_aim"] # # spaces if self.add_state_info: diff --git a/freqtrade/freqai/RL/BaseReinforcementLearningModel.py b/freqtrade/freqai/RL/BaseReinforcementLearningModel.py index 3a4d0d0e6..a8ef69394 100644 --- a/freqtrade/freqai/RL/BaseReinforcementLearningModel.py +++ b/freqtrade/freqai/RL/BaseReinforcementLearningModel.py @@ -1,3 +1,4 @@ +import copy import importlib import logging from abc import abstractmethod @@ -50,6 +51,7 @@ class BaseReinforcementLearningModel(IFreqaiModel): self.eval_callback: Optional[EvalCallback] = None self.model_type = self.freqai_info['rl_config']['model_type'] self.rl_config = self.freqai_info['rl_config'] + self.df_raw: DataFrame = DataFrame() self.continual_learning = self.freqai_info.get('continual_learning', False) if self.model_type in SB3_MODELS: import_str = 'stable_baselines3' @@ -107,6 +109,7 @@ class BaseReinforcementLearningModel(IFreqaiModel): data_dictionary: Dict[str, Any] = dk.make_train_test_datasets( features_filtered, labels_filtered) + self.df_raw = copy.deepcopy(data_dictionary["train_features"]) dk.fit_labels() # FIXME useless for now, but just satiating append methods # normalize all data based on train_dataset only @@ -143,7 +146,7 @@ class BaseReinforcementLearningModel(IFreqaiModel): train_df = data_dictionary["train_features"] test_df = data_dictionary["test_features"] - env_info = self.pack_env_dict() + env_info = self.pack_env_dict(dk.pair) self.train_env = self.MyRLEnv(df=train_df, prices=prices_train, @@ -158,7 +161,7 @@ class BaseReinforcementLearningModel(IFreqaiModel): actions = self.train_env.get_actions() self.tensorboard_callback = TensorboardCallback(verbose=1, actions=actions) - def pack_env_dict(self) -> Dict[str, Any]: + def pack_env_dict(self, pair: str) -> Dict[str, Any]: """ Create dictionary of environment arguments """ @@ -166,7 +169,9 @@ class BaseReinforcementLearningModel(IFreqaiModel): "reward_kwargs": self.reward_params, "config": self.config, "live": self.live, - "can_short": self.can_short} + "can_short": self.can_short, + "pair": pair, + "df_raw": self.df_raw} if self.data_provider: env_info["fee"] = self.data_provider._exchange \ .get_fee(symbol=self.data_provider.current_whitelist()[0]) # type: ignore @@ -347,7 +352,7 @@ class BaseReinforcementLearningModel(IFreqaiModel): sets a custom reward based on profit and trade duration. """ - def calculate_reward(self, action: int) -> float: + def calculate_reward(self, action: int) -> float: # noqa: C901 """ An example reward function. This is the one function that users will likely wish to inject their own creativity into. @@ -363,10 +368,19 @@ class BaseReinforcementLearningModel(IFreqaiModel): pnl = self.get_unrealized_profit() factor = 100. + # you can use feature values from dataframe + rsi_now = self.raw_features[f"%-rsi-period-10_shift-1_{self.pair}_" + f"{self.config['timeframe']}"].iloc[self._current_tick] + # reward agent for entering trades if (action in (Actions.Long_enter.value, Actions.Short_enter.value) and self._position == Positions.Neutral): - return 25 + if rsi_now < 40: + factor = 40 / rsi_now + else: + factor = 1 + return 25 * factor + # discourage agent from not entering trades if action == Actions.Neutral.value and self._position == Positions.Neutral: return -1 diff --git a/freqtrade/freqai/data_drawer.py b/freqtrade/freqai/data_drawer.py index 848fb20eb..c90bb23fc 100644 --- a/freqtrade/freqai/data_drawer.py +++ b/freqtrade/freqai/data_drawer.py @@ -59,7 +59,7 @@ class FreqaiDataDrawer: Juha Nykänen @suikula, Wagner Costa @wagnercosta, Johan Vlugt @Jooopieeert """ - def __init__(self, full_path: Path, config: Config, follow_mode: bool = False): + def __init__(self, full_path: Path, config: Config): self.config = config self.freqai_info = config.get("freqai", {}) @@ -72,21 +72,13 @@ class FreqaiDataDrawer: self.model_return_values: Dict[str, DataFrame] = {} self.historic_data: Dict[str, Dict[str, DataFrame]] = {} self.historic_predictions: Dict[str, DataFrame] = {} - self.follower_dict: Dict[str, pair_info] = {} self.full_path = full_path - self.follower_name: str = self.config.get("bot_name", "follower1") - self.follower_dict_path = Path( - self.full_path / f"follower_dictionary-{self.follower_name}.json" - ) self.historic_predictions_path = Path(self.full_path / "historic_predictions.pkl") self.historic_predictions_bkp_path = Path( self.full_path / "historic_predictions.backup.pkl") self.pair_dictionary_path = Path(self.full_path / "pair_dictionary.json") self.global_metadata_path = Path(self.full_path / "global_metadata.json") self.metric_tracker_path = Path(self.full_path / "metric_tracker.json") - self.follow_mode = follow_mode - if follow_mode: - self.create_follower_dict() self.load_drawer_from_disk() self.load_historic_predictions_from_disk() self.metric_tracker: Dict[str, Dict[str, Dict[str, list]]] = {} @@ -149,13 +141,8 @@ class FreqaiDataDrawer: if exists: with open(self.pair_dictionary_path, "r") as fp: self.pair_dict = rapidjson.load(fp, number_mode=rapidjson.NM_NATIVE) - elif not self.follow_mode: - logger.info("Could not find existing datadrawer, starting from scratch") else: - logger.warning( - f"Follower could not find pair_dictionary at {self.full_path} " - "sending null values back to strategy" - ) + logger.info("Could not find existing datadrawer, starting from scratch") def load_metric_tracker_from_disk(self): """ @@ -193,13 +180,8 @@ class FreqaiDataDrawer: self.historic_predictions = cloudpickle.load(fp) logger.warning('FreqAI successfully loaded the backup historical predictions file.') - elif not self.follow_mode: - logger.info("Could not find existing historic_predictions, starting from scratch") else: - logger.warning( - f"Follower could not find historic predictions at {self.full_path} " - "sending null values back to strategy" - ) + logger.info("Could not find existing historic_predictions, starting from scratch") return exists @@ -231,14 +213,6 @@ class FreqaiDataDrawer: rapidjson.dump(self.pair_dict, fp, default=self.np_encoder, number_mode=rapidjson.NM_NATIVE) - def save_follower_dict_to_disk(self): - """ - Save follower dictionary to disk (used by strategy for persistent prediction targets) - """ - with open(self.follower_dict_path, "w") as fp: - rapidjson.dump(self.follower_dict, fp, default=self.np_encoder, - number_mode=rapidjson.NM_NATIVE) - def save_global_metadata_to_disk(self, metadata: Dict[str, Any]): """ Save global metadata json to disk @@ -248,28 +222,11 @@ class FreqaiDataDrawer: rapidjson.dump(metadata, fp, default=self.np_encoder, number_mode=rapidjson.NM_NATIVE) - def create_follower_dict(self): - """ - Create or dictionary for each follower to maintain unique persistent prediction targets - """ - - whitelist_pairs = self.config.get("exchange", {}).get("pair_whitelist") - - exists = self.follower_dict_path.is_file() - - if exists: - logger.info("Found an existing follower dictionary") - - for pair in whitelist_pairs: - self.follower_dict[pair] = {} - - self.save_follower_dict_to_disk() - def np_encoder(self, object): if isinstance(object, np.generic): return object.item() - def get_pair_dict_info(self, pair: str) -> Tuple[str, int, bool]: + 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 @@ -278,32 +235,19 @@ class FreqaiDataDrawer: :return: model_filename: str = unique filename used for loading persistent objects from disk trained_timestamp: int = the last time the coin was trained - return_null_array: bool = Follower could not find pair metadata """ pair_dict = self.pair_dict.get(pair) - data_path_set = self.pair_dict.get(pair, self.empty_pair_dict).get("data_path", "") - return_null_array = False if pair_dict: model_filename = pair_dict["model_filename"] trained_timestamp = pair_dict["trained_timestamp"] - elif not self.follow_mode: + else: self.pair_dict[pair] = self.empty_pair_dict.copy() model_filename = "" trained_timestamp = 0 - if not data_path_set and self.follow_mode: - logger.warning( - f"Follower could not find current pair {pair} in " - f"pair_dictionary at path {self.full_path}, sending null values " - "back to strategy." - ) - trained_timestamp = 0 - model_filename = '' - return_null_array = True - - return model_filename, trained_timestamp, return_null_array + return model_filename, trained_timestamp def set_pair_dict_info(self, metadata: dict) -> None: pair_in_dict = self.pair_dict.get(metadata["pair"]) @@ -311,7 +255,6 @@ class FreqaiDataDrawer: return else: self.pair_dict[metadata["pair"]] = self.empty_pair_dict.copy() - return def set_initial_return_values(self, pair: str, pred_df: DataFrame) -> None: @@ -423,6 +366,12 @@ class FreqaiDataDrawer: def purge_old_models(self) -> None: + num_keep = self.freqai_info["purge_old_models"] + if not num_keep: + return + elif type(num_keep) == bool: + num_keep = 2 + model_folders = [x for x in self.full_path.iterdir() if x.is_dir()] pattern = re.compile(r"sub-train-(\w+)_(\d{10})") @@ -445,11 +394,11 @@ class FreqaiDataDrawer: delete_dict[coin]["timestamps"][int(timestamp)] = dir for coin in delete_dict: - if delete_dict[coin]["num_folders"] > 2: + if delete_dict[coin]["num_folders"] > num_keep: sorted_dict = collections.OrderedDict( sorted(delete_dict[coin]["timestamps"].items()) ) - num_delete = len(sorted_dict) - 2 + num_delete = len(sorted_dict) - num_keep deleted = 0 for k, v in sorted_dict.items(): if deleted >= num_delete: @@ -458,12 +407,6 @@ class FreqaiDataDrawer: shutil.rmtree(v) deleted += 1 - def update_follower_metadata(self): - # follower needs to load from disk to get any changes made by leader to pair_dict - self.load_drawer_from_disk() - if self.config.get("freqai", {}).get("purge_old_models", False): - self.purge_old_models() - def save_metadata(self, dk: FreqaiDataKitchen) -> None: """ Saves only metadata for backtesting studies if user prefers diff --git a/freqtrade/freqai/data_kitchen.py b/freqtrade/freqai/data_kitchen.py index 6f4a8c2b3..5d8e895a5 100644 --- a/freqtrade/freqai/data_kitchen.py +++ b/freqtrade/freqai/data_kitchen.py @@ -1,6 +1,7 @@ import copy import inspect import logging +import random import shutil from datetime import datetime, timezone from math import cos, sin @@ -170,6 +171,19 @@ class FreqaiDataKitchen: train_labels = labels train_weights = weights + if feat_dict["shuffle_after_split"]: + rint1 = random.randint(0, 100) + rint2 = random.randint(0, 100) + train_features = train_features.sample( + frac=1, random_state=rint1).reset_index(drop=True) + train_labels = train_labels.sample(frac=1, random_state=rint1).reset_index(drop=True) + train_weights = pd.DataFrame(train_weights).sample( + frac=1, random_state=rint1).reset_index(drop=True).to_numpy()[:, 0] + test_features = test_features.sample(frac=1, random_state=rint2).reset_index(drop=True) + test_labels = test_labels.sample(frac=1, random_state=rint2).reset_index(drop=True) + test_weights = pd.DataFrame(test_weights).sample( + frac=1, random_state=rint2).reset_index(drop=True).to_numpy()[:, 0] + # Simplest way to reverse the order of training and test data: if self.freqai_config['feature_parameters'].get('reverse_train_test_order', False): return self.build_data_dictionary( @@ -1247,17 +1261,19 @@ class FreqaiDataKitchen: tfs: List[str] = self.freqai_config["feature_parameters"].get("include_timeframes") for tf in tfs: + metadata = {"pair": pair, "tf": tf} informative_df = self.get_pair_data_for_features( pair, tf, strategy, corr_dataframes, base_dataframes, is_corr_pairs) informative_copy = informative_df.copy() for t in self.freqai_config["feature_parameters"]["indicator_periods_candles"]: df_features = strategy.feature_engineering_expand_all( - informative_copy.copy(), t) + informative_copy.copy(), t, metadata=metadata) suffix = f"{t}" informative_df = self.merge_features(informative_df, df_features, tf, tf, suffix) - generic_df = strategy.feature_engineering_expand_basic(informative_copy.copy()) + generic_df = strategy.feature_engineering_expand_basic( + informative_copy.copy(), metadata=metadata) suffix = "gen" informative_df = self.merge_features(informative_df, generic_df, tf, tf, suffix) @@ -1326,8 +1342,8 @@ class FreqaiDataKitchen: "include_corr_pairlist", []) dataframe = self.populate_features(dataframe.copy(), pair, strategy, corr_dataframes, base_dataframes) - - dataframe = strategy.feature_engineering_standard(dataframe.copy()) + metadata = {"pair": pair} + dataframe = strategy.feature_engineering_standard(dataframe.copy(), metadata=metadata) # ensure corr pairs are always last for corr_pair in corr_pairs: if pair == corr_pair: @@ -1336,7 +1352,7 @@ class FreqaiDataKitchen: dataframe = self.populate_features(dataframe.copy(), corr_pair, strategy, corr_dataframes, base_dataframes, True) - dataframe = strategy.set_freqai_targets(dataframe.copy()) + dataframe = strategy.set_freqai_targets(dataframe.copy(), metadata=metadata) self.get_unique_classes_from_labels(dataframe) @@ -1546,3 +1562,25 @@ class FreqaiDataKitchen: dataframe.columns = dataframe.columns.str.replace(c, "") return dataframe + + def buffer_timerange(self, timerange: TimeRange): + """ + Buffer the start and end of the timerange. This is used *after* the indicators + are populated. + + The main example use is when predicting maxima and minima, the argrelextrema + function cannot know the maxima/minima at the edges of the timerange. To improve + model accuracy, it is best to compute argrelextrema on the full timerange + and then use this function to cut off the edges (buffer) by the kernel. + + In another case, if the targets are set to a shifted price movement, this + buffer is unnecessary because the shifted candles at the end of the timerange + will be NaN and FreqAI will automatically cut those off of the training + dataset. + """ + buffer = self.freqai_config["feature_parameters"]["buffer_train_data_candles"] + if buffer: + timerange.stopts -= buffer * timeframe_to_seconds(self.config["timeframe"]) + timerange.startts += buffer * timeframe_to_seconds(self.config["timeframe"]) + + return timerange diff --git a/freqtrade/freqai/freqai_interface.py b/freqtrade/freqai/freqai_interface.py index 830970ba0..97f8c36b8 100644 --- a/freqtrade/freqai/freqai_interface.py +++ b/freqtrade/freqai/freqai_interface.py @@ -66,12 +66,11 @@ class IFreqaiModel(ABC): self.retrain = False self.first = True self.set_full_path() - self.follow_mode: bool = self.freqai_info.get("follow_mode", False) self.save_backtest_models: bool = self.freqai_info.get("save_backtest_models", True) if self.save_backtest_models: logger.info('Backtesting module configured to save all models.') - self.dd = FreqaiDataDrawer(Path(self.full_path), self.config, self.follow_mode) + self.dd = FreqaiDataDrawer(Path(self.full_path), self.config) # set current candle to arbitrary historical date self.current_candle: datetime = datetime.fromtimestamp(637887600, tz=timezone.utc) self.dd.current_candle = self.current_candle @@ -153,7 +152,7 @@ class IFreqaiModel(ABC): # (backtest window, i.e. window immediately following the training window). # FreqAI slides the window and sequentially builds the backtesting results before returning # the concatenated results for the full backtesting period back to the strategy. - elif not self.follow_mode: + else: self.dk = FreqaiDataKitchen(self.config, self.live, metadata["pair"]) if not self.config.get("freqai_backtest_live_models", False): logger.info(f"Training {len(self.dk.training_timeranges)} timeranges") @@ -228,7 +227,7 @@ class IFreqaiModel(ABC): logger.warning(f'{pair} not in current whitelist, removing from train queue.') continue - (_, trained_timestamp, _) = self.dd.get_pair_dict_info(pair) + (_, trained_timestamp) = self.dd.get_pair_dict_info(pair) dk = FreqaiDataKitchen(self.config, self.live, pair) ( @@ -286,7 +285,7 @@ class IFreqaiModel(ABC): # following tr_train. Both of these windows slide through the # entire backtest for tr_train, tr_backtest in zip(dk.training_timeranges, dk.backtesting_timeranges): - (_, _, _) = self.dd.get_pair_dict_info(pair) + (_, _) = self.dd.get_pair_dict_info(pair) train_it += 1 total_trains = len(dk.backtesting_timeranges) self.training_timerange = tr_train @@ -325,9 +324,13 @@ class IFreqaiModel(ABC): populate_indicators = False dataframe_base_train = dataframe.loc[dataframe["date"] < tr_train.stopdt, :] - dataframe_base_train = strategy.set_freqai_targets(dataframe_base_train) + dataframe_base_train = strategy.set_freqai_targets( + dataframe_base_train, metadata=metadata) dataframe_base_backtest = dataframe.loc[dataframe["date"] < tr_backtest.stopdt, :] - dataframe_base_backtest = strategy.set_freqai_targets(dataframe_base_backtest) + dataframe_base_backtest = strategy.set_freqai_targets( + dataframe_base_backtest, metadata=metadata) + + tr_train = dk.buffer_timerange(tr_train) dataframe_train = dk.slice_dataframe(tr_train, dataframe_base_train) dataframe_backtest = dk.slice_dataframe(tr_backtest, dataframe_base_backtest) @@ -379,18 +382,9 @@ class IFreqaiModel(ABC): :returns: dk: FreqaiDataKitchen = Data management/analysis tool associated to present pair only """ - # update follower - if self.follow_mode: - self.dd.update_follower_metadata() # get the model metadata associated with the current pair - (_, trained_timestamp, return_null_array) = self.dd.get_pair_dict_info(metadata["pair"]) - - # if the metadata doesn't exist, the follower returns null arrays to strategy - if self.follow_mode and return_null_array: - logger.info("Returning null array from follower to strategy") - self.dd.return_null_values_to_strategy(dataframe, dk) - return dk + (_, trained_timestamp) = self.dd.get_pair_dict_info(metadata["pair"]) # append the historic data once per round if self.dd.historic_data: @@ -398,27 +392,18 @@ class IFreqaiModel(ABC): logger.debug(f'Updating historic data on pair {metadata["pair"]}') self.track_current_candle() - if not self.follow_mode: + (_, new_trained_timerange, data_load_timerange) = dk.check_if_new_training_required( + trained_timestamp + ) + dk.set_paths(metadata["pair"], new_trained_timerange.stopts) - (_, new_trained_timerange, data_load_timerange) = dk.check_if_new_training_required( - trained_timestamp - ) - dk.set_paths(metadata["pair"], new_trained_timerange.stopts) + # load candle history into memory if it is not yet. + if not self.dd.historic_data: + self.dd.load_all_pair_histories(data_load_timerange, dk) - # load candle history into memory if it is not yet. - if not self.dd.historic_data: - self.dd.load_all_pair_histories(data_load_timerange, dk) - - if not self.scanning: - self.scanning = True - self.start_scanning(strategy) - - elif self.follow_mode: - dk.set_paths(metadata["pair"], trained_timestamp) - logger.info( - "FreqAI instance set to follow_mode, finding existing pair " - f"using { self.identifier }" - ) + if not self.scanning: + self.scanning = True + self.start_scanning(strategy) # load the model and associated data into the data kitchen self.model = self.dd.load_data(metadata["pair"], dk) @@ -580,7 +565,13 @@ class IFreqaiModel(ABC): :return: :boolean: whether the model file exists or not. """ - path_to_modelfile = Path(dk.data_path / f"{dk.model_filename}_model.joblib") + if self.dd.model_type == 'joblib': + file_type = ".joblib" + elif self.dd.model_type == 'keras': + file_type = ".h5" + elif 'stable_baselines' in self.dd.model_type or 'sb3_contrib' == self.dd.model_type: + file_type = ".zip" + path_to_modelfile = Path(dk.data_path / f"{dk.model_filename}_model{file_type}") file_exists = path_to_modelfile.is_file() if file_exists: logger.info("Found model at %s", dk.data_path / dk.model_filename) @@ -625,6 +616,8 @@ class IFreqaiModel(ABC): strategy, corr_dataframes, base_dataframes, pair ) + new_trained_timerange = dk.buffer_timerange(new_trained_timerange) + unfiltered_dataframe = dk.slice_dataframe(new_trained_timerange, unfiltered_dataframe) # find the features indicated by strategy and store in datakitchen @@ -640,8 +633,7 @@ class IFreqaiModel(ABC): if self.plot_features: plot_feature_importance(model, pair, dk, self.plot_features) - if self.freqai_info.get("purge_old_models", False): - self.dd.purge_old_models() + self.dd.purge_old_models() def set_initial_historic_predictions( self, pred_df: DataFrame, dk: FreqaiDataKitchen, pair: str, strat_df: DataFrame diff --git a/freqtrade/freqai/prediction_models/ReinforcementLearner_multiproc.py b/freqtrade/freqai/prediction_models/ReinforcementLearner_multiproc.py index a9be87b0b..9ee035c95 100644 --- a/freqtrade/freqai/prediction_models/ReinforcementLearner_multiproc.py +++ b/freqtrade/freqai/prediction_models/ReinforcementLearner_multiproc.py @@ -34,7 +34,7 @@ class ReinforcementLearner_multiproc(ReinforcementLearner): train_df = data_dictionary["train_features"] test_df = data_dictionary["test_features"] - env_info = self.pack_env_dict() + env_info = self.pack_env_dict(dk.pair) env_id = "train_env" self.train_env = SubprocVecEnv([make_env(self.MyRLEnv, env_id, i, 1, diff --git a/freqtrade/freqtradebot.py b/freqtrade/freqtradebot.py index 25ae5002a..82be6f3b5 100644 --- a/freqtrade/freqtradebot.py +++ b/freqtrade/freqtradebot.py @@ -344,7 +344,15 @@ class FreqtradeBot(LoggingMixin): try: fo = self.exchange.fetch_order_or_stoploss_order(order.order_id, order.ft_pair, order.ft_order_side == 'stoploss') - + if not order.trade: + # This should not happen, but it does if trades were deleted manually. + # This can only incur on sqlite, which doesn't enforce foreign constraints. + logger.warning( + f"Order {order.order_id} has no trade attached. " + "This may suggest a database corruption. " + f"The expected trade ID is {order.ft_trade_id}. Ignoring this order." + ) + continue self.update_trade_state(order.trade, order.order_id, fo, stoploss_order=(order.ft_order_side == 'stoploss')) @@ -355,7 +363,7 @@ class FreqtradeBot(LoggingMixin): "Order is older than 5 days. Assuming order was fully cancelled.") fo = order.to_ccxt_object() fo['status'] = 'canceled' - self.handle_timedout_order(fo, order.trade) + self.handle_cancel_order(fo, order.trade, constants.CANCEL_REASON['TIMEOUT']) except ExchangeError as e: @@ -750,13 +758,15 @@ class FreqtradeBot(LoggingMixin): self.exchange.name, order['filled'], order['amount'], order['remaining'] ) - amount = safe_value_fallback(order, 'filled', 'amount') - enter_limit_filled_price = safe_value_fallback(order, 'average', 'price') + amount = safe_value_fallback(order, 'filled', 'amount', amount) + enter_limit_filled_price = safe_value_fallback( + order, 'average', 'price', enter_limit_filled_price) # in case of FOK the order may be filled immediately and fully elif order_status == 'closed': - amount = safe_value_fallback(order, 'filled', 'amount') - enter_limit_filled_price = safe_value_fallback(order, 'average', 'price') + amount = safe_value_fallback(order, 'filled', 'amount', amount) + enter_limit_filled_price = safe_value_fallback( + order, 'average', 'price', enter_limit_requested) # Fee is applied twice because we make a LIMIT_BUY and LIMIT_SELL fee = self.exchange.get_fee(symbol=pair, taker_or_maker='maker') @@ -1068,7 +1078,7 @@ class FreqtradeBot(LoggingMixin): datetime.now(timezone.utc), enter=enter, exit_=exit_, - force_stoploss=self.edge.stoploss(trade.pair) if self.edge else 0 + force_stoploss=self.edge.get_stoploss(trade.pair) if self.edge else 0 ) for should_exit in exits: if should_exit.exit_flag: @@ -1088,7 +1098,7 @@ class FreqtradeBot(LoggingMixin): :return: True if the order succeeded, and False in case of problems. """ try: - stoploss_order = self.exchange.stoploss( + stoploss_order = self.exchange.create_stoploss( pair=trade.pair, amount=trade.amount, stop_price=stop_price, @@ -1160,15 +1170,13 @@ class FreqtradeBot(LoggingMixin): # If enter order is fulfilled but there is no stoploss, we add a stoploss on exchange if not stoploss_order: - stoploss = ( - self.edge.stoploss(pair=trade.pair) - if self.edge else - trade.stop_loss_pct / trade.leverage - ) - if trade.is_short: - stop_price = trade.open_rate * (1 - stoploss) - else: - stop_price = trade.open_rate * (1 + stoploss) + stop_price = trade.stoploss_or_liquidation + if self.edge: + stoploss = self.edge.get_stoploss(pair=trade.pair) + stop_price = ( + trade.open_rate * (1 - stoploss) if trade.is_short + else trade.open_rate * (1 + stoploss) + ) if self.create_stoploss_order(trade=trade, stop_price=stop_price): # The above will return False if the placement failed and the trade was force-sold. @@ -1253,11 +1261,11 @@ class FreqtradeBot(LoggingMixin): if not_closed: if fully_cancelled or (order_obj and self.strategy.ft_check_timed_out( trade, order_obj, datetime.now(timezone.utc))): - self.handle_timedout_order(order, trade) + self.handle_cancel_order(order, trade, constants.CANCEL_REASON['TIMEOUT']) else: self.replace_order(order, order_obj, trade) - def handle_timedout_order(self, order: Dict, trade: Trade) -> None: + def handle_cancel_order(self, order: Dict, 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() @@ -1265,10 +1273,10 @@ class FreqtradeBot(LoggingMixin): :return: None """ if order['side'] == trade.entry_side: - self.handle_cancel_enter(trade, order, constants.CANCEL_REASON['TIMEOUT']) + self.handle_cancel_enter(trade, order, reason) else: canceled = self.handle_cancel_exit( - trade, order, constants.CANCEL_REASON['TIMEOUT']) + trade, order, reason) canceled_count = trade.get_exit_order_count() max_timeouts = self.config.get('unfilledtimeout', {}).get('exit_timeout_count', 0) if canceled and max_timeouts > 0 and canceled_count >= max_timeouts: @@ -1626,7 +1634,7 @@ class FreqtradeBot(LoggingMixin): # second condition is for mypy only; order will always be passed during sub trade if sub_trade and order is not None: - amount = order.safe_filled if fill else order.amount + amount = order.safe_filled if fill else order.safe_amount order_rate: float = order.safe_price profit = trade.calc_profit(rate=order_rate, amount=amount, open_rate=trade.open_rate) @@ -1789,6 +1797,7 @@ class FreqtradeBot(LoggingMixin): is_short=trade.is_short, amount=trade.amount, stake_amount=trade.stake_amount, + leverage=trade.leverage, wallet_balance=trade.stake_amount, )) diff --git a/freqtrade/leverage/__init__.py b/freqtrade/leverage/__init__.py index ae78f4722..d4526dbec 100644 --- a/freqtrade/leverage/__init__.py +++ b/freqtrade/leverage/__init__.py @@ -1,2 +1 @@ -# flake8: noqa: F401 -from freqtrade.leverage.interest import interest +from freqtrade.leverage.interest import interest # noqa: F401 diff --git a/freqtrade/loggers.py b/freqtrade/loggers.py index f365053c9..823fa174e 100644 --- a/freqtrade/loggers.py +++ b/freqtrade/loggers.py @@ -103,9 +103,9 @@ def setup_logging(config: Config) -> None: logging.root.addHandler(handler_sl) elif s[0] == 'journald': # pragma: no cover try: - from systemd.journal import JournaldLogHandler + from cysystemd.journal import JournaldLogHandler except ImportError: - raise OperationalException("You need the systemd python package be installed in " + raise OperationalException("You need the cysystemd python package be installed in " "order to use logging to journald.") handler_jd = get_existing_handlers(JournaldLogHandler) if handler_jd: diff --git a/freqtrade/mixins/__init__.py b/freqtrade/mixins/__init__.py index f4a640fa3..c5363c076 100644 --- a/freqtrade/mixins/__init__.py +++ b/freqtrade/mixins/__init__.py @@ -1,2 +1 @@ -# flake8: noqa: F401 -from freqtrade.mixins.logging_mixin import LoggingMixin +from freqtrade.mixins.logging_mixin import LoggingMixin # noqa: F401 diff --git a/freqtrade/optimize/backtesting.py b/freqtrade/optimize/backtesting.py index 0d5237960..065a88f40 100644 --- a/freqtrade/optimize/backtesting.py +++ b/freqtrade/optimize/backtesting.py @@ -868,6 +868,7 @@ class Backtesting: open_rate=propose_rate, amount=amount, stake_amount=trade.stake_amount, + leverage=trade.leverage, wallet_balance=trade.stake_amount, is_short=is_short, )) diff --git a/freqtrade/optimize/hyperopt_loss/hyperopt_loss_sharpe_daily.py b/freqtrade/optimize/hyperopt_loss/hyperopt_loss_sharpe_daily.py index 9520123ee..88c97989a 100644 --- a/freqtrade/optimize/hyperopt_loss/hyperopt_loss_sharpe_daily.py +++ b/freqtrade/optimize/hyperopt_loss/hyperopt_loss_sharpe_daily.py @@ -44,7 +44,7 @@ class SharpeHyperOptLossDaily(IHyperOptLoss): sum_daily = ( results.resample(resample_freq, on='close_date').agg( - {"profit_ratio_after_slippage": sum}).reindex(t_index).fillna(0) + {"profit_ratio_after_slippage": 'sum'}).reindex(t_index).fillna(0) ) total_profit = sum_daily["profit_ratio_after_slippage"] - risk_free_rate diff --git a/freqtrade/optimize/hyperopt_loss/hyperopt_loss_sortino_daily.py b/freqtrade/optimize/hyperopt_loss/hyperopt_loss_sortino_daily.py index fac96664d..f5fe4590e 100644 --- a/freqtrade/optimize/hyperopt_loss/hyperopt_loss_sortino_daily.py +++ b/freqtrade/optimize/hyperopt_loss/hyperopt_loss_sortino_daily.py @@ -46,7 +46,7 @@ class SortinoHyperOptLossDaily(IHyperOptLoss): sum_daily = ( results.resample(resample_freq, on='close_date').agg( - {"profit_ratio_after_slippage": sum}).reindex(t_index).fillna(0) + {"profit_ratio_after_slippage": 'sum'}).reindex(t_index).fillna(0) ) total_profit = sum_daily["profit_ratio_after_slippage"] - minimum_acceptable_return diff --git a/freqtrade/optimize/hyperopt_tools.py b/freqtrade/optimize/hyperopt_tools.py old mode 100755 new mode 100644 diff --git a/freqtrade/optimize/space/__init__.py b/freqtrade/optimize/space/__init__.py index bbdac4ab9..6c59a4d8f 100644 --- a/freqtrade/optimize/space/__init__.py +++ b/freqtrade/optimize/space/__init__.py @@ -1,4 +1,3 @@ -# flake8: noqa: F401 -from skopt.space import Categorical, Dimension, Integer, Real +from skopt.space import Categorical, Dimension, Integer, Real # noqa: F401 -from .decimalspace import SKDecimal +from .decimalspace import SKDecimal # noqa: F401 diff --git a/freqtrade/persistence/pairlock.py b/freqtrade/persistence/pairlock.py index 926c641b0..938cd14bc 100644 --- a/freqtrade/persistence/pairlock.py +++ b/freqtrade/persistence/pairlock.py @@ -21,9 +21,9 @@ class PairLock(_DECL_BASE): side = Column(String(25), nullable=False, default="*") reason = Column(String(255), nullable=True) # Time the pair was locked (start time) - lock_time = Column(DateTime, nullable=False) + lock_time = Column(DateTime(), nullable=False) # Time until the pair is locked (end time) - lock_end_time = Column(DateTime, nullable=False, index=True) + lock_end_time = Column(DateTime(), nullable=False, index=True) active = Column(Boolean, nullable=False, default=True, index=True) diff --git a/freqtrade/persistence/trade_model.py b/freqtrade/persistence/trade_model.py index daeccb216..c84fcec9e 100644 --- a/freqtrade/persistence/trade_model.py +++ b/freqtrade/persistence/trade_model.py @@ -46,31 +46,31 @@ class Order(_DECL_BASE): trade = relationship("Trade", back_populates="orders") # order_side can only be 'buy', 'sell' or 'stoploss' - ft_order_side: str = Column(String(25), nullable=False) - ft_pair: str = Column(String(25), nullable=False) + ft_order_side = Column(String(25), nullable=False) + ft_pair = Column(String(25), nullable=False) ft_is_open = Column(Boolean, nullable=False, default=True, index=True) - ft_amount = Column(Float, nullable=False) - ft_price = Column(Float, nullable=False) + ft_amount = Column(Float(), nullable=False) + ft_price = Column(Float(), nullable=False) - order_id: str = Column(String(255), nullable=False, index=True) + order_id = Column(String(255), nullable=False, index=True) status = Column(String(255), nullable=True) symbol = Column(String(25), nullable=True) - order_type: str = Column(String(50), nullable=True) + order_type = Column(String(50), nullable=True) side = Column(String(25), nullable=True) - price = Column(Float, nullable=True) - average = Column(Float, nullable=True) - amount = Column(Float, nullable=True) - filled = Column(Float, nullable=True) - remaining = Column(Float, nullable=True) - cost = Column(Float, nullable=True) - stop_price = Column(Float, nullable=True) - order_date = Column(DateTime, nullable=True, default=datetime.utcnow) - order_filled_date = Column(DateTime, nullable=True) - order_update_date = Column(DateTime, nullable=True) + price = Column(Float(), nullable=True) + average = Column(Float(), nullable=True) + amount = Column(Float(), nullable=True) + filled = Column(Float(), nullable=True) + remaining = Column(Float(), nullable=True) + cost = Column(Float(), nullable=True) + stop_price = Column(Float(), nullable=True) + order_date = Column(DateTime(), nullable=True, default=datetime.utcnow) + order_filled_date = Column(DateTime(), nullable=True) + order_update_date = Column(DateTime(), nullable=True) - funding_fee = Column(Float, nullable=True) + funding_fee = Column(Float(), nullable=True) - ft_fee_base = Column(Float, nullable=True) + ft_fee_base = Column(Float(), nullable=True) @property def order_date_utc(self) -> datetime: @@ -151,7 +151,7 @@ class Order(_DECL_BASE): self.order_update_date = datetime.now(timezone.utc) def to_ccxt_object(self) -> Dict[str, Any]: - return { + order = { 'id': self.order_id, 'symbol': self.ft_pair, 'price': self.price, @@ -169,10 +169,13 @@ class Order(_DECL_BASE): 'fee': None, 'info': {}, } + if self.ft_order_side == 'stoploss': + order['ft_order_type'] = 'stoploss' + return order def to_json(self, entry_side: str, minified: bool = False) -> Dict[str, Any]: resp = { - 'amount': self.amount, + 'amount': self.safe_amount, 'safe_price': self.safe_price, 'ft_order_side': self.ft_order_side, 'order_filled_timestamp': int(self.order_filled_date.replace( @@ -1177,44 +1180,44 @@ class Trade(_DECL_BASE, LocalTrade): base_currency = Column(String(25), nullable=True) stake_currency = Column(String(25), nullable=True) is_open = Column(Boolean, nullable=False, default=True, index=True) - fee_open = Column(Float, nullable=False, default=0.0) - fee_open_cost = Column(Float, nullable=True) + fee_open = Column(Float(), nullable=False, default=0.0) + fee_open_cost = Column(Float(), nullable=True) fee_open_currency = Column(String(25), nullable=True) - fee_close = Column(Float, nullable=False, default=0.0) - fee_close_cost = Column(Float, nullable=True) + fee_close = Column(Float(), nullable=False, default=0.0) + fee_close_cost = Column(Float(), nullable=True) fee_close_currency = Column(String(25), nullable=True) - open_rate: float = Column(Float) - open_rate_requested = Column(Float) + open_rate: float = Column(Float()) + open_rate_requested = Column(Float()) # open_trade_value - calculated via _calc_open_trade_value - open_trade_value = Column(Float) - close_rate: Optional[float] = Column(Float) - close_rate_requested = Column(Float) - realized_profit = Column(Float, default=0.0) - close_profit = Column(Float) - close_profit_abs = Column(Float) - stake_amount = Column(Float, nullable=False) - max_stake_amount = Column(Float) - amount = Column(Float) - amount_requested = Column(Float) - open_date = Column(DateTime, nullable=False, default=datetime.utcnow) - close_date = Column(DateTime) + open_trade_value = Column(Float()) + close_rate: Optional[float] = Column(Float()) + close_rate_requested = Column(Float()) + realized_profit = Column(Float(), default=0.0) + close_profit = Column(Float()) + close_profit_abs = Column(Float()) + stake_amount = Column(Float(), nullable=False) + max_stake_amount = Column(Float()) + amount = Column(Float()) + amount_requested = Column(Float()) + open_date = Column(DateTime(), nullable=False, default=datetime.utcnow) + close_date = Column(DateTime()) open_order_id = Column(String(255)) # absolute value of the stop loss - stop_loss = Column(Float, nullable=True, default=0.0) + stop_loss = Column(Float(), nullable=True, default=0.0) # percentage value of the stop loss - stop_loss_pct = Column(Float, nullable=True) + stop_loss_pct = Column(Float(), nullable=True) # absolute value of the initial stop loss - initial_stop_loss = Column(Float, nullable=True, default=0.0) + initial_stop_loss = Column(Float(), nullable=True, default=0.0) # percentage value of the initial stop loss - initial_stop_loss_pct = Column(Float, nullable=True) + initial_stop_loss_pct = Column(Float(), nullable=True) # stoploss order id which is on exchange stoploss_order_id = Column(String(255), nullable=True, index=True) # last update time of the stoploss order on exchange - stoploss_last_update = Column(DateTime, nullable=True) + stoploss_last_update = Column(DateTime(), nullable=True) # absolute value of the highest reached price - max_rate = Column(Float, nullable=True, default=0.0) + max_rate = Column(Float(), nullable=True, default=0.0) # Lowest price reached - min_rate = Column(Float, nullable=True) + min_rate = Column(Float(), nullable=True) exit_reason = Column(String(100), nullable=True) exit_order_status = Column(String(100), nullable=True) strategy = Column(String(100), nullable=True) @@ -1222,21 +1225,21 @@ class Trade(_DECL_BASE, LocalTrade): timeframe = Column(Integer, nullable=True) trading_mode = Column(Enum(TradingMode), nullable=True) - amount_precision = Column(Float, nullable=True) - price_precision = Column(Float, nullable=True) + amount_precision = Column(Float(), nullable=True) + price_precision = Column(Float(), nullable=True) precision_mode = Column(Integer, nullable=True) - contract_size = Column(Float, nullable=True) + contract_size = Column(Float(), nullable=True) # Leverage trading properties - leverage = Column(Float, nullable=True, default=1.0) + leverage = Column(Float(), nullable=True, default=1.0) is_short = Column(Boolean, nullable=False, default=False) - liquidation_price = Column(Float, nullable=True) + liquidation_price = Column(Float(), nullable=True) # Margin Trading Properties - interest_rate = Column(Float, nullable=False, default=0.0) + interest_rate = Column(Float(), nullable=False, default=0.0) # Futures properties - funding_fees = Column(Float, nullable=True, default=None) + funding_fees = Column(Float(), nullable=True, default=None) def __init__(self, **kwargs): super().__init__(**kwargs) diff --git a/freqtrade/rpc/__init__.py b/freqtrade/rpc/__init__.py index 957565e2c..07f83abc0 100644 --- a/freqtrade/rpc/__init__.py +++ b/freqtrade/rpc/__init__.py @@ -1,3 +1,2 @@ -# flake8: noqa: F401 -from .rpc import RPC, RPCException, RPCHandler -from .rpc_manager import RPCManager +from .rpc import RPC, RPCException, RPCHandler # noqa: F401 +from .rpc_manager import RPCManager # noqa: F401 diff --git a/freqtrade/rpc/api_server/__init__.py b/freqtrade/rpc/api_server/__init__.py index df255c186..b2ed3e6e0 100644 --- a/freqtrade/rpc/api_server/__init__.py +++ b/freqtrade/rpc/api_server/__init__.py @@ -1,2 +1 @@ -# flake8: noqa: F401 -from .webserver import ApiServer +from .webserver import ApiServer # noqa: F401 diff --git a/freqtrade/rpc/api_server/api_backtest.py b/freqtrade/rpc/api_server/api_backtest.py index bc2a40d91..d9d7a27f1 100644 --- a/freqtrade/rpc/api_server/api_backtest.py +++ b/freqtrade/rpc/api_server/api_backtest.py @@ -10,7 +10,7 @@ from fastapi.exceptions import HTTPException from freqtrade.configuration.config_validation import validate_config_consistency from freqtrade.data.btanalysis import get_backtest_resultlist, load_and_merge_backtest_result from freqtrade.enums import BacktestState -from freqtrade.exceptions import DependencyException +from freqtrade.exceptions import DependencyException, OperationalException from freqtrade.misc import deep_merge_dicts from freqtrade.rpc.api_server.api_schemas import (BacktestHistoryEntry, BacktestRequest, BacktestResponse) @@ -26,9 +26,10 @@ router = APIRouter() @router.post('/backtest', response_model=BacktestResponse, tags=['webserver', 'backtest']) -# flake8: noqa: C901 -async def api_start_backtest(bt_settings: BacktestRequest, background_tasks: BackgroundTasks, - config=Depends(get_config), ws_mode=Depends(is_webserver_mode)): +async def api_start_backtest( # noqa: C901 + bt_settings: BacktestRequest, background_tasks: BackgroundTasks, + config=Depends(get_config), ws_mode=Depends(is_webserver_mode)): + ApiServer._bt['bt_error'] = None """Start backtesting if not done so already""" if ApiServer._bgtask_running: raise RPCException('Bot Background task already running') @@ -60,30 +61,31 @@ async def api_start_backtest(bt_settings: BacktestRequest, background_tasks: Bac asyncio.set_event_loop(asyncio.new_event_loop()) try: # Reload strategy - lastconfig = ApiServer._bt_last_config + lastconfig = ApiServer._bt['last_config'] strat = StrategyResolver.load_strategy(btconfig) validate_config_consistency(btconfig) if ( - not ApiServer._bt + not ApiServer._bt['bt'] or lastconfig.get('timeframe') != strat.timeframe or lastconfig.get('timeframe_detail') != btconfig.get('timeframe_detail') or lastconfig.get('timerange') != btconfig['timerange'] ): from freqtrade.optimize.backtesting import Backtesting - ApiServer._bt = Backtesting(btconfig) - ApiServer._bt.load_bt_data_detail() + ApiServer._bt['bt'] = Backtesting(btconfig) + ApiServer._bt['bt'].load_bt_data_detail() else: - ApiServer._bt.config = btconfig - ApiServer._bt.init_backtest() + ApiServer._bt['bt'].config = btconfig + ApiServer._bt['bt'].init_backtest() # Only reload data if timeframe changed. if ( - not ApiServer._bt_data - or not ApiServer._bt_timerange + not ApiServer._bt['data'] + or not ApiServer._bt['timerange'] or lastconfig.get('timeframe') != strat.timeframe or lastconfig.get('timerange') != btconfig['timerange'] ): - ApiServer._bt_data, ApiServer._bt_timerange = ApiServer._bt.load_bt_data() + ApiServer._bt['data'], ApiServer._bt['timerange'] = ApiServer._bt[ + 'bt'].load_bt_data() lastconfig['timerange'] = btconfig['timerange'] lastconfig['timeframe'] = strat.timeframe @@ -91,34 +93,35 @@ async def api_start_backtest(bt_settings: BacktestRequest, background_tasks: Bac lastconfig['enable_protections'] = btconfig.get('enable_protections') lastconfig['dry_run_wallet'] = btconfig.get('dry_run_wallet') - ApiServer._bt.enable_protections = btconfig.get('enable_protections', False) - ApiServer._bt.strategylist = [strat] - ApiServer._bt.results = {} - ApiServer._bt.load_prior_backtest() + ApiServer._bt['bt'].enable_protections = btconfig.get('enable_protections', False) + ApiServer._bt['bt'].strategylist = [strat] + ApiServer._bt['bt'].results = {} + ApiServer._bt['bt'].load_prior_backtest() - ApiServer._bt.abort = False - if (ApiServer._bt.results and - strat.get_strategy_name() in ApiServer._bt.results['strategy']): + ApiServer._bt['bt'].abort = False + if (ApiServer._bt['bt'].results and + strat.get_strategy_name() in ApiServer._bt['bt'].results['strategy']): # When previous result hash matches - reuse that result and skip backtesting. logger.info(f'Reusing result of previous backtest for {strat.get_strategy_name()}') else: - min_date, max_date = ApiServer._bt.backtest_one_strategy( - strat, ApiServer._bt_data, ApiServer._bt_timerange) + min_date, max_date = ApiServer._bt['bt'].backtest_one_strategy( + strat, ApiServer._bt['data'], ApiServer._bt['timerange']) - ApiServer._bt.results = generate_backtest_stats( - ApiServer._bt_data, ApiServer._bt.all_results, + ApiServer._bt['bt'].results = generate_backtest_stats( + ApiServer._bt['data'], ApiServer._bt['bt'].all_results, min_date=min_date, max_date=max_date) if btconfig.get('export', 'none') == 'trades': store_backtest_stats( - btconfig['exportfilename'], ApiServer._bt.results, + btconfig['exportfilename'], ApiServer._bt['bt'].results, datetime.now().strftime("%Y-%m-%d_%H-%M-%S") ) logger.info("Backtest finished.") - except DependencyException as e: - logger.info(f"Backtesting caused an error: {e}") + except (Exception, OperationalException, DependencyException) as e: + logger.exception(f"Backtesting caused an error: {e}") + ApiServer._bt['bt_error'] = str(e) pass finally: ApiServer._bgtask_running = False @@ -146,13 +149,14 @@ def api_get_backtest(ws_mode=Depends(is_webserver_mode)): return { "status": "running", "running": True, - "step": ApiServer._bt.progress.action if ApiServer._bt else str(BacktestState.STARTUP), - "progress": ApiServer._bt.progress.progress if ApiServer._bt else 0, + "step": (ApiServer._bt['bt'].progress.action if ApiServer._bt['bt'] + else str(BacktestState.STARTUP)), + "progress": ApiServer._bt['bt'].progress.progress if ApiServer._bt['bt'] else 0, "trade_count": len(LocalTrade.trades), "status_msg": "Backtest running", } - if not ApiServer._bt: + if not ApiServer._bt['bt']: return { "status": "not_started", "running": False, @@ -160,6 +164,14 @@ def api_get_backtest(ws_mode=Depends(is_webserver_mode)): "progress": 0, "status_msg": "Backtest not yet executed" } + if ApiServer._bt['bt_error']: + return { + "status": "error", + "running": False, + "step": "", + "progress": 0, + "status_msg": f"Backtest failed with {ApiServer._bt['bt_error']}" + } return { "status": "ended", @@ -167,7 +179,7 @@ def api_get_backtest(ws_mode=Depends(is_webserver_mode)): "status_msg": "Backtest ended", "step": "finished", "progress": 1, - "backtest_result": ApiServer._bt.results, + "backtest_result": ApiServer._bt['bt'].results, } @@ -182,12 +194,12 @@ def api_delete_backtest(ws_mode=Depends(is_webserver_mode)): "progress": 0, "status_msg": "Backtest running", } - if ApiServer._bt: - ApiServer._bt.cleanup() - del ApiServer._bt - ApiServer._bt = None - del ApiServer._bt_data - ApiServer._bt_data = None + if ApiServer._bt['bt']: + ApiServer._bt['bt'].cleanup() + del ApiServer._bt['bt'] + ApiServer._bt['bt'] = None + del ApiServer._bt['data'] + ApiServer._bt['data'] = None logger.info("Backtesting reset") return { "status": "reset", @@ -208,7 +220,7 @@ def api_backtest_abort(ws_mode=Depends(is_webserver_mode)): "progress": 0, "status_msg": "Backtest ended", } - ApiServer._bt.abort = True + ApiServer._bt['bt'].abort = True return { "status": "stopping", "running": False, @@ -218,14 +230,17 @@ def api_backtest_abort(ws_mode=Depends(is_webserver_mode)): } -@router.get('/backtest/history', response_model=List[BacktestHistoryEntry], tags=['webserver', 'backtest']) +@router.get('/backtest/history', response_model=List[BacktestHistoryEntry], + tags=['webserver', 'backtest']) def api_backtest_history(config=Depends(get_config), ws_mode=Depends(is_webserver_mode)): # Get backtest result history, read from metadata files return get_backtest_resultlist(config['user_data_dir'] / 'backtest_results') -@router.get('/backtest/history/result', response_model=BacktestResponse, tags=['webserver', 'backtest']) -def api_backtest_history_result(filename: str, strategy: str, config=Depends(get_config), ws_mode=Depends(is_webserver_mode)): +@router.get('/backtest/history/result', response_model=BacktestResponse, + tags=['webserver', 'backtest']) +def api_backtest_history_result(filename: str, strategy: str, config=Depends(get_config), + ws_mode=Depends(is_webserver_mode)): # Get backtest result history, read from metadata files fn = config['user_data_dir'] / 'backtest_results' / filename results: Dict[str, Any] = { diff --git a/freqtrade/rpc/api_server/api_schemas.py b/freqtrade/rpc/api_server/api_schemas.py index d96055b69..58f6ad583 100644 --- a/freqtrade/rpc/api_server/api_schemas.py +++ b/freqtrade/rpc/api_server/api_schemas.py @@ -168,6 +168,7 @@ class ShowConfig(BaseModel): max_open_trades: IntOrInf minimal_roi: Dict[str, Any] stoploss: Optional[float] + stoploss_on_exchange: bool trailing_stop: Optional[bool] trailing_stop_positive: Optional[float] trailing_stop_positive_offset: Optional[float] diff --git a/freqtrade/rpc/api_server/api_v1.py b/freqtrade/rpc/api_server/api_v1.py index b64f6c0e8..73bdde86b 100644 --- a/freqtrade/rpc/api_server/api_v1.py +++ b/freqtrade/rpc/api_server/api_v1.py @@ -41,7 +41,8 @@ logger = logging.getLogger(__name__) # 2.21: Add new_candle messagetype # 2.22: Add FreqAI to backtesting # 2.23: Allow plot config request in webserver mode -API_VERSION = 2.23 +# 2.24: Add cancel_open_order endpoint +API_VERSION = 2.24 # Public API, requires no auth. router_public = APIRouter() @@ -123,6 +124,12 @@ def trades_delete(tradeid: int, rpc: RPC = Depends(get_rpc)): return rpc._rpc_delete(tradeid) +@router.delete('/trades/{tradeid}/open-order', response_model=OpenTradeSchema, tags=['trading']) +def cancel_open_order(tradeid: int, rpc: RPC = Depends(get_rpc)): + rpc._rpc_cancel_open_order(tradeid) + return rpc._rpc_trade_status([tradeid])[0] + + # TODO: Missing response model @router.get('/edge', tags=['info']) def edge(rpc: RPC = Depends(get_rpc)): diff --git a/freqtrade/rpc/api_server/api_ws.py b/freqtrade/rpc/api_server/api_ws.py index 18714f15f..b253d66c2 100644 --- a/freqtrade/rpc/api_server/api_ws.py +++ b/freqtrade/rpc/api_server/api_ws.py @@ -90,7 +90,7 @@ async def _process_consumer_request( elif type == RPCRequestType.ANALYZED_DF: # Limit the amount of candles per dataframe to 'limit' or 1500 - limit = min(data.get('limit', 1500), 1500) if data else None + limit = int(min(data.get('limit', 1500), 1500)) if data else None pair = data.get('pair', None) if data else None # For every pair in the generator, send a separate message diff --git a/freqtrade/rpc/api_server/webserver.py b/freqtrade/rpc/api_server/webserver.py index 92bded1c5..b53662451 100644 --- a/freqtrade/rpc/api_server/webserver.py +++ b/freqtrade/rpc/api_server/webserver.py @@ -36,10 +36,13 @@ class ApiServer(RPCHandler): _rpc: RPC # Backtesting type: Backtesting - _bt = None - _bt_data = None - _bt_timerange = None - _bt_last_config: Config = {} + _bt: Dict[str, Any] = { + 'bt': None, + 'data': None, + 'timerange': None, + 'last_config': {}, + 'bt_error': None, + } _has_rpc: bool = False _bgtask_running: bool = False _config: Config = {} diff --git a/freqtrade/rpc/api_server/ws/__init__.py b/freqtrade/rpc/api_server/ws/__init__.py index 0b94d3fee..b76428119 100644 --- a/freqtrade/rpc/api_server/ws/__init__.py +++ b/freqtrade/rpc/api_server/ws/__init__.py @@ -1,7 +1,6 @@ -# flake8: noqa: F401 # isort: off -from freqtrade.rpc.api_server.ws.types import WebSocketType -from freqtrade.rpc.api_server.ws.proxy import WebSocketProxy -from freqtrade.rpc.api_server.ws.serializer import HybridJSONWebSocketSerializer -from freqtrade.rpc.api_server.ws.channel import WebSocketChannel -from freqtrade.rpc.api_server.ws.message_stream import MessageStream +from freqtrade.rpc.api_server.ws.types import WebSocketType # noqa: F401 +from freqtrade.rpc.api_server.ws.proxy import WebSocketProxy # noqa: F401 +from freqtrade.rpc.api_server.ws.serializer import HybridJSONWebSocketSerializer # noqa: F401 +from freqtrade.rpc.api_server.ws.channel import WebSocketChannel # noqa: F401 +from freqtrade.rpc.api_server.ws.message_stream import MessageStream # noqa: F401 diff --git a/freqtrade/rpc/rpc.py b/freqtrade/rpc/rpc.py index f1dd3fe85..83bffb779 100644 --- a/freqtrade/rpc/rpc.py +++ b/freqtrade/rpc/rpc.py @@ -122,6 +122,7 @@ class RPC: if config['max_open_trades'] != float('inf') else -1), 'minimal_roi': config['minimal_roi'].copy() if 'minimal_roi' in config else {}, 'stoploss': config.get('stoploss'), + 'stoploss_on_exchange': config.get('stoploss_on_exchange', False), 'trailing_stop': config.get('trailing_stop'), 'trailing_stop_positive': config.get('trailing_stop_positive'), 'trailing_stop_positive_offset': config.get('trailing_stop_positive_offset'), @@ -812,6 +813,29 @@ class RPC: else: raise RPCException(f'Failed to enter position for {pair}.') + def _rpc_cancel_open_order(self, trade_id: int): + if self._freqtrade.state != State.RUNNING: + raise RPCException('trader is not running') + with self._freqtrade._exit_lock: + # Query for trade + trade = Trade.get_trades( + trade_filter=[Trade.id == trade_id, Trade.is_open.is_(True), ] + ).first() + if not trade: + logger.warning('cancel_open_order: Invalid trade_id received.') + raise RPCException('Invalid trade_id.') + if not trade.open_order_id: + logger.warning('cancel_open_order: No open order for trade_id.') + raise RPCException('No open order for trade_id.') + + try: + order = self._freqtrade.exchange.fetch_order(trade.open_order_id, trade.pair) + except ExchangeError as e: + logger.info(f"Cannot query order for {trade} due to {e}.", exc_info=True) + raise RPCException("Order not found.") + self._freqtrade.handle_cancel_order(order, trade, CANCEL_REASON['USER_CANCEL']) + Trade.commit() + def _rpc_delete(self, trade_id: int) -> Dict[str, Union[str, int]]: """ Handler for delete . diff --git a/freqtrade/rpc/telegram.py b/freqtrade/rpc/telegram.py index c02a4000a..fbd675d02 100644 --- a/freqtrade/rpc/telegram.py +++ b/freqtrade/rpc/telegram.py @@ -174,6 +174,7 @@ class Telegram(RPCHandler): self._force_enter, order_side=SignalDirection.SHORT)), CommandHandler('trades', self._trades), CommandHandler('delete', self._delete_trade), + CommandHandler(['coo', 'cancel_open_order'], self._cancel_open_order), CommandHandler('performance', self._performance), CommandHandler(['buys', 'entries'], self._enter_tag_performance), CommandHandler(['sells', 'exits'], self._exit_reason_performance), @@ -1144,10 +1145,25 @@ class Telegram(RPCHandler): raise RPCException("Trade-id not set.") trade_id = int(context.args[0]) msg = self._rpc._rpc_delete(trade_id) - self._send_msg(( + self._send_msg( f"`{msg['result_msg']}`\n" 'Please make sure to take care of this asset on the exchange manually.' - )) + ) + + @authorized_only + def _cancel_open_order(self, update: Update, context: CallbackContext) -> None: + """ + Handler for /cancel_open_order . + Cancel open order for tradeid + :param bot: telegram bot + :param update: message update + :return: None + """ + if not context.args or len(context.args) == 0: + raise RPCException("Trade-id not set.") + trade_id = int(context.args[0]) + self._rpc._rpc_cancel_open_order(trade_id) + self._send_msg('Open order canceled.') @authorized_only def _performance(self, update: Update, context: CallbackContext) -> None: @@ -1456,6 +1472,10 @@ class Telegram(RPCHandler): "*/fx |all:* `Alias to /forceexit`\n" f"{force_enter_text if self._config.get('force_entry_enable', False) else ''}" "*/delete :* `Instantly delete the given trade in the database`\n" + "*/cancel_open_order :* `Cancels open orders for trade. " + "Only valid when the trade has open orders.`\n" + "*/coo |all:* `Alias to /cancel_open_order`\n" + "*/whitelist [sorted] [baseonly]:* `Show current whitelist. Optionally in " "order and/or only displaying the base currency of each pairing.`\n" "*/blacklist [pair]:* `Show current blacklist, or adds one or more pairs " diff --git a/freqtrade/strategy/hyper.py b/freqtrade/strategy/hyper.py index 2be1d7e86..52ba22951 100644 --- a/freqtrade/strategy/hyper.py +++ b/freqtrade/strategy/hyper.py @@ -163,7 +163,7 @@ class HyperStrategyMixin: else: logger.info(f'Strategy Parameter(default): {attr_name} = {attr.value}') - def get_no_optimize_params(self): + def get_no_optimize_params(self) -> Dict[str, Dict]: """ Returns list of Parameters that are not part of the current optimize job """ @@ -173,7 +173,7 @@ class HyperStrategyMixin: 'protection': {}, } for name, p in self.enumerate_parameters(): - if not p.optimize or not p.in_space: + if p.category and (not p.optimize or not p.in_space): params[p.category][name] = p.value return params diff --git a/freqtrade/strategy/interface.py b/freqtrade/strategy/interface.py index fce4e629e..1f687c196 100644 --- a/freqtrade/strategy/interface.py +++ b/freqtrade/strategy/interface.py @@ -614,8 +614,8 @@ class IStrategy(ABC, HyperStrategyMixin): """ return df - def feature_engineering_expand_all(self, dataframe: DataFrame, - period: int, **kwargs): + def feature_engineering_expand_all(self, dataframe: DataFrame, period: int, + metadata: Dict, **kwargs): """ *Only functional with FreqAI enabled strategies* This function will automatically expand the defined features on the config defined @@ -634,13 +634,14 @@ class IStrategy(ABC, HyperStrategyMixin): https://www.freqtrade.io/en/latest/freqai-feature-engineering/#defining-the-features - :param df: strategy dataframe which will receive the features + :param dataframe: strategy dataframe which will receive the features :param period: period of the indicator - usage example: + :param metadata: metadata of current pair dataframe["%-ema-period"] = ta.EMA(dataframe, timeperiod=period) """ return dataframe - def feature_engineering_expand_basic(self, dataframe: DataFrame, **kwargs): + def feature_engineering_expand_basic(self, dataframe: DataFrame, metadata: Dict, **kwargs): """ *Only functional with FreqAI enabled strategies* This function will automatically expand the defined features on the config defined @@ -662,13 +663,14 @@ class IStrategy(ABC, HyperStrategyMixin): https://www.freqtrade.io/en/latest/freqai-feature-engineering/#defining-the-features - :param df: strategy dataframe which will receive the features + :param dataframe: strategy dataframe which will receive the features + :param metadata: metadata of current pair dataframe["%-pct-change"] = dataframe["close"].pct_change() dataframe["%-ema-200"] = ta.EMA(dataframe, timeperiod=200) """ return dataframe - def feature_engineering_standard(self, dataframe: DataFrame, **kwargs): + def feature_engineering_standard(self, dataframe: DataFrame, metadata: Dict, **kwargs): """ *Only functional with FreqAI enabled strategies* This optional function will be called once with the dataframe of the base timeframe. @@ -686,12 +688,13 @@ class IStrategy(ABC, HyperStrategyMixin): https://www.freqtrade.io/en/latest/freqai-feature-engineering - :param df: strategy dataframe which will receive the features + :param dataframe: strategy dataframe which will receive the features + :param metadata: metadata of current pair usage example: dataframe["%-day_of_week"] = (dataframe["date"].dt.dayofweek + 1) / 7 """ return dataframe - def set_freqai_targets(self, dataframe, **kwargs): + def set_freqai_targets(self, dataframe: DataFrame, metadata: Dict, **kwargs): """ *Only functional with FreqAI enabled strategies* Required function to set the targets for the model. @@ -701,7 +704,8 @@ class IStrategy(ABC, HyperStrategyMixin): https://www.freqtrade.io/en/latest/freqai-feature-engineering - :param df: strategy dataframe which will receive the targets + :param dataframe: strategy dataframe which will receive the targets + :param metadata: metadata of current pair usage example: dataframe["&-target"] = dataframe["close"].shift(-1) / dataframe["close"] """ return dataframe @@ -1079,10 +1083,10 @@ class IStrategy(ABC, HyperStrategyMixin): trade.adjust_min_max_rates(high or current_rate, low or current_rate) - stoplossflag = self.stop_loss_reached(current_rate=current_rate, trade=trade, - current_time=current_time, - current_profit=current_profit, - force_stoploss=force_stoploss, low=low, high=high) + stoplossflag = self.ft_stoploss_reached(current_rate=current_rate, trade=trade, + current_time=current_time, + current_profit=current_profit, + force_stoploss=force_stoploss, low=low, high=high) # Set current rate to high for backtesting exits current_rate = (low if trade.is_short else high) or rate @@ -1149,13 +1153,12 @@ class IStrategy(ABC, HyperStrategyMixin): return exits - def stop_loss_reached(self, current_rate: float, trade: Trade, - current_time: datetime, current_profit: float, - force_stoploss: float, low: Optional[float] = None, - high: Optional[float] = None) -> ExitCheckTuple: + def ft_stoploss_adjust(self, current_rate: float, trade: Trade, + current_time: datetime, current_profit: float, + force_stoploss: float, low: Optional[float] = None, + high: Optional[float] = None) -> None: """ - Based on current profit of the trade and configured (trailing) stoploss, - decides to exit or not + Adjust stop-loss dynamically if configured to do so. :param current_profit: current profit as ratio :param low: Low value of this candle, only set in backtesting :param high: High value of this candle, only set in backtesting @@ -1201,6 +1204,20 @@ class IStrategy(ABC, HyperStrategyMixin): trade.adjust_stop_loss(bound or current_rate, stop_loss_value) + def ft_stoploss_reached(self, current_rate: float, trade: Trade, + current_time: datetime, current_profit: float, + force_stoploss: float, low: Optional[float] = None, + high: Optional[float] = None) -> ExitCheckTuple: + """ + Based on current profit of the trade and configured (trailing) stoploss, + decides to exit or not + :param current_profit: current profit as ratio + :param low: Low value of this candle, only set in backtesting + :param high: High value of this candle, only set in backtesting + """ + self.ft_stoploss_adjust(current_rate, trade, current_time, current_profit, + force_stoploss, low, high) + sl_higher_long = (trade.stop_loss >= (low or current_rate) and not trade.is_short) sl_lower_short = (trade.stop_loss <= (high or current_rate) and trade.is_short) liq_higher_long = (trade.liquidation_price diff --git a/freqtrade/templates/FreqaiExampleHybridStrategy.py b/freqtrade/templates/FreqaiExampleHybridStrategy.py index c5dbe8dbd..0e7113f8c 100644 --- a/freqtrade/templates/FreqaiExampleHybridStrategy.py +++ b/freqtrade/templates/FreqaiExampleHybridStrategy.py @@ -1,12 +1,13 @@ import logging +from typing import Dict -import numpy as np -import pandas as pd +import numpy as np # noqa +import pandas as pd # noqa import talib.abstract as ta from pandas import DataFrame from technical import qtpylib -from freqtrade.strategy import IntParameter, IStrategy, merge_informative_pair +from freqtrade.strategy import IntParameter, IStrategy, merge_informative_pair # noqa logger = logging.getLogger(__name__) @@ -26,7 +27,7 @@ class FreqaiExampleHybridStrategy(IStrategy): "freqai": { "enabled": true, - "purge_old_models": true, + "purge_old_models": 2, "train_period_days": 15, "identifier": "uniqe-id", "feature_parameters": { @@ -95,7 +96,8 @@ class FreqaiExampleHybridStrategy(IStrategy): short_rsi = IntParameter(low=51, high=100, default=70, space='sell', optimize=True, load=True) exit_short_rsi = IntParameter(low=1, high=50, default=30, space='buy', optimize=True, load=True) - def feature_engineering_expand_all(self, dataframe, period, **kwargs): + def feature_engineering_expand_all(self, dataframe: DataFrame, period: int, + metadata: Dict, **kwargs): """ *Only functional with FreqAI enabled strategies* This function will automatically expand the defined features on the config defined @@ -114,8 +116,9 @@ class FreqaiExampleHybridStrategy(IStrategy): https://www.freqtrade.io/en/latest/freqai-feature-engineering/#defining-the-features - :param df: strategy dataframe which will receive the features + :param dataframe: strategy dataframe which will receive the features :param period: period of the indicator - usage example: + :param metadata: metadata of current pair dataframe["%-ema-period"] = ta.EMA(dataframe, timeperiod=period) """ @@ -148,7 +151,7 @@ class FreqaiExampleHybridStrategy(IStrategy): return dataframe - def feature_engineering_expand_basic(self, dataframe, **kwargs): + def feature_engineering_expand_basic(self, dataframe: DataFrame, metadata: Dict, **kwargs): """ *Only functional with FreqAI enabled strategies* This function will automatically expand the defined features on the config defined @@ -170,7 +173,8 @@ class FreqaiExampleHybridStrategy(IStrategy): https://www.freqtrade.io/en/latest/freqai-feature-engineering/#defining-the-features - :param df: strategy dataframe which will receive the features + :param dataframe: strategy dataframe which will receive the features + :param metadata: metadata of current pair dataframe["%-pct-change"] = dataframe["close"].pct_change() dataframe["%-ema-200"] = ta.EMA(dataframe, timeperiod=200) """ @@ -179,7 +183,7 @@ class FreqaiExampleHybridStrategy(IStrategy): dataframe["%-raw_price"] = dataframe["close"] return dataframe - def feature_engineering_standard(self, dataframe, **kwargs): + def feature_engineering_standard(self, dataframe: DataFrame, metadata: Dict, **kwargs): """ *Only functional with FreqAI enabled strategies* This optional function will be called once with the dataframe of the base timeframe. @@ -197,14 +201,15 @@ class FreqaiExampleHybridStrategy(IStrategy): https://www.freqtrade.io/en/latest/freqai-feature-engineering - :param df: strategy dataframe which will receive the features + :param dataframe: strategy dataframe which will receive the features + :param metadata: metadata of current pair usage example: dataframe["%-day_of_week"] = (dataframe["date"].dt.dayofweek + 1) / 7 """ dataframe["%-day_of_week"] = dataframe["date"].dt.dayofweek dataframe["%-hour_of_day"] = dataframe["date"].dt.hour return dataframe - def set_freqai_targets(self, dataframe, **kwargs): + def set_freqai_targets(self, dataframe: DataFrame, metadata: Dict, **kwargs): """ *Only functional with FreqAI enabled strategies* Required function to set the targets for the model. @@ -214,16 +219,16 @@ class FreqaiExampleHybridStrategy(IStrategy): https://www.freqtrade.io/en/latest/freqai-feature-engineering - :param df: strategy dataframe which will receive the targets + :param dataframe: strategy dataframe which will receive the targets + :param metadata: metadata of current pair usage example: dataframe["&-target"] = dataframe["close"].shift(-1) / dataframe["close"] """ dataframe['&s-up_or_down'] = np.where(dataframe["close"].shift(-50) > - dataframe["close"], 'up', 'down') + dataframe["close"], 'up', 'down') return dataframe - # flake8: noqa: C901 - def populate_indicators(self, dataframe: DataFrame, metadata: dict) -> DataFrame: + def populate_indicators(self, dataframe: DataFrame, metadata: dict) -> DataFrame: # noqa: C901 # User creates their own custom strat here. Present example is a supertrend # based strategy. diff --git a/freqtrade/templates/FreqaiExampleStrategy.py b/freqtrade/templates/FreqaiExampleStrategy.py index 8e34d733e..0093c7f7a 100644 --- a/freqtrade/templates/FreqaiExampleStrategy.py +++ b/freqtrade/templates/FreqaiExampleStrategy.py @@ -1,5 +1,6 @@ import logging from functools import reduce +from typing import Dict import talib.abstract as ta from pandas import DataFrame @@ -46,7 +47,8 @@ class FreqaiExampleStrategy(IStrategy): std_dev_multiplier_sell = CategoricalParameter( [0.75, 1, 1.25, 1.5, 1.75], space="sell", default=1.25, optimize=True) - def feature_engineering_expand_all(self, dataframe, period, **kwargs): + def feature_engineering_expand_all(self, dataframe: DataFrame, period: int, + metadata: Dict, **kwargs): """ *Only functional with FreqAI enabled strategies* This function will automatically expand the defined features on the config defined @@ -58,6 +60,10 @@ class FreqaiExampleStrategy(IStrategy): All features must be prepended with `%` to be recognized by FreqAI internals. + Access metadata such as the current pair/timeframe with: + + `metadata["pair"]` `metadata["tf"]` + More details on how these config defined parameters accelerate feature engineering in the documentation at: @@ -65,8 +71,9 @@ class FreqaiExampleStrategy(IStrategy): https://www.freqtrade.io/en/latest/freqai-feature-engineering/#defining-the-features - :param df: strategy dataframe which will receive the features + :param dataframe: strategy dataframe which will receive the features :param period: period of the indicator - usage example: + :param metadata: metadata of current pair dataframe["%-ema-period"] = ta.EMA(dataframe, timeperiod=period) """ @@ -99,7 +106,7 @@ class FreqaiExampleStrategy(IStrategy): return dataframe - def feature_engineering_expand_basic(self, dataframe, **kwargs): + def feature_engineering_expand_basic(self, dataframe: DataFrame, metadata: Dict, **kwargs): """ *Only functional with FreqAI enabled strategies* This function will automatically expand the defined features on the config defined @@ -114,6 +121,10 @@ class FreqaiExampleStrategy(IStrategy): All features must be prepended with `%` to be recognized by FreqAI internals. + Access metadata such as the current pair/timeframe with: + + `metadata["pair"]` `metadata["tf"]` + More details on how these config defined parameters accelerate feature engineering in the documentation at: @@ -121,7 +132,8 @@ class FreqaiExampleStrategy(IStrategy): https://www.freqtrade.io/en/latest/freqai-feature-engineering/#defining-the-features - :param df: strategy dataframe which will receive the features + :param dataframe: strategy dataframe which will receive the features + :param metadata: metadata of current pair dataframe["%-pct-change"] = dataframe["close"].pct_change() dataframe["%-ema-200"] = ta.EMA(dataframe, timeperiod=200) """ @@ -130,7 +142,7 @@ class FreqaiExampleStrategy(IStrategy): dataframe["%-raw_price"] = dataframe["close"] return dataframe - def feature_engineering_standard(self, dataframe, **kwargs): + def feature_engineering_standard(self, dataframe: DataFrame, metadata: Dict, **kwargs): """ *Only functional with FreqAI enabled strategies* This optional function will be called once with the dataframe of the base timeframe. @@ -144,28 +156,38 @@ class FreqaiExampleStrategy(IStrategy): All features must be prepended with `%` to be recognized by FreqAI internals. + Access metadata such as the current pair with: + + `metadata["pair"]` + More details about feature engineering available: https://www.freqtrade.io/en/latest/freqai-feature-engineering - :param df: strategy dataframe which will receive the features + :param dataframe: strategy dataframe which will receive the features + :param metadata: metadata of current pair usage example: dataframe["%-day_of_week"] = (dataframe["date"].dt.dayofweek + 1) / 7 """ dataframe["%-day_of_week"] = dataframe["date"].dt.dayofweek dataframe["%-hour_of_day"] = dataframe["date"].dt.hour return dataframe - def set_freqai_targets(self, dataframe, **kwargs): + def set_freqai_targets(self, dataframe: DataFrame, metadata: Dict, **kwargs): """ *Only functional with FreqAI enabled strategies* Required function to set the targets for the model. All targets must be prepended with `&` to be recognized by the FreqAI internals. + Access metadata such as the current pair with: + + `metadata["pair"]` + More details about feature engineering available: https://www.freqtrade.io/en/latest/freqai-feature-engineering - :param df: strategy dataframe which will receive the targets + :param dataframe: strategy dataframe which will receive the targets + :param metadata: metadata of current pair usage example: dataframe["&-target"] = dataframe["close"].shift(-1) / dataframe["close"] """ dataframe["&-s_close"] = ( diff --git a/freqtrade/templates/strategy_analysis_example.ipynb b/freqtrade/templates/strategy_analysis_example.ipynb index dfbcedb72..2bfa4155d 100644 --- a/freqtrade/templates/strategy_analysis_example.ipynb +++ b/freqtrade/templates/strategy_analysis_example.ipynb @@ -118,6 +118,7 @@ "from freqtrade.data.dataprovider import DataProvider\n", "strategy = StrategyResolver.load_strategy(config)\n", "strategy.dp = DataProvider(config, None, None)\n", + "strategy.ft_bot_start()\n", "\n", "# Generate buy/sell signals using strategy\n", "df = strategy.analyze_ticker(candles, {'pair': pair})\n", diff --git a/freqtrade/util/__init__.py b/freqtrade/util/__init__.py index 7980b7ca2..3c3c034c1 100644 --- a/freqtrade/util/__init__.py +++ b/freqtrade/util/__init__.py @@ -1,3 +1,2 @@ -# flake8: noqa: F401 -from freqtrade.util.ft_precise import FtPrecise -from freqtrade.util.periodic_cache import PeriodicCache +from freqtrade.util.ft_precise import FtPrecise # noqa: F401 +from freqtrade.util.periodic_cache import PeriodicCache # noqa: F401 diff --git a/freqtrade/vendor/qtpylib/indicators.py b/freqtrade/vendor/qtpylib/indicators.py index 4f14ae13c..3da4f038d 100644 --- a/freqtrade/vendor/qtpylib/indicators.py +++ b/freqtrade/vendor/qtpylib/indicators.py @@ -1,4 +1,3 @@ -#!/usr/bin/env python # -*- coding: utf-8 -*- # # QTPyLib: Quantitative Trading Python Library diff --git a/freqtrade/worker.py b/freqtrade/worker.py old mode 100755 new mode 100644 diff --git a/requirements-dev.txt b/requirements-dev.txt index a63756e97..32b7cfcc5 100644 --- a/requirements-dev.txt +++ b/requirements-dev.txt @@ -9,25 +9,25 @@ coveralls==3.3.1 flake8==6.0.0 flake8-tidy-imports==4.8.0 -mypy==0.991 -pre-commit==2.21.0 +mypy==1.0.1 +pre-commit==3.0.4 pytest==7.2.1 pytest-asyncio==0.20.3 pytest-cov==4.0.0 pytest-mock==3.10.0 pytest-random-order==1.1.0 -isort==5.11.4 +isort==5.12.0 # For datetime mocking time-machine==2.9.0 # fastapi testing httpx==0.23.3 # Convert jupyter notebooks to markdown documents -nbconvert==7.2.8 +nbconvert==7.2.9 # mypy types -types-cachetools==5.2.1 +types-cachetools==5.3.0.0 types-filelock==3.2.7 -types-requests==2.28.11.8 +types-requests==2.28.11.13 types-tabulate==0.9.0.0 types-python-dateutil==2.8.19.6 diff --git a/requirements-freqai-rl.txt b/requirements-freqai-rl.txt index db8d8d169..c242af43e 100644 --- a/requirements-freqai-rl.txt +++ b/requirements-freqai-rl.txt @@ -3,7 +3,8 @@ # Required for freqai-rl torch==1.13.1 -stable-baselines3==1.6.2 -sb3-contrib==1.6.2 +stable-baselines3==1.7.0 +sb3-contrib==1.7.0 # Gym is forced to this version by stable-baselines3. +setuptools==65.5.1 # Should be removed when gym is fixed. gym==0.21 diff --git a/requirements-freqai.txt b/requirements-freqai.txt index 575e4f1e1..cf5bc4c0b 100644 --- a/requirements-freqai.txt +++ b/requirements-freqai.txt @@ -6,6 +6,6 @@ scikit-learn==1.1.3 joblib==1.2.0 catboost==1.1.1; platform_machine != 'aarch64' -lightgbm==3.3.4 +lightgbm==3.3.5 xgboost==1.7.3 -tensorboard==2.11.2 +tensorboard==2.12.0 diff --git a/requirements-hyperopt.txt b/requirements-hyperopt.txt index 171ede929..904b5d661 100644 --- a/requirements-hyperopt.txt +++ b/requirements-hyperopt.txt @@ -2,7 +2,7 @@ -r requirements.txt # Required for hyperopt -scipy==1.10.0 +scipy==1.10.1 scikit-learn==1.1.3 scikit-optimize==0.9.0 filelock==3.9.0 diff --git a/requirements-plot.txt b/requirements-plot.txt index 75e3234a1..b97d42fb6 100644 --- a/requirements-plot.txt +++ b/requirements-plot.txt @@ -1,4 +1,4 @@ # Include all requirements to run the bot. -r requirements.txt -plotly==5.11.0 +plotly==5.13.0 diff --git a/requirements.txt b/requirements.txt index 00a748ce1..855aa664d 100644 --- a/requirements.txt +++ b/requirements.txt @@ -1,12 +1,10 @@ -numpy==1.24.1 +numpy==1.24.2 pandas==1.5.3 pandas-ta==0.3.14b -ccxt==2.7.12 -# Pin cryptography for now due to rust build errors with piwheels -cryptography==38.0.1; platform_machine == 'armv7l' -cryptography==39.0.0; platform_machine != 'armv7l' -aiohttp==3.8.3 +ccxt==2.8.17 +cryptography==39.0.1 +aiohttp==3.8.4 SQLAlchemy==1.4.46 python-telegram-bot==13.15 arrow==1.2.3 @@ -15,14 +13,14 @@ requests==2.28.2 urllib3==1.26.14 jsonschema==4.17.3 TA-Lib==0.4.25 -technical==1.3.0 +technical==1.4.0 tabulate==0.9.0 pycoingecko==3.1.0 jinja2==3.1.2 tables==3.8.0 blosc==1.11.1 joblib==1.2.0 -pyarrow==10.0.1; platform_machine != 'armv7l' +pyarrow==11.0.0; platform_machine != 'armv7l' # find first, C search in arrays py_find_1st==1.1.5 @@ -30,17 +28,17 @@ py_find_1st==1.1.5 # Load ticker files 30% faster python-rapidjson==1.9 # Properly format api responses -orjson==3.8.5 +orjson==3.8.6 # Notify systemd sdnotify==0.3.2 # API Server -fastapi==0.89.1 +fastapi==0.92.0 pydantic==1.10.4 uvicorn==0.20.0 pyjwt==2.6.0 -aiofiles==22.1.0 +aiofiles==23.1.0 psutil==5.9.4 # Support for colorized terminal output diff --git a/scripts/rest_client.py b/scripts/rest_client.py index 3cf2199fb..144d428e5 100755 --- a/scripts/rest_client.py +++ b/scripts/rest_client.py @@ -177,8 +177,7 @@ class FtRestClient(): return self._get("version") def show_config(self): - """ - Returns part of the configuration, relevant for trading operations. + """ Returns part of the configuration, relevant for trading operations. :return: json object containing the version """ return self._get("show_config") @@ -232,6 +231,14 @@ class FtRestClient(): """ return self._delete(f"trades/{trade_id}") + def cancel_open_order(self, trade_id): + """Cancel open order for trade. + + :param trade_id: Cancels open orders for this trade. + :return: json object + """ + return self._delete(f"trades/{trade_id}/open-order") + def whitelist(self): """Show the current whitelist. diff --git a/scripts/ws_client.py b/scripts/ws_client.py old mode 100644 new mode 100755 diff --git a/setup.sh b/setup.sh index 4cb504853..a9ff36536 100755 --- a/setup.sh +++ b/setup.sh @@ -49,48 +49,50 @@ function updateenv() { source .env/bin/activate SYS_ARCH=$(uname -m) echo "pip install in-progress. Please wait..." - ${PYTHON} -m pip install --upgrade pip - read -p "Do you want to install dependencies for dev [y/N]? " + # Setuptools 65.5.0 is the last version that can install gym==0.21.0 + ${PYTHON} -m pip install --upgrade pip wheel setuptools==65.5.1 + REQUIREMENTS_HYPEROPT="" + REQUIREMENTS_PLOT="" + REQUIREMENTS_FREQAI="" + REQUIREMENTS_FREQAI_RL="" + REQUIREMENTS=requirements.txt + + read -p "Do you want to install dependencies for development (Performs a full install with all dependencies) [y/N]? " dev=$REPLY if [[ $REPLY =~ ^[Yy]$ ]] then REQUIREMENTS=requirements-dev.txt else - REQUIREMENTS=requirements.txt - fi - REQUIREMENTS_HYPEROPT="" - REQUIREMENTS_PLOT="" - read -p "Do you want to install plotting dependencies (plotly) [y/N]? " - if [[ $REPLY =~ ^[Yy]$ ]] - then - REQUIREMENTS_PLOT="-r requirements-plot.txt" - fi - if [ "${SYS_ARCH}" == "armv7l" ] || [ "${SYS_ARCH}" == "armv6l" ]; then - echo "Detected Raspberry, installing cython, skipping hyperopt installation." - ${PYTHON} -m pip install --upgrade cython - else - # Is not Raspberry - read -p "Do you want to install hyperopt dependencies [y/N]? " + # requirements-dev.txt includes all the below requirements already, so further questions are pointless. + read -p "Do you want to install plotting dependencies (plotly) [y/N]? " if [[ $REPLY =~ ^[Yy]$ ]] then - REQUIREMENTS_HYPEROPT="-r requirements-hyperopt.txt" + REQUIREMENTS_PLOT="-r requirements-plot.txt" + fi + if [ "${SYS_ARCH}" == "armv7l" ] || [ "${SYS_ARCH}" == "armv6l" ]; then + echo "Detected Raspberry, installing cython, skipping hyperopt installation." + ${PYTHON} -m pip install --upgrade cython + else + # Is not Raspberry + read -p "Do you want to install hyperopt dependencies [y/N]? " + if [[ $REPLY =~ ^[Yy]$ ]] + then + REQUIREMENTS_HYPEROPT="-r requirements-hyperopt.txt" + fi fi - fi - REQUIREMENTS_FREQAI="" - REQUIREMENTS_FREQAI_RL="" - read -p "Do you want to install dependencies for freqai [y/N]? " - dev=$REPLY - if [[ $REPLY =~ ^[Yy]$ ]] - then - REQUIREMENTS_FREQAI="-r requirements-freqai.txt --use-pep517" - read -p "Do you also want dependencies for freqai-rl (~700mb additional space required) [y/N]? " - dev=$REPLY + read -p "Do you want to install dependencies for freqai [y/N]? " if [[ $REPLY =~ ^[Yy]$ ]] then - REQUIREMENTS_FREQAI="-r requirements-freqai-rl.txt" + REQUIREMENTS_FREQAI="-r requirements-freqai.txt --use-pep517" + read -p "Do you also want dependencies for freqai-rl (~700mb additional space required) [y/N]? " + if [[ $REPLY =~ ^[Yy]$ ]] + then + REQUIREMENTS_FREQAI="-r requirements-freqai-rl.txt" + fi fi fi + install_talib ${PYTHON} -m pip install --upgrade -r ${REQUIREMENTS} ${REQUIREMENTS_HYPEROPT} ${REQUIREMENTS_PLOT} ${REQUIREMENTS_FREQAI} ${REQUIREMENTS_FREQAI_RL} if [ $? -ne 0 ]; then @@ -168,21 +170,18 @@ function install_macos() { if [[ $version -ge 9 ]]; then #Checks if python version >= 3.9 install_mac_newer_python_dependencies fi - install_talib } # Install bot Debian_ubuntu function install_debian() { sudo apt-get update sudo apt-get install -y gcc build-essential autoconf libtool pkg-config make wget git curl $(echo lib${PYTHON}-dev ${PYTHON}-venv) - install_talib } # Install bot RedHat_CentOS function install_redhat() { sudo yum update sudo yum install -y gcc gcc-c++ make autoconf libtool pkg-config wget git $(echo ${PYTHON}-devel | sed 's/\.//g') - install_talib } # Upgrade the bot @@ -191,26 +190,37 @@ function update() { updateenv } +function check_git_changes() { + if [ -z "$(git status --porcelain)" ]; then + echo "No changes in git directory" + return 1 + else + echo "Changes in git directory" + return 0 + fi +} + # Reset Develop or Stable branch function reset() { echo_block "Resetting branch and virtual env" if [ "1" == $(git branch -vv |grep -cE "\* develop|\* stable") ] then + if check_git_changes; then + read -p "Keep your local changes? (Otherwise will remove all changes you made!) [Y/n]? " + if [[ $REPLY =~ ^[Nn]$ ]]; then - read -p "Reset git branch? (This will remove all changes you made!) [y/N]? " - if [[ $REPLY =~ ^[Yy]$ ]]; then + git fetch -a - git fetch -a - - if [ "1" == $(git branch -vv | grep -c "* develop") ] - then - echo "- Hard resetting of 'develop' branch." - git reset --hard origin/develop - elif [ "1" == $(git branch -vv | grep -c "* stable") ] - then - echo "- Hard resetting of 'stable' branch." - git reset --hard origin/stable + if [ "1" == $(git branch -vv | grep -c "* develop") ] + then + echo "- Hard resetting of 'develop' branch." + git reset --hard origin/develop + elif [ "1" == $(git branch -vv | grep -c "* stable") ] + then + echo "- Hard resetting of 'stable' branch." + git reset --hard origin/stable + fi fi fi else diff --git a/tests/conftest.py b/tests/conftest.py index 06a86c3b3..c74b1f0f1 100644 --- a/tests/conftest.py +++ b/tests/conftest.py @@ -2573,7 +2573,7 @@ def import_fails() -> None: realimport = builtins.__import__ def mockedimport(name, *args, **kwargs): - if name in ["filelock", 'systemd.journal', 'uvloop']: + if name in ["filelock", 'cysystemd.journal', 'uvloop']: raise ImportError(f"No module named '{name}'") return realimport(name, *args, **kwargs) diff --git a/tests/data/test_dataprovider.py b/tests/data/test_dataprovider.py index e0c79d52a..c6b1dcc5a 100644 --- a/tests/data/test_dataprovider.py +++ b/tests/data/test_dataprovider.py @@ -437,6 +437,7 @@ def test_dp__add_external_df(default_conf_usdt): # Add the same dataframe again - dataframe size shall not change. res = dp._add_external_df('ETH/USDT', df, last_analyzed, timeframe, CandleType.SPOT) assert res[0] is True + assert isinstance(res[1], int) assert res[1] == 0 df, _ = dp.get_producer_df('ETH/USDT', timeframe, CandleType.SPOT) assert len(df) == 24 @@ -446,6 +447,7 @@ def test_dp__add_external_df(default_conf_usdt): res = dp._add_external_df('ETH/USDT', df2, last_analyzed, timeframe, CandleType.SPOT) assert res[0] is True + assert isinstance(res[1], int) assert res[1] == 0 df, _ = dp.get_producer_df('ETH/USDT', timeframe, CandleType.SPOT) assert len(df) == 48 @@ -455,6 +457,7 @@ def test_dp__add_external_df(default_conf_usdt): res = dp._add_external_df('ETH/USDT', df3, last_analyzed, timeframe, CandleType.SPOT) assert res[0] is True + assert isinstance(res[1], int) assert res[1] == 0 df, _ = dp.get_producer_df('ETH/USDT', timeframe, CandleType.SPOT) # New length = 48 + 12 (since we have a 12 hour offset). @@ -478,6 +481,7 @@ def test_dp__add_external_df(default_conf_usdt): res = dp._add_external_df('ETH/USDT', df4, last_analyzed, timeframe, CandleType.SPOT) assert res[0] is False # 36 hours - from 2022-01-03 12:00:00+00:00 to 2022-01-05 00:00:00+00:00 + assert isinstance(res[1], int) assert res[1] == 36 df, _ = dp.get_producer_df('ETH/USDT', timeframe, CandleType.SPOT) # New length = 61 + 1 @@ -488,4 +492,5 @@ def test_dp__add_external_df(default_conf_usdt): res = dp._add_external_df('ETH/USDT', df4, last_analyzed, timeframe, CandleType.SPOT) assert res[0] is False # 36 hours - from 2022-01-03 12:00:00+00:00 to 2022-01-05 00:00:00+00:00 + assert isinstance(res[1], int) assert res[1] == 0 diff --git a/tests/data/test_entryexitanalysis.py b/tests/data/test_entryexitanalysis.py old mode 100755 new mode 100644 diff --git a/tests/edge/test_edge.py b/tests/edge/test_edge.py index 1b0191fda..e414d7624 100644 --- a/tests/edge/test_edge.py +++ b/tests/edge/test_edge.py @@ -139,7 +139,7 @@ def test_adjust(mocker, edge_conf): assert (edge.adjust(pairs) == ['E/F', 'C/D']) -def test_stoploss(mocker, edge_conf): +def test_edge_get_stoploss(mocker, edge_conf): freqtrade = get_patched_freqtradebot(mocker, edge_conf) edge = Edge(edge_conf, freqtrade.exchange, freqtrade.strategy) mocker.patch('freqtrade.edge.Edge._cached_pairs', mocker.PropertyMock( @@ -150,10 +150,10 @@ def test_stoploss(mocker, edge_conf): } )) - assert edge.stoploss('E/F') == -0.01 + assert edge.get_stoploss('E/F') == -0.01 -def test_nonexisting_stoploss(mocker, edge_conf): +def test_nonexisting_get_stoploss(mocker, edge_conf): freqtrade = get_patched_freqtradebot(mocker, edge_conf) edge = Edge(edge_conf, freqtrade.exchange, freqtrade.strategy) mocker.patch('freqtrade.edge.Edge._cached_pairs', mocker.PropertyMock( @@ -162,7 +162,7 @@ def test_nonexisting_stoploss(mocker, edge_conf): } )) - assert edge.stoploss('N/O') == -0.1 + assert edge.get_stoploss('N/O') == -0.1 def test_edge_stake_amount(mocker, edge_conf): diff --git a/tests/exchange/test_binance.py b/tests/exchange/test_binance.py index cb304f699..79d3c0836 100644 --- a/tests/exchange/test_binance.py +++ b/tests/exchange/test_binance.py @@ -20,7 +20,7 @@ from tests.exchange.test_exchange import ccxt_exceptionhandlers (0.99, 220 * 1.01, "buy"), (0.98, 220 * 1.02, "buy"), ]) -def test_stoploss_order_binance(default_conf, mocker, limitratio, expected, side, trademode): +def test_create_stoploss_order_binance(default_conf, mocker, limitratio, expected, side, trademode): api_mock = MagicMock() order_id = 'test_prod_buy_{}'.format(randint(0, 10 ** 6)) order_type = 'stop_loss_limit' if trademode == TradingMode.SPOT else 'stop' @@ -40,7 +40,7 @@ def test_stoploss_order_binance(default_conf, mocker, limitratio, expected, side exchange = get_patched_exchange(mocker, default_conf, api_mock, 'binance') with pytest.raises(OperationalException): - order = exchange.stoploss( + order = exchange.create_stoploss( pair='ETH/BTC', amount=1, stop_price=190, @@ -50,11 +50,11 @@ def test_stoploss_order_binance(default_conf, mocker, limitratio, expected, side ) api_mock.create_order.reset_mock() - order_types = {'stoploss': 'limit'} + order_types = {'stoploss': 'limit', 'stoploss_price_type': 'mark'} if limitratio is not None: order_types.update({'stoploss_on_exchange_limit_ratio': limitratio}) - order = exchange.stoploss( + order = exchange.create_stoploss( pair='ETH/BTC', amount=1, stop_price=220, @@ -75,14 +75,14 @@ def test_stoploss_order_binance(default_conf, mocker, limitratio, expected, side if trademode == TradingMode.SPOT: params_dict = {'stopPrice': 220} else: - params_dict = {'stopPrice': 220, 'reduceOnly': True} + params_dict = {'stopPrice': 220, 'reduceOnly': True, 'workingType': 'MARK_PRICE'} assert api_mock.create_order.call_args_list[0][1]['params'] == params_dict # test exception handling with pytest.raises(DependencyException): api_mock.create_order = MagicMock(side_effect=ccxt.InsufficientFunds("0 balance")) exchange = get_patched_exchange(mocker, default_conf, api_mock, 'binance') - exchange.stoploss( + exchange.create_stoploss( pair='ETH/BTC', amount=1, stop_price=220, @@ -94,7 +94,7 @@ def test_stoploss_order_binance(default_conf, mocker, limitratio, expected, side api_mock.create_order = MagicMock( side_effect=ccxt.InvalidOrder("binance Order would trigger immediately.")) exchange = get_patched_exchange(mocker, default_conf, api_mock, 'binance') - exchange.stoploss( + exchange.create_stoploss( pair='ETH/BTC', amount=1, stop_price=220, @@ -104,12 +104,12 @@ def test_stoploss_order_binance(default_conf, mocker, limitratio, expected, side ) ccxt_exceptionhandlers(mocker, default_conf, api_mock, "binance", - "stoploss", "create_order", retries=1, + "create_stoploss", "create_order", retries=1, pair='ETH/BTC', amount=1, stop_price=220, order_types={}, side=side, leverage=1.0) -def test_stoploss_order_dry_run_binance(default_conf, mocker): +def test_create_stoploss_order_dry_run_binance(default_conf, mocker): api_mock = MagicMock() order_type = 'stop_loss_limit' default_conf['dry_run'] = True @@ -119,7 +119,7 @@ def test_stoploss_order_dry_run_binance(default_conf, mocker): exchange = get_patched_exchange(mocker, default_conf, api_mock, 'binance') with pytest.raises(OperationalException): - order = exchange.stoploss( + order = exchange.create_stoploss( pair='ETH/BTC', amount=1, stop_price=190, @@ -130,7 +130,7 @@ def test_stoploss_order_dry_run_binance(default_conf, mocker): api_mock.create_order.reset_mock() - order = exchange.stoploss( + order = exchange.create_stoploss( pair='ETH/BTC', amount=1, stop_price=220, @@ -495,7 +495,8 @@ def test_fill_leverage_tiers_binance_dryrun(default_conf, mocker, leverage_tiers for key, value in leverage_tiers.items(): v = exchange._leverage_tiers[key] assert isinstance(v, list) - assert len(v) == len(value) + # Assert if conftest leverage tiers have less or equal tiers than the exchange + assert len(v) >= len(value) def test_additional_exchange_init_binance(default_conf, mocker): @@ -522,8 +523,15 @@ def test__set_leverage_binance(mocker, default_conf): api_mock.set_leverage = MagicMock() type(api_mock).has = PropertyMock(return_value={'setLeverage': True}) default_conf['dry_run'] = False - exchange = get_patched_exchange(mocker, default_conf, id="binance") - exchange._set_leverage(3.0, trading_mode=TradingMode.MARGIN) + default_conf['trading_mode'] = TradingMode.FUTURES + default_conf['margin_mode'] = MarginMode.ISOLATED + + exchange = get_patched_exchange(mocker, default_conf, api_mock, id="binance") + exchange._set_leverage(3.2, 'BTC/USDT:USDT') + assert api_mock.set_leverage.call_count == 1 + # Leverage is rounded to 3. + assert api_mock.set_leverage.call_args_list[0][1]['leverage'] == 3 + assert api_mock.set_leverage.call_args_list[0][1]['symbol'] == 'BTC/USDT:USDT' ccxt_exceptionhandlers( mocker, diff --git a/tests/exchange/test_bybit.py b/tests/exchange/test_bybit.py new file mode 100644 index 000000000..7c8324bf6 --- /dev/null +++ b/tests/exchange/test_bybit.py @@ -0,0 +1,57 @@ +from unittest.mock import MagicMock + +from freqtrade.enums.marginmode import MarginMode +from freqtrade.enums.tradingmode import TradingMode +from freqtrade.exchange.exchange_utils import timeframe_to_msecs +from tests.conftest import get_mock_coro, get_patched_exchange +from tests.exchange.test_exchange import ccxt_exceptionhandlers + + +def test_additional_exchange_init_bybit(default_conf, mocker): + default_conf['dry_run'] = False + default_conf['trading_mode'] = TradingMode.FUTURES + default_conf['margin_mode'] = MarginMode.ISOLATED + api_mock = MagicMock() + api_mock.set_position_mode = MagicMock(return_value={"dualSidePosition": False}) + get_patched_exchange(mocker, default_conf, id="bybit", api_mock=api_mock) + assert api_mock.set_position_mode.call_count == 1 + ccxt_exceptionhandlers(mocker, default_conf, api_mock, 'bybit', + "additional_exchange_init", "set_position_mode") + + +async def test_bybit_fetch_funding_rate(default_conf, mocker): + default_conf['trading_mode'] = 'futures' + default_conf['margin_mode'] = 'isolated' + api_mock = MagicMock() + api_mock.fetch_funding_rate_history = get_mock_coro(return_value=[]) + exchange = get_patched_exchange(mocker, default_conf, id='bybit', api_mock=api_mock) + limit = 200 + # Test fetch_funding_rate_history (current data) + await exchange._fetch_funding_rate_history( + pair='BTC/USDT:USDT', + timeframe='4h', + limit=limit, + ) + + assert api_mock.fetch_funding_rate_history.call_count == 1 + assert api_mock.fetch_funding_rate_history.call_args_list[0][0][0] == 'BTC/USDT:USDT' + kwargs = api_mock.fetch_funding_rate_history.call_args_list[0][1] + assert kwargs['params'] == {} + assert kwargs['since'] is None + + api_mock.fetch_funding_rate_history.reset_mock() + since_ms = 1610000000000 + since_ms_end = since_ms + (timeframe_to_msecs('4h') * limit) + # Test fetch_funding_rate_history (current data) + await exchange._fetch_funding_rate_history( + pair='BTC/USDT:USDT', + timeframe='4h', + limit=limit, + since_ms=since_ms, + ) + + assert api_mock.fetch_funding_rate_history.call_count == 1 + assert api_mock.fetch_funding_rate_history.call_args_list[0][0][0] == 'BTC/USDT:USDT' + kwargs = api_mock.fetch_funding_rate_history.call_args_list[0][1] + assert kwargs['params'] == {'until': since_ms_end} + assert kwargs['since'] == since_ms diff --git a/tests/exchange/test_ccxt_compat.py b/tests/exchange/test_ccxt_compat.py index 18f8581e8..bbeb56c6a 100644 --- a/tests/exchange/test_ccxt_compat.py +++ b/tests/exchange/test_ccxt_compat.py @@ -49,8 +49,8 @@ EXCHANGES = { "orderListId": -1, "clientOrderId": "x-R4DD3S8297c73a11ccb9dc8f2811ba", "transactTime": 1674493798550, - "price": "15.00000000", - "origQty": "1.00000000", + "price": "15.50000000", + "origQty": "1.10000000", "executedQty": "0.00000000", "cummulativeQuoteQty": "0.00000000", "status": "NEW", @@ -74,8 +74,8 @@ EXCHANGES = { "orderListId": -1, "clientOrderId": "x-R4DD3S8297c73a11ccb9dc8f2811ba", "transactTime": 1674493798550, - "price": "15.00000000", - "origQty": "1.00000000", + "price": "15.50000000", + "origQty": "1.10000000", "executedQty": "0.00000000", "cummulativeQuoteQty": "0.00000000", "status": "NEW", @@ -106,15 +106,15 @@ EXCHANGES = { {'id': '63d6742d0adc5570001d2bbf7'}, # create order { 'id': '63d6742d0adc5570001d2bbf7', - 'symbol': 'NAKA-USDT', + 'symbol': 'SOL-USDT', 'opType': 'DEAL', 'type': 'limit', 'side': 'buy', - 'price': '30', - 'size': '0.1', + 'price': '15.5', + 'size': '1.1', 'funds': '0', - 'dealFunds': '0.032626', - 'dealSize': '0.1', + 'dealFunds': '17.05', + 'dealSize': '1.1', 'fee': '0.000065252', 'feeCurrency': 'USDT', 'stp': '', @@ -137,7 +137,7 @@ EXCHANGES = { 'tradeType': 'TRADE' }], }, - 'gateio': { + 'gate': { 'pair': 'BTC/USDT', 'stake_currency': 'USDT', 'hasQuoteVolume': True, @@ -147,6 +147,69 @@ EXCHANGES = { 'hasQuoteVolumeFutures': True, 'leverage_tiers_public': True, 'leverage_in_spot_market': True, + 'sample_order': [ + { + "id": "276266139423", + "text": "apiv4", + "create_time": "1674493798", + "update_time": "1674493798", + "create_time_ms": "1674493798550", + "update_time_ms": "1674493798550", + "status": "closed", + "currency_pair": "SOL_USDT", + "type": "limit", + "account": "spot", + "side": "buy", + "amount": "1.1", + "price": "15.5", + "time_in_force": "gtc", + "iceberg": "0", + "left": "0", + "fill_price": "17.05", + "filled_total": "17.05", + "avg_deal_price": "15.5", + "fee": "0.0000018", + "fee_currency": "SOL", + "point_fee": "0", + "gt_fee": "0", + "gt_maker_fee": "0", + "gt_taker_fee": "0.0015", + "gt_discount": True, + "rebated_fee": "0", + "rebated_fee_currency": "USDT" + }, + { + # market order + 'id': '276401180529', + 'text': 'apiv4', + 'create_time': '1674493798', + 'update_time': '1674493798', + 'create_time_ms': '1674493798550', + 'update_time_ms': '1674493798550', + 'status': 'cancelled', + 'currency_pair': 'SOL_USDT', + 'type': 'market', + 'account': 'spot', + 'side': 'buy', + 'amount': '17.05', + 'price': '0', + 'time_in_force': 'ioc', + 'iceberg': '0', + 'left': '0.0000000016228', + 'fill_price': '17.05', + 'filled_total': '17.05', + 'avg_deal_price': '15.5', + 'fee': '0', + 'fee_currency': 'SOL', + 'point_fee': '0.0199999999967544', + 'gt_fee': '0', + 'gt_maker_fee': '0', + 'gt_taker_fee': '0', + 'gt_discount': False, + 'rebated_fee': '0', + 'rebated_fee_currency': 'USDT' + } + ], }, 'okx': { 'pair': 'BTC/USDT', @@ -159,6 +222,33 @@ EXCHANGES = { 'leverage_tiers_public': True, 'leverage_in_spot_market': True, }, + 'bybit': { + 'pair': 'BTC/USDT', + 'stake_currency': 'USDT', + 'hasQuoteVolume': True, + 'timeframe': '5m', + 'futures_pair': 'BTC/USDT:USDT', + 'futures': True, + 'leverage_tiers_public': True, + 'leverage_in_spot_market': True, + 'sample_order': [ + { + "orderId": "1274754916287346280", + "orderLinkId": "1666798627015730", + "symbol": "SOLUSDT", + "createTime": "1674493798550", + "orderPrice": "15.5", + "orderQty": "1.1", + "orderType": "LIMIT", + "side": "BUY", + "status": "NEW", + "timeInForce": "GTC", + "accountId": "5555555", + "execQty": "0", + "orderCategory": "0" + } + ] + }, 'huobi': { 'pair': 'ETH/BTC', 'stake_currency': 'BTC', @@ -219,7 +309,7 @@ def exchange(request, exchange_conf): @pytest.fixture(params=EXCHANGES, scope="class") def exchange_futures(request, exchange_conf, class_mocker): - if not EXCHANGES[request.param].get('futures') is True: + if EXCHANGES[request.param].get('futures') is not True: yield None, request.param else: exchange_conf = set_test_proxy( @@ -235,6 +325,7 @@ def exchange_futures(request, exchange_conf, class_mocker): class_mocker.patch('freqtrade.exchange.exchange.Exchange.fetch_trading_fees') class_mocker.patch('freqtrade.exchange.okx.Okx.additional_exchange_init') class_mocker.patch('freqtrade.exchange.binance.Binance.additional_exchange_init') + class_mocker.patch('freqtrade.exchange.bybit.Bybit.additional_exchange_init') class_mocker.patch('freqtrade.exchange.exchange.Exchange.load_cached_leverage_tiers', return_value=None) class_mocker.patch('freqtrade.exchange.exchange.Exchange.cache_leverage_tiers') @@ -266,8 +357,8 @@ class TestCCXTExchange(): 'stoploss': 'limit', }) - if exchangename == 'gateio': - # gateio doesn't have market orders on spot + if exchangename == 'gate': + # gate doesn't have market orders on spot return exch.validate_ordertypes({ 'entry': 'market', @@ -295,13 +386,22 @@ class TestCCXTExchange(): po = exch._api.parse_order(order) assert isinstance(po['id'], str) assert po['id'] is not None - if len(order.keys()) > 1: - assert po['timestamp'] == 1674493798550 - assert isinstance(po['datetime'], str) - assert isinstance(po['timestamp'], int) - assert isinstance(po['price'], float) - assert isinstance(po['amount'], float) - assert isinstance(po['status'], str) + if len(order.keys()) < 5: + # Kucoin case + assert po['status'] == 'closed' + continue + assert po['timestamp'] == 1674493798550 + assert isinstance(po['datetime'], str) + assert isinstance(po['timestamp'], int) + assert isinstance(po['price'], float) + assert po['price'] == 15.5 + if po['average'] is not None: + assert isinstance(po['average'], float) + assert po['average'] == 15.5 + assert po['symbol'] == 'SOL/USDT' + assert isinstance(po['amount'], float) + assert po['amount'] == 1.1 + assert isinstance(po['status'], str) else: pytest.skip(f"No sample order available for exchange {exchange_name}") @@ -321,7 +421,7 @@ class TestCCXTExchange(): def test_ccxt_fetch_tickers_futures(self, exchange_futures: EXCHANGE_FIXTURE_TYPE): exch, exchangename = exchange_futures - if not exch or exchangename in ('gateio'): + if not exch or exchangename in ('gate'): # exchange_futures only returns values for supported exchanges return @@ -361,8 +461,8 @@ class TestCCXTExchange(): assert len(l2['bids']) >= 1 l2_limit_range = exch._ft_has['l2_limit_range'] l2_limit_range_required = exch._ft_has['l2_limit_range_required'] - if exchangename == 'gateio': - # TODO: Gateio is unstable here at the moment, ignoring the limit partially. + if exchangename == 'gate': + # TODO: Gate is unstable here at the moment, ignoring the limit partially. return for val in [1, 2, 5, 25, 100]: l2 = exch.fetch_l2_order_book(pair, val) @@ -434,9 +534,12 @@ class TestCCXTExchange(): def test_ccxt__async_get_candle_history(self, exchange: EXCHANGE_FIXTURE_TYPE): exc, exchangename = exchange - # For some weired reason, this test returns random lengths for bittrex. - if not exc._ft_has['ohlcv_has_history'] or exchangename in ('bittrex'): - return + if exchangename in ('bittrex'): + # For some weired reason, this test returns random lengths for bittrex. + pytest.skip("Exchange doesn't provide stable ohlcv history") + + if not exc._ft_has['ohlcv_has_history']: + pytest.skip("Exchange does not support candle history") pair = EXCHANGES[exchangename]['pair'] timeframe = EXCHANGES[exchangename]['timeframe'] self.ccxt__async_get_candle_history( @@ -616,23 +719,25 @@ class TestCCXTExchange(): ) liquidation_price = futures.dry_run_liquidation_price( - futures_pair, - 40000, - False, - 100, - 100, - 100, + pair=futures_pair, + open_rate=40000, + is_short=False, + amount=100, + stake_amount=100, + leverage=5, + wallet_balance=100, ) assert (isinstance(liquidation_price, float)) assert liquidation_price >= 0.0 liquidation_price = futures.dry_run_liquidation_price( - futures_pair, - 40000, - False, - 100, - 100, - 100, + pair=futures_pair, + open_rate=40000, + is_short=False, + amount=100, + stake_amount=100, + leverage=5, + wallet_balance=100, ) assert (isinstance(liquidation_price, float)) assert liquidation_price >= 0.0 diff --git a/tests/exchange/test_exchange.py b/tests/exchange/test_exchange.py index 0fa7f90ec..13613df37 100644 --- a/tests/exchange/test_exchange.py +++ b/tests/exchange/test_exchange.py @@ -27,7 +27,7 @@ from tests.conftest import (generate_test_data_raw, get_mock_coro, get_patched_e # Make sure to always keep one exchange here which is NOT subclassed!! -EXCHANGES = ['bittrex', 'binance', 'kraken', 'gateio'] +EXCHANGES = ['bittrex', 'binance', 'kraken', 'gate'] get_entry_rate_data = [ ('other', 20, 19, 10, 0.0, 20), # Full ask side @@ -1060,6 +1060,47 @@ def test_validate_ordertypes(default_conf, mocker): Exchange(default_conf) +@pytest.mark.parametrize('exchange_name,stopadv, expected', [ + ('binance', 'last', True), + ('binance', 'mark', True), + ('binance', 'index', False), + ('bybit', 'last', True), + ('bybit', 'mark', True), + ('bybit', 'index', True), + # ('okx', 'last', True), + # ('okx', 'mark', True), + # ('okx', 'index', True), + ('gate', 'last', True), + ('gate', 'mark', True), + ('gate', 'index', True), + ]) +def test_validate_ordertypes_stop_advanced(default_conf, mocker, exchange_name, stopadv, expected): + + api_mock = MagicMock() + default_conf['trading_mode'] = TradingMode.FUTURES + default_conf['margin_mode'] = MarginMode.ISOLATED + type(api_mock).has = PropertyMock(return_value={'createMarketOrder': True}) + mocker.patch('freqtrade.exchange.Exchange._init_ccxt', MagicMock(return_value=api_mock)) + mocker.patch('freqtrade.exchange.Exchange._load_markets', MagicMock(return_value={})) + mocker.patch('freqtrade.exchange.Exchange.validate_pairs') + mocker.patch('freqtrade.exchange.Exchange.validate_timeframes') + mocker.patch('freqtrade.exchange.Exchange.validate_stakecurrency') + mocker.patch('freqtrade.exchange.Exchange.validate_pricing') + default_conf['order_types'] = { + 'entry': 'limit', + 'exit': 'limit', + 'stoploss': 'limit', + 'stoploss_on_exchange': True, + 'stoploss_price_type': stopadv, + } + if expected: + ExchangeResolver.load_exchange(exchange_name, default_conf) + else: + with pytest.raises(OperationalException, + match=r'On exchange stoploss price type is not supported for .*'): + ExchangeResolver.load_exchange(exchange_name, default_conf) + + def test_validate_order_types_not_in_config(default_conf, mocker): api_mock = MagicMock() mocker.patch('freqtrade.exchange.Exchange._init_ccxt', MagicMock(return_value=api_mock)) @@ -1182,7 +1223,7 @@ def test_create_dry_run_order_fees( 'freqtrade.exchange.Exchange.get_fee', side_effect=lambda symbol, taker_or_maker: 2.0 if taker_or_maker == 'taker' else 1.0 ) - mocker.patch('freqtrade.exchange.Exchange._is_dry_limit_order_filled', + mocker.patch('freqtrade.exchange.Exchange._dry_is_price_crossed', return_value=price_side == 'other') exchange = get_patched_exchange(mocker, default_conf) @@ -1200,25 +1241,27 @@ def test_create_dry_run_order_fees( else: assert order['fee'] is None - mocker.patch('freqtrade.exchange.Exchange._is_dry_limit_order_filled', + mocker.patch('freqtrade.exchange.Exchange._dry_is_price_crossed', return_value=price_side != 'other') order1 = exchange.fetch_dry_run_order(order['id']) assert order1['fee']['rate'] == fee -@pytest.mark.parametrize("side,price,filled", [ +@pytest.mark.parametrize("side,price,filled,converted", [ # order_book_l2_usd spread: # best ask: 25.566 # best bid: 25.563 - ("buy", 25.563, False), - ("buy", 25.566, True), - ("sell", 25.566, False), - ("sell", 25.563, True), + ("buy", 25.563, False, False), + ("buy", 25.566, True, False), + ("sell", 25.566, False, False), + ("sell", 25.563, True, False), + ("buy", 29.563, True, True), + ("sell", 21.563, True, True), ]) @pytest.mark.parametrize("exchange_name", EXCHANGES) -def test_create_dry_run_order_limit_fill(default_conf, mocker, side, price, filled, - exchange_name, order_book_l2_usd): +def test_create_dry_run_order_limit_fill(default_conf, mocker, side, price, filled, caplog, + exchange_name, order_book_l2_usd, converted): default_conf['dry_run'] = True exchange = get_patched_exchange(mocker, default_conf, id=exchange_name) mocker.patch.multiple('freqtrade.exchange.Exchange', @@ -1238,9 +1281,16 @@ def test_create_dry_run_order_limit_fill(default_conf, mocker, side, price, fill assert 'id' in order assert f'dry_run_{side}_' in order["id"] assert order["side"] == side - assert order["type"] == "limit" + if not converted: + assert order["average"] == price + assert order["type"] == "limit" + else: + # Converted to market order + assert order["type"] == "market" + assert 25.5 < order["average"] < 25.6 + assert log_has_re(r"Converted .* to market order.*", caplog) + assert order["symbol"] == "LTC/USDT" - assert order["average"] == price assert order['status'] == 'open' if not filled else 'closed' order_book_l2_usd.reset_mock() @@ -1742,7 +1792,7 @@ def test_fetch_trading_fees(default_conf, mocker): 'maker': 0.0, 'taker': 0.0005} } - exchange_name = 'gateio' + exchange_name = 'gate' default_conf['dry_run'] = False default_conf['trading_mode'] = TradingMode.FUTURES default_conf['margin_mode'] = MarginMode.ISOLATED @@ -2977,7 +3027,7 @@ def test_get_historic_trades_notsupported(default_conf, mocker, caplog, exchange def test_cancel_order_dry_run(default_conf, mocker, exchange_name): default_conf['dry_run'] = True exchange = get_patched_exchange(mocker, default_conf, id=exchange_name) - mocker.patch('freqtrade.exchange.Exchange._is_dry_limit_order_filled', return_value=True) + mocker.patch('freqtrade.exchange.Exchange._dry_is_price_crossed', return_value=True) assert exchange.cancel_order(order_id='123', pair='TKN/BTC') == {} assert exchange.cancel_stoploss_order(order_id='123', pair='TKN/BTC') == {} @@ -3106,24 +3156,24 @@ def test_cancel_stoploss_order(default_conf, mocker, exchange_name): def test_cancel_stoploss_order_with_result(default_conf, mocker, exchange_name): default_conf['dry_run'] = False mocker.patch('freqtrade.exchange.Exchange.fetch_stoploss_order', return_value={'for': 123}) - mocker.patch('freqtrade.exchange.Gateio.fetch_stoploss_order', return_value={'for': 123}) + mocker.patch('freqtrade.exchange.Gate.fetch_stoploss_order', return_value={'for': 123}) exchange = get_patched_exchange(mocker, default_conf, id=exchange_name) res = {'fee': {}, 'status': 'canceled', 'amount': 1234} mocker.patch('freqtrade.exchange.Exchange.cancel_stoploss_order', return_value=res) - mocker.patch('freqtrade.exchange.Gateio.cancel_stoploss_order', return_value=res) + mocker.patch('freqtrade.exchange.Gate.cancel_stoploss_order', return_value=res) co = exchange.cancel_stoploss_order_with_result(order_id='_', pair='TKN/BTC', amount=555) assert co == res mocker.patch('freqtrade.exchange.Exchange.cancel_stoploss_order', return_value='canceled') - mocker.patch('freqtrade.exchange.Gateio.cancel_stoploss_order', return_value='canceled') + mocker.patch('freqtrade.exchange.Gate.cancel_stoploss_order', return_value='canceled') # Fall back to fetch_stoploss_order co = exchange.cancel_stoploss_order_with_result(order_id='_', pair='TKN/BTC', amount=555) assert co == {'for': 123} exc = InvalidOrderException("") mocker.patch('freqtrade.exchange.Exchange.fetch_stoploss_order', side_effect=exc) - mocker.patch('freqtrade.exchange.Gateio.fetch_stoploss_order', side_effect=exc) + mocker.patch('freqtrade.exchange.Gate.fetch_stoploss_order', side_effect=exc) co = exchange.cancel_stoploss_order_with_result(order_id='_', pair='TKN/BTC', amount=555) assert co['amount'] == 555 assert co == {'fee': {}, 'status': 'canceled', 'amount': 555, 'info': {}} @@ -3131,7 +3181,7 @@ def test_cancel_stoploss_order_with_result(default_conf, mocker, exchange_name): with pytest.raises(InvalidOrderException): exc = InvalidOrderException("Did not find order") mocker.patch('freqtrade.exchange.Exchange.cancel_stoploss_order', side_effect=exc) - mocker.patch('freqtrade.exchange.Gateio.cancel_stoploss_order', side_effect=exc) + mocker.patch('freqtrade.exchange.Gate.cancel_stoploss_order', side_effect=exc) exchange = get_patched_exchange(mocker, default_conf, id=exchange_name) exchange.cancel_stoploss_order_with_result(order_id='_', pair='TKN/BTC', amount=123) @@ -3339,7 +3389,7 @@ def test_get_fee(default_conf, mocker, exchange_name): def test_stoploss_order_unsupported_exchange(default_conf, mocker): exchange = get_patched_exchange(mocker, default_conf, id='bittrex') with pytest.raises(OperationalException, match=r"stoploss is not implemented .*"): - exchange.stoploss( + exchange.create_stoploss( pair='ETH/BTC', amount=1, stop_price=220, @@ -3911,14 +3961,14 @@ def test_set_margin_mode(mocker, default_conf, margin_mode): ("bittrex", TradingMode.MARGIN, MarginMode.ISOLATED, True), ("bittrex", TradingMode.FUTURES, MarginMode.CROSS, True), ("bittrex", TradingMode.FUTURES, MarginMode.ISOLATED, True), - ("gateio", TradingMode.MARGIN, MarginMode.ISOLATED, True), + ("gate", TradingMode.MARGIN, MarginMode.ISOLATED, True), ("okx", TradingMode.SPOT, None, False), ("okx", TradingMode.MARGIN, MarginMode.CROSS, True), ("okx", TradingMode.MARGIN, MarginMode.ISOLATED, True), ("okx", TradingMode.FUTURES, MarginMode.CROSS, True), ("binance", TradingMode.FUTURES, MarginMode.ISOLATED, False), - ("gateio", TradingMode.FUTURES, MarginMode.ISOLATED, False), + ("gate", TradingMode.FUTURES, MarginMode.ISOLATED, False), ("okx", TradingMode.FUTURES, MarginMode.ISOLATED, False), # * Remove once implemented @@ -3926,16 +3976,16 @@ def test_set_margin_mode(mocker, default_conf, margin_mode): ("binance", TradingMode.FUTURES, MarginMode.CROSS, True), ("kraken", TradingMode.MARGIN, MarginMode.CROSS, True), ("kraken", TradingMode.FUTURES, MarginMode.CROSS, True), - ("gateio", TradingMode.MARGIN, MarginMode.CROSS, True), - ("gateio", TradingMode.FUTURES, MarginMode.CROSS, True), + ("gate", TradingMode.MARGIN, MarginMode.CROSS, True), + ("gate", TradingMode.FUTURES, MarginMode.CROSS, True), # * Uncomment once implemented # ("binance", TradingMode.MARGIN, MarginMode.CROSS, False), # ("binance", TradingMode.FUTURES, MarginMode.CROSS, False), # ("kraken", TradingMode.MARGIN, MarginMode.CROSS, False), # ("kraken", TradingMode.FUTURES, MarginMode.CROSS, False), - # ("gateio", TradingMode.MARGIN, MarginMode.CROSS, False), - # ("gateio", TradingMode.FUTURES, MarginMode.CROSS, False), + # ("gate", TradingMode.MARGIN, MarginMode.CROSS, False), + # ("gate", TradingMode.FUTURES, MarginMode.CROSS, False), ]) def test_validate_trading_mode_and_margin_mode( default_conf, @@ -3959,8 +4009,8 @@ def test_validate_trading_mode_and_margin_mode( ("binance", "margin", {"options": {"defaultType": "margin"}}), ("binance", "futures", {"options": {"defaultType": "swap"}}), ("bybit", "spot", {"options": {"defaultType": "spot"}}), - ("bybit", "futures", {"options": {"defaultType": "linear"}}), - ("gateio", "futures", {"options": {"defaultType": "swap"}}), + ("bybit", "futures", {"options": {"defaultType": "swap"}}), + ("gate", "futures", {"options": {"defaultType": "swap"}}), ("hitbtc", "futures", {"options": {"defaultType": "swap"}}), ("kraken", "futures", {"options": {"defaultType": "swap"}}), ("kucoin", "futures", {"options": {"defaultType": "swap"}}), @@ -3991,7 +4041,7 @@ def test_get_max_leverage_from_margin(default_conf, mocker, pair, nominal_value, default_conf['margin_mode'] = 'isolated' api_mock = MagicMock() type(api_mock).has = PropertyMock(return_value={'fetchLeverageTiers': False}) - exchange = get_patched_exchange(mocker, default_conf, api_mock, id="gateio") + exchange = get_patched_exchange(mocker, default_conf, api_mock, id="gate") assert exchange.get_max_leverage(pair, nominal_value) == max_lev @@ -4136,10 +4186,10 @@ def test_combine_funding_and_mark( # ('kraken', "2021-09-01 00:00:00", "2021-09-01 07:59:59", 30.0, -0.0012443999999999999), # ('kraken', "2021-09-01 00:00:00", "2021-09-01 12:00:00", 30.0, 0.0045759), # ('kraken', "2021-09-01 00:00:01", "2021-09-01 08:00:00", 30.0, -0.0008289), - ('gateio', 0, 2, "2021-09-01 00:10:00", "2021-09-01 04:00:00", 30.0, 0.0), - ('gateio', 0, 2, "2021-09-01 00:00:00", "2021-09-01 08:00:00", 30.0, -0.0009140999), - ('gateio', 0, 2, "2021-09-01 00:00:00", "2021-09-01 12:00:00", 30.0, -0.0009140999), - ('gateio', 1, 2, "2021-09-01 00:00:01", "2021-09-01 08:00:00", 30.0, -0.0002493), + ('gate', 0, 2, "2021-09-01 00:10:00", "2021-09-01 04:00:00", 30.0, 0.0), + ('gate', 0, 2, "2021-09-01 00:00:00", "2021-09-01 08:00:00", 30.0, -0.0009140999), + ('gate', 0, 2, "2021-09-01 00:00:00", "2021-09-01 12:00:00", 30.0, -0.0009140999), + ('gate', 1, 2, "2021-09-01 00:00:01", "2021-09-01 08:00:00", 30.0, -0.0002493), ('binance', 0, 2, "2021-09-01 00:00:00", "2021-09-01 08:00:00", 50.0, -0.0015235), # TODO: Uncoment once _calculate_funding_fees can pas time_in_ratio to exchange._get_funding_fee # ('kraken', "2021-09-01 00:00:00", "2021-09-01 08:00:00", 50.0, -0.0024895), @@ -4197,7 +4247,7 @@ def test__fetch_and_calculate_funding_fees( d2 = datetime.strptime(f"{d2} +0000", '%Y-%m-%d %H:%M:%S %z') funding_rate_history = { 'binance': funding_rate_history_octohourly, - 'gateio': funding_rate_history_octohourly, + 'gate': funding_rate_history_octohourly, }[exchange][rate_start:rate_end] api_mock = MagicMock() api_mock.fetch_funding_rate_history = get_mock_coro(return_value=funding_rate_history) @@ -4226,7 +4276,7 @@ def test__fetch_and_calculate_funding_fees( @pytest.mark.parametrize('exchange,expected_fees', [ ('binance', -0.0009140999999999999), - ('gateio', -0.0009140999999999999), + ('gate', -0.0009140999999999999), ]) def test__fetch_and_calculate_funding_fees_datetime_called( mocker, @@ -4367,7 +4417,7 @@ def test__order_contracts_to_amount( 'info': {}, }, { - # Realistic stoploss order on gateio. + # Realistic stoploss order on gate. 'id': '123456380', 'clientOrderId': '12345638203', 'timestamp': None, @@ -4566,6 +4616,7 @@ def test_liquidation_price_is_none( is_short=is_short, amount=71200.81144, stake_amount=open_rate * 71200.81144, + leverage=5, wallet_balance=-56354.57, mm_ex_1=0.10, upnl_ex_1=0.0 @@ -4586,7 +4637,7 @@ def test_liquidation_price_is_none( ("binance", False, 'futures', 'cross', 1535443.01, 356512.508, -448192.89, 16300.000, 109.488, 32481.980, 0.025, 26316.89) ]) -def test_liquidation_price( +def test_liquidation_price_binance( mocker, default_conf, exchange_name, open_rate, is_short, trading_mode, margin_mode, wallet_balance, mm_ex_1, upnl_ex_1, maintenance_amt, amount, mm_ratio, expected ): @@ -4604,6 +4655,7 @@ def test_liquidation_price( upnl_ex_1=upnl_ex_1, amount=amount, stake_amount=open_rate * amount, + leverage=5, ), 2)) == expected @@ -4963,7 +5015,7 @@ def test_get_max_leverage_futures(default_conf, mocker, leverage_tiers): exchange.get_max_leverage("BTC/USDT:USDT", 1000000000.01) -@pytest.mark.parametrize("exchange_name", ['bittrex', 'binance', 'kraken', 'gateio', 'okx']) +@pytest.mark.parametrize("exchange_name", ['bittrex', 'binance', 'kraken', 'gate', 'okx']) def test__get_params(mocker, default_conf, exchange_name): api_mock = MagicMock() mocker.patch('freqtrade.exchange.Exchange.exchange_has', return_value=True) @@ -5025,6 +5077,7 @@ def test__get_params(mocker, default_conf, exchange_name): def test_get_liquidation_price1(mocker, default_conf): api_mock = MagicMock() + leverage = 9.97 positions = [ { 'info': {}, @@ -5037,7 +5090,7 @@ def test_get_liquidation_price1(mocker, default_conf): 'maintenanceMarginPercentage': 0.025, 'entryPrice': 18.884, 'notional': 15.1072, - 'leverage': 9.97, + 'leverage': leverage, 'unrealizedPnl': 0.0048, 'contracts': 8, 'contractSize': 0.1, @@ -5067,6 +5120,7 @@ def test_get_liquidation_price1(mocker, default_conf): is_short=False, amount=0.8, stake_amount=18.884 * 0.8, + leverage=leverage, wallet_balance=18.884 * 0.8, ) assert liq_price == 17.47 @@ -5079,6 +5133,7 @@ def test_get_liquidation_price1(mocker, default_conf): is_short=False, amount=0.8, stake_amount=18.884 * 0.8, + leverage=leverage, wallet_balance=18.884 * 0.8, ) assert liq_price == 17.540699999999998 @@ -5091,6 +5146,7 @@ def test_get_liquidation_price1(mocker, default_conf): is_short=False, amount=0.8, stake_amount=18.884 * 0.8, + leverage=leverage, wallet_balance=18.884 * 0.8, ) assert liq_price is None @@ -5104,17 +5160,18 @@ def test_get_liquidation_price1(mocker, default_conf): is_short=False, amount=0.8, stake_amount=18.884 * 0.8, + leverage=leverage, wallet_balance=18.884 * 0.8, ) -@pytest.mark.parametrize('liquidation_buffer', [0.0, 0.05]) +@pytest.mark.parametrize('liquidation_buffer', [0.0]) @pytest.mark.parametrize( "is_short,trading_mode,exchange_name,margin_mode,leverage,open_rate,amount,expected_liq", [ (False, 'spot', 'binance', '', 5.0, 10.0, 1.0, None), (True, 'spot', 'binance', '', 5.0, 10.0, 1.0, None), - (False, 'spot', 'gateio', '', 5.0, 10.0, 1.0, None), - (True, 'spot', 'gateio', '', 5.0, 10.0, 1.0, None), + (False, 'spot', 'gate', '', 5.0, 10.0, 1.0, None), + (True, 'spot', 'gate', '', 5.0, 10.0, 1.0, None), (False, 'spot', 'okx', '', 5.0, 10.0, 1.0, None), (True, 'spot', 'okx', '', 5.0, 10.0, 1.0, None), # Binance, short @@ -5127,16 +5184,26 @@ def test_get_liquidation_price1(mocker, default_conf): (False, 'futures', 'binance', 'isolated', 5, 8, 1.0, 6.454545454545454), (False, 'futures', 'binance', 'isolated', 3, 10, 1.0, 6.723905723905723), (False, 'futures', 'binance', 'isolated', 5, 10, 0.6, 8.063973063973064), - # Gateio/okx, short - (True, 'futures', 'gateio', 'isolated', 5, 10, 1.0, 11.87413417771621), - (True, 'futures', 'gateio', 'isolated', 5, 10, 2.0, 11.87413417771621), - (True, 'futures', 'gateio', 'isolated', 3, 10, 1.0, 13.193482419684678), - (True, 'futures', 'gateio', 'isolated', 5, 8, 1.0, 9.499307342172967), + # Gate/okx, short + (True, 'futures', 'gate', 'isolated', 5, 10, 1.0, 11.87413417771621), + (True, 'futures', 'gate', 'isolated', 5, 10, 2.0, 11.87413417771621), + (True, 'futures', 'gate', 'isolated', 3, 10, 1.0, 13.193482419684678), + (True, 'futures', 'gate', 'isolated', 5, 8, 1.0, 9.499307342172967), (True, 'futures', 'okx', 'isolated', 3, 10, 1.0, 13.193482419684678), - # Gateio/okx, long - (False, 'futures', 'gateio', 'isolated', 5.0, 10.0, 1.0, 8.085708510208207), - (False, 'futures', 'gateio', 'isolated', 3.0, 10.0, 1.0, 6.738090425173506), + # Gate/okx, long + (False, 'futures', 'gate', 'isolated', 5.0, 10.0, 1.0, 8.085708510208207), + (False, 'futures', 'gate', 'isolated', 3.0, 10.0, 1.0, 6.738090425173506), (False, 'futures', 'okx', 'isolated', 3.0, 10.0, 1.0, 6.738090425173506), + # bybit, long + (False, 'futures', 'bybit', 'isolated', 1.0, 10.0, 1.0, 0.1), + (False, 'futures', 'bybit', 'isolated', 3.0, 10.0, 1.0, 6.7666666), + (False, 'futures', 'bybit', 'isolated', 5.0, 10.0, 1.0, 8.1), + (False, 'futures', 'bybit', 'isolated', 10.0, 10.0, 1.0, 9.1), + # bybit, short + (True, 'futures', 'bybit', 'isolated', 1.0, 10.0, 1.0, 19.9), + (True, 'futures', 'bybit', 'isolated', 3.0, 10.0, 1.0, 13.233333), + (True, 'futures', 'bybit', 'isolated', 5.0, 10.0, 1.0, 11.9), + (True, 'futures', 'bybit', 'isolated', 10.0, 10.0, 1.0, 10.9), ] ) def test_get_liquidation_price( @@ -5182,7 +5249,7 @@ def test_get_liquidation_price( leverage = 5, open_rate = 10, amount = 0.6 ((1.6 + 0.01) - (1 * 0.6 * 10)) / ((0.6 * 0.01) - (1 * 0.6)) = 7.39057239057239 - Gateio/Okx, Short + Gate/Okx, Short leverage = 5, open_rate = 10, amount = 1.0 (open_rate + (wallet_balance / position)) / (1 + (mm_ratio + taker_fee_rate)) (10 + (2 / 1.0)) / (1 + (0.01 + 0.0006)) = 11.87413417771621 @@ -5193,7 +5260,7 @@ def test_get_liquidation_price( leverage = 5, open_rate = 8, amount = 1.0 (8 + (1.6 / 1.0)) / (1 + (0.01 + 0.0006)) = 9.499307342172967 - Gateio/Okx, Long + Gate/Okx, Long leverage = 5, open_rate = 10, amount = 1.0 (open_rate - (wallet_balance / position)) / (1 - (mm_ratio + taker_fee_rate)) (10 - (2 / 1)) / (1 - (0.01 + 0.0006)) = 8.085708510208207 @@ -5208,7 +5275,7 @@ def test_get_liquidation_price( default_conf_usdt['trading_mode'] = trading_mode default_conf_usdt['exchange']['name'] = exchange_name default_conf_usdt['margin_mode'] = margin_mode - mocker.patch('freqtrade.exchange.Gateio.validate_ordertypes') + mocker.patch('freqtrade.exchange.Gate.validate_ordertypes') exchange = get_patched_exchange(mocker, default_conf_usdt, id=exchange_name) exchange.get_maintenance_ratio_and_amt = MagicMock(return_value=(0.01, 0.01)) @@ -5222,7 +5289,7 @@ def test_get_liquidation_price( amount=amount, stake_amount=amount * open_rate / leverage, wallet_balance=amount * open_rate / leverage, - # leverage=leverage, + leverage=leverage, is_short=is_short, ) if expected_liq is None: @@ -5260,7 +5327,7 @@ def test_stoploss_contract_size(mocker, default_conf, contract_size, order_amoun exchange.get_contract_size = MagicMock(return_value=contract_size) api_mock.create_order.reset_mock() - order = exchange.stoploss( + order = exchange.create_stoploss( pair='ETH/BTC', amount=100, stop_price=220, diff --git a/tests/exchange/test_gateio.py b/tests/exchange/test_gate.py similarity index 85% rename from tests/exchange/test_gateio.py rename to tests/exchange/test_gate.py index dabdbba65..f777dd7d0 100644 --- a/tests/exchange/test_gateio.py +++ b/tests/exchange/test_gate.py @@ -5,22 +5,22 @@ import pytest from freqtrade.enums import MarginMode, TradingMode from freqtrade.exceptions import OperationalException -from freqtrade.exchange import Gateio +from freqtrade.exchange import Gate from freqtrade.resolvers.exchange_resolver import ExchangeResolver from tests.conftest import get_patched_exchange -def test_validate_order_types_gateio(default_conf, mocker): - default_conf['exchange']['name'] = 'gateio' +def test_validate_order_types_gate(default_conf, mocker): + default_conf['exchange']['name'] = 'gate' mocker.patch('freqtrade.exchange.Exchange._init_ccxt') mocker.patch('freqtrade.exchange.Exchange._load_markets', return_value={}) mocker.patch('freqtrade.exchange.Exchange.validate_pairs') mocker.patch('freqtrade.exchange.Exchange.validate_timeframes') mocker.patch('freqtrade.exchange.Exchange.validate_stakecurrency') mocker.patch('freqtrade.exchange.Exchange.validate_pricing') - mocker.patch('freqtrade.exchange.Exchange.name', 'Bittrex') - exch = ExchangeResolver.load_exchange('gateio', default_conf, True) - assert isinstance(exch, Gateio) + mocker.patch('freqtrade.exchange.Exchange.name', 'Gate') + exch = ExchangeResolver.load_exchange('gate', default_conf, True) + assert isinstance(exch, Gate) default_conf['order_types'] = { 'entry': 'market', @@ -31,18 +31,18 @@ def test_validate_order_types_gateio(default_conf, mocker): with pytest.raises(OperationalException, match=r'Exchange .* does not support market orders.'): - ExchangeResolver.load_exchange('gateio', default_conf, True) + ExchangeResolver.load_exchange('gate', default_conf, True) # market-orders supported on futures markets. default_conf['trading_mode'] = 'futures' default_conf['margin_mode'] = 'isolated' - ex = ExchangeResolver.load_exchange('gateio', default_conf, True) + ex = ExchangeResolver.load_exchange('gate', default_conf, True) assert ex @pytest.mark.usefixtures("init_persistence") -def test_fetch_stoploss_order_gateio(default_conf, mocker): - exchange = get_patched_exchange(mocker, default_conf, id='gateio') +def test_fetch_stoploss_order_gate(default_conf, mocker): + exchange = get_patched_exchange(mocker, default_conf, id='gate') fetch_order_mock = MagicMock() exchange.fetch_order = fetch_order_mock @@ -56,7 +56,7 @@ def test_fetch_stoploss_order_gateio(default_conf, mocker): default_conf['trading_mode'] = 'futures' default_conf['margin_mode'] = 'isolated' - exchange = get_patched_exchange(mocker, default_conf, id='gateio') + exchange = get_patched_exchange(mocker, default_conf, id='gate') exchange.fetch_order = MagicMock(return_value={ 'status': 'closed', @@ -73,8 +73,8 @@ def test_fetch_stoploss_order_gateio(default_conf, mocker): assert exchange.fetch_order.call_args_list[1][1]['order_id'] == '222555' -def test_cancel_stoploss_order_gateio(default_conf, mocker): - exchange = get_patched_exchange(mocker, default_conf, id='gateio') +def test_cancel_stoploss_order_gate(default_conf, mocker): + exchange = get_patched_exchange(mocker, default_conf, id='gate') cancel_order_mock = MagicMock() exchange.cancel_order = cancel_order_mock @@ -90,8 +90,8 @@ def test_cancel_stoploss_order_gateio(default_conf, mocker): (1501, 1499, 1501, "sell"), (1499, 1501, 1499, "buy") ]) -def test_stoploss_adjust_gateio(mocker, default_conf, sl1, sl2, sl3, side): - exchange = get_patched_exchange(mocker, default_conf, id='gateio') +def test_stoploss_adjust_gate(mocker, default_conf, sl1, sl2, sl3, side): + exchange = get_patched_exchange(mocker, default_conf, id='gate') order = { 'price': 1500, 'stopPrice': 1500, @@ -104,7 +104,7 @@ def test_stoploss_adjust_gateio(mocker, default_conf, sl1, sl2, sl3, side): ('taker', 0.0005, 0.0001554325), ('maker', 0.0, 0.0), ]) -def test_fetch_my_trades_gateio(mocker, default_conf, takerormaker, rate, cost): +def test_fetch_my_trades_gate(mocker, default_conf, takerormaker, rate, cost): mocker.patch('freqtrade.exchange.Exchange.exchange_has', return_value=True) tick = {'ETH/USDT:USDT': { 'info': {'user_id': '', @@ -134,7 +134,7 @@ def test_fetch_my_trades_gateio(mocker, default_conf, takerormaker, rate, cost): 'takerOrMaker': takerormaker, 'amount': 1, # 1 contract }]) - exchange = get_patched_exchange(mocker, default_conf, api_mock=api_mock, id='gateio') + exchange = get_patched_exchange(mocker, default_conf, api_mock=api_mock, id='gate') exchange._trading_fees = tick trades = exchange.get_trades_for_order('22255', 'ETH/USDT:USDT', datetime.now(timezone.utc)) trade = trades[0] diff --git a/tests/exchange/test_huobi.py b/tests/exchange/test_huobi.py index 2ce379a47..e5fa986c3 100644 --- a/tests/exchange/test_huobi.py +++ b/tests/exchange/test_huobi.py @@ -14,7 +14,7 @@ from tests.exchange.test_exchange import ccxt_exceptionhandlers (0.99, 220 * 0.99, "sell"), (0.98, 220 * 0.98, "sell"), ]) -def test_stoploss_order_huobi(default_conf, mocker, limitratio, expected, side): +def test_create_stoploss_order_huobi(default_conf, mocker, limitratio, expected, side): api_mock = MagicMock() order_id = 'test_prod_buy_{}'.format(randint(0, 10 ** 6)) order_type = 'stop-limit' @@ -32,15 +32,15 @@ def test_stoploss_order_huobi(default_conf, mocker, limitratio, expected, side): exchange = get_patched_exchange(mocker, default_conf, api_mock, 'huobi') with pytest.raises(OperationalException): - order = exchange.stoploss(pair='ETH/BTC', amount=1, stop_price=190, - order_types={'stoploss_on_exchange_limit_ratio': 1.05}, - side=side, - leverage=1.0) + order = exchange.create_stoploss(pair='ETH/BTC', amount=1, stop_price=190, + order_types={'stoploss_on_exchange_limit_ratio': 1.05}, + side=side, + leverage=1.0) api_mock.create_order.reset_mock() order_types = {} if limitratio is None else {'stoploss_on_exchange_limit_ratio': limitratio} - order = exchange.stoploss(pair='ETH/BTC', amount=1, stop_price=220, order_types=order_types, - side=side, leverage=1.0) + order = exchange.create_stoploss( + pair='ETH/BTC', amount=1, stop_price=220, order_types=order_types, side=side, leverage=1.0) assert 'id' in order assert 'info' in order @@ -59,23 +59,23 @@ def test_stoploss_order_huobi(default_conf, mocker, limitratio, expected, side): with pytest.raises(DependencyException): api_mock.create_order = MagicMock(side_effect=ccxt.InsufficientFunds("0 balance")) exchange = get_patched_exchange(mocker, default_conf, api_mock, 'huobi') - exchange.stoploss(pair='ETH/BTC', amount=1, stop_price=220, - order_types={}, side=side, leverage=1.0) + exchange.create_stoploss(pair='ETH/BTC', amount=1, stop_price=220, + order_types={}, side=side, leverage=1.0) with pytest.raises(InvalidOrderException): api_mock.create_order = MagicMock( side_effect=ccxt.InvalidOrder("binance Order would trigger immediately.")) exchange = get_patched_exchange(mocker, default_conf, api_mock, 'binance') - exchange.stoploss(pair='ETH/BTC', amount=1, stop_price=220, - order_types={}, side=side, leverage=1.0) + exchange.create_stoploss(pair='ETH/BTC', amount=1, stop_price=220, + order_types={}, side=side, leverage=1.0) ccxt_exceptionhandlers(mocker, default_conf, api_mock, "huobi", - "stoploss", "create_order", retries=1, + "create_stoploss", "create_order", retries=1, pair='ETH/BTC', amount=1, stop_price=220, order_types={}, side=side, leverage=1.0) -def test_stoploss_order_dry_run_huobi(default_conf, mocker): +def test_create_stoploss_order_dry_run_huobi(default_conf, mocker): api_mock = MagicMock() order_type = 'stop-limit' default_conf['dry_run'] = True @@ -85,14 +85,14 @@ def test_stoploss_order_dry_run_huobi(default_conf, mocker): exchange = get_patched_exchange(mocker, default_conf, api_mock, 'huobi') with pytest.raises(OperationalException): - order = exchange.stoploss(pair='ETH/BTC', amount=1, stop_price=190, - order_types={'stoploss_on_exchange_limit_ratio': 1.05}, - side='sell', leverage=1.0) + order = exchange.create_stoploss(pair='ETH/BTC', amount=1, stop_price=190, + order_types={'stoploss_on_exchange_limit_ratio': 1.05}, + side='sell', leverage=1.0) api_mock.create_order.reset_mock() - order = exchange.stoploss(pair='ETH/BTC', amount=1, stop_price=220, - order_types={}, side='sell', leverage=1.0) + order = exchange.create_stoploss(pair='ETH/BTC', amount=1, stop_price=220, + order_types={}, side='sell', leverage=1.0) assert 'id' in order assert 'info' in order diff --git a/tests/exchange/test_kraken.py b/tests/exchange/test_kraken.py index 66006f2fe..3a183de93 100644 --- a/tests/exchange/test_kraken.py +++ b/tests/exchange/test_kraken.py @@ -179,7 +179,7 @@ def test_get_balances_prod(default_conf, mocker): ("sell", 217.8), ("buy", 222.2), ]) -def test_stoploss_order_kraken(default_conf, mocker, ordertype, side, adjustedprice): +def test_create_stoploss_order_kraken(default_conf, mocker, ordertype, side, adjustedprice): api_mock = MagicMock() order_id = 'test_prod_buy_{}'.format(randint(0, 10 ** 6)) @@ -196,7 +196,7 @@ def test_stoploss_order_kraken(default_conf, mocker, ordertype, side, adjustedpr exchange = get_patched_exchange(mocker, default_conf, api_mock, 'kraken') - order = exchange.stoploss( + order = exchange.create_stoploss( pair='ETH/BTC', amount=1, stop_price=220, @@ -230,7 +230,7 @@ def test_stoploss_order_kraken(default_conf, mocker, ordertype, side, adjustedpr with pytest.raises(DependencyException): api_mock.create_order = MagicMock(side_effect=ccxt.InsufficientFunds("0 balance")) exchange = get_patched_exchange(mocker, default_conf, api_mock, 'kraken') - exchange.stoploss( + exchange.create_stoploss( pair='ETH/BTC', amount=1, stop_price=220, @@ -243,7 +243,7 @@ def test_stoploss_order_kraken(default_conf, mocker, ordertype, side, adjustedpr api_mock.create_order = MagicMock( side_effect=ccxt.InvalidOrder("kraken Order would trigger immediately.")) exchange = get_patched_exchange(mocker, default_conf, api_mock, 'kraken') - exchange.stoploss( + exchange.create_stoploss( pair='ETH/BTC', amount=1, stop_price=220, @@ -253,13 +253,13 @@ def test_stoploss_order_kraken(default_conf, mocker, ordertype, side, adjustedpr ) ccxt_exceptionhandlers(mocker, default_conf, api_mock, "kraken", - "stoploss", "create_order", retries=1, + "create_stoploss", "create_order", retries=1, pair='ETH/BTC', amount=1, stop_price=220, order_types={}, side=side, leverage=1.0) @pytest.mark.parametrize('side', ['buy', 'sell']) -def test_stoploss_order_dry_run_kraken(default_conf, mocker, side): +def test_create_stoploss_order_dry_run_kraken(default_conf, mocker, side): api_mock = MagicMock() default_conf['dry_run'] = True mocker.patch('freqtrade.exchange.Exchange.amount_to_precision', lambda s, x, y: y) @@ -269,7 +269,7 @@ def test_stoploss_order_dry_run_kraken(default_conf, mocker, side): api_mock.create_order.reset_mock() - order = exchange.stoploss( + order = exchange.create_stoploss( pair='ETH/BTC', amount=1, stop_price=220, diff --git a/tests/exchange/test_kucoin.py b/tests/exchange/test_kucoin.py index ebaf5ae81..65c855b7a 100644 --- a/tests/exchange/test_kucoin.py +++ b/tests/exchange/test_kucoin.py @@ -15,7 +15,7 @@ from tests.exchange.test_exchange import ccxt_exceptionhandlers (0.99, 220 * 0.99, "sell"), (0.98, 220 * 0.98, "sell"), ]) -def test_stoploss_order_kucoin(default_conf, mocker, limitratio, expected, side, order_type): +def test_create_stoploss_order_kucoin(default_conf, mocker, limitratio, expected, side, order_type): api_mock = MagicMock() order_id = 'test_prod_buy_{}'.format(randint(0, 10 ** 6)) @@ -32,18 +32,18 @@ def test_stoploss_order_kucoin(default_conf, mocker, limitratio, expected, side, exchange = get_patched_exchange(mocker, default_conf, api_mock, 'kucoin') if order_type == 'limit': with pytest.raises(OperationalException): - order = exchange.stoploss(pair='ETH/BTC', amount=1, stop_price=190, - order_types={ - 'stoploss': order_type, - 'stoploss_on_exchange_limit_ratio': 1.05}, - side=side, leverage=1.0) + order = exchange.create_stoploss(pair='ETH/BTC', amount=1, stop_price=190, + order_types={ + 'stoploss': order_type, + 'stoploss_on_exchange_limit_ratio': 1.05}, + side=side, leverage=1.0) api_mock.create_order.reset_mock() order_types = {'stoploss': order_type} if limitratio is not None: order_types.update({'stoploss_on_exchange_limit_ratio': limitratio}) - order = exchange.stoploss(pair='ETH/BTC', amount=1, stop_price=220, - order_types=order_types, side=side, leverage=1.0) + order = exchange.create_stoploss(pair='ETH/BTC', amount=1, stop_price=220, + order_types=order_types, side=side, leverage=1.0) assert 'id' in order assert 'info' in order @@ -67,18 +67,18 @@ def test_stoploss_order_kucoin(default_conf, mocker, limitratio, expected, side, with pytest.raises(DependencyException): api_mock.create_order = MagicMock(side_effect=ccxt.InsufficientFunds("0 balance")) exchange = get_patched_exchange(mocker, default_conf, api_mock, 'kucoin') - exchange.stoploss(pair='ETH/BTC', amount=1, stop_price=220, - order_types={}, side=side, leverage=1.0) + exchange.create_stoploss(pair='ETH/BTC', amount=1, stop_price=220, + order_types={}, side=side, leverage=1.0) with pytest.raises(InvalidOrderException): api_mock.create_order = MagicMock( side_effect=ccxt.InvalidOrder("kucoin Order would trigger immediately.")) exchange = get_patched_exchange(mocker, default_conf, api_mock, 'kucoin') - exchange.stoploss(pair='ETH/BTC', amount=1, stop_price=220, - order_types={}, side=side, leverage=1.0) + exchange.create_stoploss(pair='ETH/BTC', amount=1, stop_price=220, + order_types={}, side=side, leverage=1.0) ccxt_exceptionhandlers(mocker, default_conf, api_mock, "kucoin", - "stoploss", "create_order", retries=1, + "create_stoploss", "create_order", retries=1, pair='ETH/BTC', amount=1, stop_price=220, order_types={}, side=side, leverage=1.0) @@ -93,15 +93,15 @@ def test_stoploss_order_dry_run_kucoin(default_conf, mocker): exchange = get_patched_exchange(mocker, default_conf, api_mock, 'kucoin') with pytest.raises(OperationalException): - order = exchange.stoploss(pair='ETH/BTC', amount=1, stop_price=190, - order_types={'stoploss': 'limit', - 'stoploss_on_exchange_limit_ratio': 1.05}, - side='sell', leverage=1.0) + order = exchange.create_stoploss(pair='ETH/BTC', amount=1, stop_price=190, + order_types={'stoploss': 'limit', + 'stoploss_on_exchange_limit_ratio': 1.05}, + side='sell', leverage=1.0) api_mock.create_order.reset_mock() - order = exchange.stoploss(pair='ETH/BTC', amount=1, stop_price=220, - order_types={}, side='sell', leverage=1.0) + order = exchange.create_stoploss(pair='ETH/BTC', amount=1, stop_price=220, + order_types={}, side='sell', leverage=1.0) assert 'id' in order assert 'info' in order diff --git a/tests/freqai/conftest.py b/tests/freqai/conftest.py index bee7df27e..68e7ea49a 100644 --- a/tests/freqai/conftest.py +++ b/tests/freqai/conftest.py @@ -27,7 +27,7 @@ def freqai_conf(default_conf, tmpdir): "timerange": "20180110-20180115", "freqai": { "enabled": True, - "purge_old_models": True, + "purge_old_models": 2, "train_period_days": 2, "backtest_period_days": 10, "live_retrain_hours": 0, @@ -46,6 +46,8 @@ def freqai_conf(default_conf, tmpdir): "use_SVM_to_remove_outliers": True, "stratify_training_data": 0, "indicator_periods_candles": [10], + "shuffle_after_split": False, + "buffer_train_data_candles": 0 }, "data_split_parameters": {"test_size": 0.33, "shuffle": False}, "model_training_parameters": {"n_estimators": 100}, diff --git a/tests/freqai/test_freqai_interface.py b/tests/freqai/test_freqai_interface.py index 4ef99720a..5565bbed2 100644 --- a/tests/freqai/test_freqai_interface.py +++ b/tests/freqai/test_freqai_interface.py @@ -27,19 +27,20 @@ def is_mac() -> bool: return "Darwin" in machine -@pytest.mark.parametrize('model, pca, dbscan, float32, can_short', [ - ('LightGBMRegressor', True, False, True, True), - ('XGBoostRegressor', False, True, False, True), - ('XGBoostRFRegressor', False, False, False, True), - ('CatboostRegressor', False, False, False, True), - ('ReinforcementLearner', False, True, False, True), - ('ReinforcementLearner_multiproc', False, False, False, True), - ('ReinforcementLearner_test_3ac', False, False, False, False), - ('ReinforcementLearner_test_3ac', False, False, False, True), - ('ReinforcementLearner_test_4ac', False, False, False, True) +@pytest.mark.parametrize('model, pca, dbscan, float32, can_short, shuffle, buffer', [ + ('LightGBMRegressor', True, False, True, True, False, 0), + ('XGBoostRegressor', False, True, False, True, False, 10), + ('XGBoostRFRegressor', False, False, False, True, False, 0), + ('CatboostRegressor', False, False, False, True, True, 0), + ('ReinforcementLearner', False, True, False, True, False, 0), + ('ReinforcementLearner_multiproc', False, False, False, True, False, 0), + ('ReinforcementLearner_test_3ac', False, False, False, False, False, 0), + ('ReinforcementLearner_test_3ac', False, False, False, True, False, 0), + ('ReinforcementLearner_test_4ac', False, False, False, True, False, 0) ]) def test_extract_data_and_train_model_Standard(mocker, freqai_conf, model, pca, - dbscan, float32, can_short): + dbscan, float32, can_short, shuffle, buffer): + if is_arm() and model == 'CatboostRegressor': pytest.skip("CatBoost is not supported on ARM") @@ -53,6 +54,8 @@ def test_extract_data_and_train_model_Standard(mocker, freqai_conf, model, pca, freqai_conf['freqai']['feature_parameters'].update({"principal_component_analysis": pca}) freqai_conf['freqai']['feature_parameters'].update({"use_DBSCAN_to_remove_outliers": dbscan}) freqai_conf.update({"reduce_df_footprint": float32}) + freqai_conf['freqai']['feature_parameters'].update({"shuffle_after_split": shuffle}) + freqai_conf['freqai']['feature_parameters'].update({"buffer_train_data_candles": buffer}) if 'ReinforcementLearner' in model: model_save_ext = 'zip' @@ -376,57 +379,6 @@ def test_backtesting_fit_live_predictions(mocker, freqai_conf, caplog): shutil.rmtree(Path(freqai.dk.full_path)) -def test_follow_mode(mocker, freqai_conf): - freqai_conf.update({"timerange": "20180110-20180130"}) - - strategy = get_patched_freqai_strategy(mocker, freqai_conf) - exchange = get_patched_exchange(mocker, freqai_conf) - strategy.dp = DataProvider(freqai_conf, exchange) - strategy.freqai_info = freqai_conf.get("freqai", {}) - freqai = strategy.freqai - freqai.live = True - freqai.dk = FreqaiDataKitchen(freqai_conf) - timerange = TimeRange.parse_timerange("20180110-20180130") - freqai.dd.load_all_pair_histories(timerange, freqai.dk) - - metadata = {"pair": "ADA/BTC"} - freqai.dd.set_pair_dict_info(metadata) - - data_load_timerange = TimeRange.parse_timerange("20180110-20180130") - new_timerange = TimeRange.parse_timerange("20180120-20180130") - - freqai.extract_data_and_train_model( - new_timerange, "ADA/BTC", strategy, freqai.dk, data_load_timerange) - - assert Path(freqai.dk.data_path / f"{freqai.dk.model_filename}_model.joblib").is_file() - assert Path(freqai.dk.data_path / f"{freqai.dk.model_filename}_metadata.json").is_file() - assert Path(freqai.dk.data_path / f"{freqai.dk.model_filename}_trained_df.pkl").is_file() - assert Path(freqai.dk.data_path / f"{freqai.dk.model_filename}_svm_model.joblib").is_file() - - # start the follower and ask it to predict on existing files - - freqai_conf.get("freqai", {}).update({"follow_mode": "true"}) - - strategy = get_patched_freqai_strategy(mocker, freqai_conf) - exchange = get_patched_exchange(mocker, freqai_conf) - strategy.dp = DataProvider(freqai_conf, exchange) - strategy.freqai_info = freqai_conf.get("freqai", {}) - freqai = strategy.freqai - freqai.live = True - freqai.dk = FreqaiDataKitchen(freqai_conf, freqai.live) - timerange = TimeRange.parse_timerange("20180110-20180130") - freqai.dd.load_all_pair_histories(timerange, freqai.dk) - - df = strategy.dp.get_pair_dataframe('ADA/BTC', '5m') - - freqai.dk.pair = "ADA/BTC" - freqai.start_live(df, metadata, strategy, freqai.dk) - - assert len(freqai.dk.return_dataframe.index) == 5702 - - shutil.rmtree(Path(freqai.dk.full_path)) - - def test_principal_component_analysis(mocker, freqai_conf): freqai_conf.update({"timerange": "20180110-20180130"}) freqai_conf.get("freqai", {}).get("feature_parameters", {}).update( diff --git a/tests/rpc/test_rpc.py b/tests/rpc/test_rpc.py index 4871d9b24..31e19ce3f 100644 --- a/tests/rpc/test_rpc.py +++ b/tests/rpc/test_rpc.py @@ -112,7 +112,7 @@ def test_rpc_trade_status(default_conf, ticker, fee, mocker) -> None: 'freqtrade.exchange.Exchange', fetch_ticker=ticker, get_fee=fee, - _is_dry_limit_order_filled=MagicMock(side_effect=[False, True]), + _dry_is_price_crossed=MagicMock(side_effect=[False, True]), ) freqtradebot = get_patched_freqtradebot(mocker, default_conf) @@ -226,7 +226,7 @@ def test_rpc_status_table(default_conf, ticker, fee, mocker) -> None: freqtradebot.state = State.RUNNING with pytest.raises(RPCException, match=r'.*no active trade*'): rpc._rpc_status_table(default_conf['stake_currency'], 'USD') - mocker.patch('freqtrade.exchange.Exchange._is_dry_limit_order_filled', return_value=False) + mocker.patch('freqtrade.exchange.Exchange._dry_is_price_crossed', return_value=False) freqtradebot.enter_positions() result, headers, fiat_profit_sum = rpc._rpc_status_table(default_conf['stake_currency'], 'USD') @@ -237,7 +237,7 @@ def test_rpc_status_table(default_conf, ticker, fee, mocker) -> None: assert '0.00' == result[0][3] assert isnan(fiat_profit_sum) - mocker.patch('freqtrade.exchange.Exchange._is_dry_limit_order_filled', return_value=True) + mocker.patch('freqtrade.exchange.Exchange._dry_is_price_crossed', return_value=True) freqtradebot.process() result, headers, fiat_profit_sum = rpc._rpc_status_table(default_conf['stake_currency'], 'USD') @@ -688,7 +688,7 @@ def test_rpc_force_exit(default_conf, ticker, fee, mocker) -> None: 'filled': 0.0, } ), - _is_dry_limit_order_filled=MagicMock(return_value=True), + _dry_is_price_crossed=MagicMock(return_value=True), get_fee=fee, ) mocker.patch('freqtrade.wallets.Wallets.get_free', return_value=1000) @@ -726,7 +726,7 @@ def test_rpc_force_exit(default_conf, ticker, fee, mocker) -> None: freqtradebot.state = State.RUNNING assert cancel_order_mock.call_count == 0 mocker.patch( - 'freqtrade.exchange.Exchange._is_dry_limit_order_filled', MagicMock(return_value=False)) + 'freqtrade.exchange.Exchange._dry_is_price_crossed', MagicMock(return_value=False)) freqtradebot.enter_positions() # make an limit-buy open trade trade = Trade.query.filter(Trade.id == '3').first() diff --git a/tests/rpc/test_rpc_apiserver.py b/tests/rpc/test_rpc_apiserver.py index cd1a988d2..43d9abb78 100644 --- a/tests/rpc/test_rpc_apiserver.py +++ b/tests/rpc/test_rpc_apiserver.py @@ -706,6 +706,46 @@ def test_api_delete_trade(botclient, mocker, fee, markets, is_short): assert_response(rc, 502) +@pytest.mark.parametrize('is_short', [True, False]) +def test_api_delete_open_order(botclient, mocker, fee, markets, ticker, is_short): + ftbot, client = botclient + patch_get_signal(ftbot, enter_long=not is_short, enter_short=is_short) + stoploss_mock = MagicMock() + cancel_mock = MagicMock() + mocker.patch.multiple( + 'freqtrade.exchange.Exchange', + markets=PropertyMock(return_value=markets), + fetch_ticker=ticker, + cancel_order=cancel_mock, + cancel_stoploss_order=stoploss_mock, + ) + + rc = client_delete(client, f"{BASE_URI}/trades/10/open-order") + assert_response(rc, 502) + assert 'Invalid trade_id.' in rc.json()['error'] + + create_mock_trades(fee, is_short=is_short) + Trade.commit() + + rc = client_delete(client, f"{BASE_URI}/trades/5/open-order") + assert_response(rc, 502) + assert 'No open order for trade_id' in rc.json()['error'] + trade = Trade.get_trades([Trade.id == 6]).first() + mocker.patch('freqtrade.exchange.Exchange.fetch_order', + side_effect=ExchangeError) + rc = client_delete(client, f"{BASE_URI}/trades/6/open-order") + assert_response(rc, 502) + assert 'Order not found.' in rc.json()['error'] + + trade = Trade.get_trades([Trade.id == 6]).first() + mocker.patch('freqtrade.exchange.Exchange.fetch_order', + return_value=trade.orders[-1].to_ccxt_object()) + + rc = client_delete(client, f"{BASE_URI}/trades/6/open-order") + assert_response(rc) + assert cancel_mock.call_count == 1 + + def test_api_logs(botclient): ftbot, client = botclient rc = client_get(client, f"{BASE_URI}/logs") @@ -1240,7 +1280,7 @@ def test_api_forceexit(botclient, mocker, ticker, fee, markets): fetch_ticker=ticker, get_fee=fee, markets=PropertyMock(return_value=markets), - _is_dry_limit_order_filled=MagicMock(return_value=True), + _dry_is_price_crossed=MagicMock(return_value=True), ) patch_get_signal(ftbot) @@ -1697,9 +1737,15 @@ def test_api_backtesting(botclient, mocker, fee, caplog, tmpdir): data['stake_amount'] = 101 mocker.patch('freqtrade.optimize.backtesting.Backtesting.backtest_one_strategy', - side_effect=DependencyException()) + side_effect=DependencyException('DeadBeef')) rc = client_post(client, f"{BASE_URI}/backtest", data=data) - assert log_has("Backtesting caused an error: ", caplog) + assert log_has("Backtesting caused an error: DeadBeef", caplog) + + rc = client_get(client, f"{BASE_URI}/backtest") + assert_response(rc) + result = rc.json() + assert result['status'] == 'error' + assert 'Backtest failed' in result['status_msg'] # Delete backtesting to avoid leakage since the backtest-object may stick around. rc = client_delete(client, f"{BASE_URI}/backtest") diff --git a/tests/rpc/test_rpc_telegram.py b/tests/rpc/test_rpc_telegram.py index 85475ae8e..855062af0 100644 --- a/tests/rpc/test_rpc_telegram.py +++ b/tests/rpc/test_rpc_telegram.py @@ -99,7 +99,7 @@ def test_telegram_init(default_conf, mocker, caplog) -> None: message_str = ("rpc.telegram is listening for following commands: [['status'], ['profit'], " "['balance'], ['start'], ['stop'], " "['forcesell', 'forceexit', 'fx'], ['forcebuy', 'forcelong'], ['forceshort'], " - "['trades'], ['delete'], ['performance'], " + "['trades'], ['delete'], ['coo', 'cancel_open_order'], ['performance'], " "['buys', 'entries'], ['sells', 'exits'], ['mix_tags'], " "['stats'], ['daily'], ['weekly'], ['monthly'], " "['count'], ['locks'], ['unlock', 'delete_locks'], " @@ -313,7 +313,7 @@ def test_status_handle(default_conf, update, ticker, fee, mocker) -> None: 'freqtrade.exchange.Exchange', fetch_ticker=ticker, get_fee=fee, - _is_dry_limit_order_filled=MagicMock(return_value=True), + _dry_is_price_crossed=MagicMock(return_value=True), ) status_table = MagicMock() mocker.patch.multiple( @@ -930,7 +930,7 @@ def test_telegram_forceexit_handle(default_conf, update, ticker, fee, 'freqtrade.exchange.Exchange', fetch_ticker=ticker, get_fee=fee, - _is_dry_limit_order_filled=MagicMock(return_value=True), + _dry_is_price_crossed=MagicMock(return_value=True), ) freqtradebot = FreqtradeBot(default_conf) @@ -999,7 +999,7 @@ def test_telegram_force_exit_down_handle(default_conf, update, ticker, fee, 'freqtrade.exchange.Exchange', fetch_ticker=ticker, get_fee=fee, - _is_dry_limit_order_filled=MagicMock(return_value=True), + _dry_is_price_crossed=MagicMock(return_value=True), ) freqtradebot = FreqtradeBot(default_conf) @@ -1070,7 +1070,7 @@ def test_forceexit_all_handle(default_conf, update, ticker, fee, mocker) -> None 'freqtrade.exchange.Exchange', fetch_ticker=ticker, get_fee=fee, - _is_dry_limit_order_filled=MagicMock(return_value=True), + _dry_is_price_crossed=MagicMock(return_value=True), ) default_conf['max_open_trades'] = 4 freqtradebot = FreqtradeBot(default_conf) @@ -1155,7 +1155,7 @@ def test_force_exit_no_pair(default_conf, update, ticker, fee, mocker) -> None: 'freqtrade.exchange.Exchange', fetch_ticker=ticker, get_fee=fee, - _is_dry_limit_order_filled=MagicMock(return_value=True), + _dry_is_price_crossed=MagicMock(return_value=True), ) femock = mocker.patch('freqtrade.rpc.rpc.RPC._rpc_force_exit') telegram, freqtradebot, msg_mock = get_telegram_testobject(mocker, default_conf) @@ -1678,6 +1678,40 @@ def test_telegram_delete_trade(mocker, update, default_conf, fee, is_short): assert "Please make sure to take care of this asset" in msg_mock.call_args_list[0][0][0] +@pytest.mark.parametrize('is_short', [True, False]) +def test_telegram_delete_open_order(mocker, update, default_conf, fee, is_short, ticker): + + mocker.patch.multiple( + 'freqtrade.exchange.Exchange', + fetch_ticker=ticker, + ) + telegram, _, msg_mock = get_telegram_testobject(mocker, default_conf) + context = MagicMock() + context.args = [] + + telegram._cancel_open_order(update=update, context=context) + assert "Trade-id not set." in msg_mock.call_args_list[0][0][0] + + msg_mock.reset_mock() + create_mock_trades(fee, is_short=is_short) + + context = MagicMock() + context.args = [5] + telegram._cancel_open_order(update=update, context=context) + assert "No open order for trade_id" in msg_mock.call_args_list[0][0][0] + + msg_mock.reset_mock() + + trade = Trade.get_trades([Trade.id == 6]).first() + mocker.patch('freqtrade.exchange.Exchange.fetch_order', + return_value=trade.orders[-1].to_ccxt_object()) + context = MagicMock() + context.args = [6] + telegram._cancel_open_order(update=update, context=context) + assert msg_mock.call_count == 1 + assert "Open order canceled." in msg_mock.call_args_list[0][0][0] + + def test_help_handle(default_conf, update, mocker) -> None: telegram, _, msg_mock = get_telegram_testobject(mocker, default_conf) diff --git a/tests/strategy/strats/freqai_rl_test_strat.py b/tests/strategy/strats/freqai_rl_test_strat.py index 6fa926fc9..2bf4aaa30 100644 --- a/tests/strategy/strats/freqai_rl_test_strat.py +++ b/tests/strategy/strats/freqai_rl_test_strat.py @@ -1,5 +1,6 @@ import logging from functools import reduce +from typing import Dict import talib.abstract as ta from pandas import DataFrame @@ -24,20 +25,21 @@ class freqai_rl_test_strat(IStrategy): startup_candle_count: int = 300 can_short = False - def feature_engineering_expand_all(self, dataframe, period, **kwargs): + def feature_engineering_expand_all(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, **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, **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 +51,7 @@ class freqai_rl_test_strat(IStrategy): return dataframe - def set_freqai_targets(self, dataframe, **kwargs): + def set_freqai_targets(self, dataframe: DataFrame, metadata: Dict, **kwargs): dataframe["&-action"] = 0 diff --git a/tests/strategy/strats/freqai_test_classifier.py b/tests/strategy/strats/freqai_test_classifier.py index 02427ab59..61b9f0c37 100644 --- a/tests/strategy/strats/freqai_test_classifier.py +++ b/tests/strategy/strats/freqai_test_classifier.py @@ -1,5 +1,6 @@ import logging from functools import reduce +from typing import Dict import numpy as np import talib.abstract as ta @@ -56,7 +57,8 @@ class freqai_test_classifier(IStrategy): informative_pairs.append((pair, tf)) return informative_pairs - def feature_engineering_expand_all(self, dataframe, period, **kwargs): + def feature_engineering_expand_all(self, dataframe: DataFrame, period: int, + metadata: Dict, **kwargs): dataframe["%-rsi-period"] = ta.RSI(dataframe, timeperiod=period) dataframe["%-mfi-period"] = ta.MFI(dataframe, timeperiod=period) @@ -64,7 +66,7 @@ class freqai_test_classifier(IStrategy): return dataframe - def feature_engineering_expand_basic(self, dataframe: DataFrame, **kwargs): + def feature_engineering_expand_basic(self, dataframe: DataFrame, metadata: Dict, **kwargs): dataframe["%-pct-change"] = dataframe["close"].pct_change() dataframe["%-raw_volume"] = dataframe["volume"] @@ -72,14 +74,14 @@ class freqai_test_classifier(IStrategy): return dataframe - def feature_engineering_standard(self, dataframe, **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, **kwargs): + def set_freqai_targets(self, dataframe: DataFrame, metadata: Dict, **kwargs): 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 65f2e4540..b2ddc21e3 100644 --- a/tests/strategy/strats/freqai_test_multimodel_classifier_strat.py +++ b/tests/strategy/strats/freqai_test_multimodel_classifier_strat.py @@ -1,5 +1,6 @@ import logging from functools import reduce +from typing import Dict import numpy as np import talib.abstract as ta @@ -43,7 +44,8 @@ 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, period, **kwargs): + def feature_engineering_expand_all(self, dataframe: DataFrame, period: int, + metadata: Dict, **kwargs): dataframe["%-rsi-period"] = ta.RSI(dataframe, timeperiod=period) dataframe["%-mfi-period"] = ta.MFI(dataframe, timeperiod=period) @@ -51,7 +53,7 @@ class freqai_test_multimodel_classifier_strat(IStrategy): return dataframe - def feature_engineering_expand_basic(self, dataframe: DataFrame, **kwargs): + def feature_engineering_expand_basic(self, dataframe: DataFrame, metadata: Dict, **kwargs): dataframe["%-pct-change"] = dataframe["close"].pct_change() dataframe["%-raw_volume"] = dataframe["volume"] @@ -59,14 +61,14 @@ class freqai_test_multimodel_classifier_strat(IStrategy): return dataframe - def feature_engineering_standard(self, dataframe, **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, **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 5c9712629..5b09598a5 100644 --- a/tests/strategy/strats/freqai_test_multimodel_strat.py +++ b/tests/strategy/strats/freqai_test_multimodel_strat.py @@ -1,5 +1,6 @@ import logging from functools import reduce +from typing import Dict import talib.abstract as ta from pandas import DataFrame @@ -42,7 +43,8 @@ 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, period, **kwargs): + def feature_engineering_expand_all(self, dataframe: DataFrame, period: int, + metadata: Dict, **kwargs): dataframe["%-rsi-period"] = ta.RSI(dataframe, timeperiod=period) dataframe["%-mfi-period"] = ta.MFI(dataframe, timeperiod=period) @@ -50,7 +52,7 @@ class freqai_test_multimodel_strat(IStrategy): return dataframe - def feature_engineering_expand_basic(self, dataframe: DataFrame, **kwargs): + def feature_engineering_expand_basic(self, dataframe: DataFrame, metadata: Dict, **kwargs): dataframe["%-pct-change"] = dataframe["close"].pct_change() dataframe["%-raw_volume"] = dataframe["volume"] @@ -58,14 +60,14 @@ class freqai_test_multimodel_strat(IStrategy): return dataframe - def feature_engineering_standard(self, dataframe, **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, **kwargs): + def set_freqai_targets(self, dataframe: DataFrame, metadata: Dict, **kwargs): dataframe["&-s_close"] = ( dataframe["close"] diff --git a/tests/strategy/strats/freqai_test_strat.py b/tests/strategy/strats/freqai_test_strat.py index b52c95908..6db308406 100644 --- a/tests/strategy/strats/freqai_test_strat.py +++ b/tests/strategy/strats/freqai_test_strat.py @@ -1,5 +1,6 @@ import logging from functools import reduce +from typing import Dict import talib.abstract as ta from pandas import DataFrame @@ -42,7 +43,8 @@ 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, period, **kwargs): + def feature_engineering_expand_all(self, dataframe: DataFrame, period: int, + metadata: Dict, **kwargs): dataframe["%-rsi-period"] = ta.RSI(dataframe, timeperiod=period) dataframe["%-mfi-period"] = ta.MFI(dataframe, timeperiod=period) @@ -50,7 +52,7 @@ class freqai_test_strat(IStrategy): return dataframe - def feature_engineering_expand_basic(self, dataframe: DataFrame, **kwargs): + def feature_engineering_expand_basic(self, dataframe: DataFrame, metadata: Dict, **kwargs): dataframe["%-pct-change"] = dataframe["close"].pct_change() dataframe["%-raw_volume"] = dataframe["volume"] @@ -58,14 +60,14 @@ class freqai_test_strat(IStrategy): return dataframe - def feature_engineering_standard(self, dataframe, **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, **kwargs): + def set_freqai_targets(self, dataframe: DataFrame, metadata: Dict, **kwargs): dataframe["&-s_close"] = ( dataframe["close"] diff --git a/tests/strategy/test_interface.py b/tests/strategy/test_interface.py index 294021c83..0b30d2059 100644 --- a/tests/strategy/test_interface.py +++ b/tests/strategy/test_interface.py @@ -214,12 +214,12 @@ def test_ignore_expired_candle(default_conf): current_time = latest_date + timedelta(seconds=30 + 300) - assert not strategy.ignore_expired_candle( + assert strategy.ignore_expired_candle( latest_date=latest_date, current_time=current_time, timeframe_seconds=300, enter=True - ) is True + ) is not True def test_assert_df_raise(mocker, caplog, ohlcv_history): @@ -452,8 +452,8 @@ def test_min_roi_reached3(default_conf, fee) -> None: (0.05, 0.9, ExitType.NONE, None, False, True, 0.09, 0.9, ExitType.NONE, lambda **kwargs: None), ]) -def test_stop_loss_reached(default_conf, fee, profit, adjusted, expected, liq, trailing, custom, - profit2, adjusted2, expected2, custom_stop) -> None: +def test_ft_stoploss_reached(default_conf, fee, profit, adjusted, expected, liq, trailing, custom, + profit2, adjusted2, expected2, custom_stop) -> None: strategy = StrategyResolver.load_strategy(default_conf) trade = Trade( @@ -477,9 +477,9 @@ def test_stop_loss_reached(default_conf, fee, profit, adjusted, expected, liq, t now = arrow.utcnow().datetime current_rate = trade.open_rate * (1 + profit) - sl_flag = strategy.stop_loss_reached(current_rate=current_rate, trade=trade, - current_time=now, current_profit=profit, - force_stoploss=0, high=None) + sl_flag = strategy.ft_stoploss_reached(current_rate=current_rate, trade=trade, + current_time=now, current_profit=profit, + force_stoploss=0, high=None) assert isinstance(sl_flag, ExitCheckTuple) assert sl_flag.exit_type == expected if expected == ExitType.NONE: @@ -489,9 +489,9 @@ def test_stop_loss_reached(default_conf, fee, profit, adjusted, expected, liq, t assert round(trade.stop_loss, 2) == adjusted current_rate2 = trade.open_rate * (1 + profit2) - sl_flag = strategy.stop_loss_reached(current_rate=current_rate2, trade=trade, - current_time=now, current_profit=profit2, - force_stoploss=0, high=None) + sl_flag = strategy.ft_stoploss_reached(current_rate=current_rate2, trade=trade, + current_time=now, current_profit=profit2, + force_stoploss=0, high=None) assert sl_flag.exit_type == expected2 if expected2 == ExitType.NONE: assert sl_flag.exit_flag is False @@ -579,7 +579,7 @@ def test_should_sell(default_conf, fee) -> None: assert res == [ExitCheckTuple(exit_type=ExitType.ROI)] strategy.min_roi_reached = MagicMock(return_value=True) - strategy.stop_loss_reached = MagicMock( + strategy.ft_stoploss_reached = MagicMock( return_value=ExitCheckTuple(exit_type=ExitType.STOP_LOSS)) res = strategy.should_exit(trade, 1, now, @@ -603,7 +603,7 @@ def test_should_sell(default_conf, fee) -> None: ExitCheckTuple(exit_type=ExitType.ROI), ] - strategy.stop_loss_reached = MagicMock( + strategy.ft_stoploss_reached = MagicMock( return_value=ExitCheckTuple(exit_type=ExitType.TRAILING_STOP_LOSS)) # Regular exit signal res = strategy.should_exit(trade, 1, now, diff --git a/tests/strategy/test_strategy_helpers.py b/tests/strategy/test_strategy_helpers.py index f42f9e681..36e997f7b 100644 --- a/tests/strategy/test_strategy_helpers.py +++ b/tests/strategy/test_strategy_helpers.py @@ -119,53 +119,54 @@ def test_merge_informative_pair_suffix_append_timeframe(): merge_informative_pair(data, informative, '15m', '1h', suffix="suf") -def test_stoploss_from_open(): +@pytest.mark.parametrize("side,profitrange", [ + # profit range for long is [-1, inf] while for shorts is [-inf, 1] + ("long", [-0.99, 2, 30]), + ("short", [-2.0, 0.99, 30]), +]) +def test_stoploss_from_open(side, profitrange): open_price_ranges = [ [0.01, 1.00, 30], [1, 100, 30], [100, 10000, 30], ] - # profit range for long is [-1, inf] while for shorts is [-inf, 1] - current_profit_range_dict = {'long': [-0.99, 2, 30], 'short': [-2.0, 0.99, 30]} - desired_stop_range = [-0.50, 0.50, 30] - for side, current_profit_range in current_profit_range_dict.items(): - for open_range in open_price_ranges: - for open_price in np.linspace(*open_range): - for desired_stop in np.linspace(*desired_stop_range): + for open_range in open_price_ranges: + for open_price in np.linspace(*open_range): + for desired_stop in np.linspace(-0.50, 0.50, 30): + if side == 'long': + # -1 is not a valid current_profit, should return 1 + assert stoploss_from_open(desired_stop, -1) == 1 + else: + # 1 is not a valid current_profit for shorts, should return 1 + assert stoploss_from_open(desired_stop, 1, True) == 1 + + for current_profit in np.linspace(*profitrange): if side == 'long': - # -1 is not a valid current_profit, should return 1 - assert stoploss_from_open(desired_stop, -1) == 1 + current_price = open_price * (1 + current_profit) + expected_stop_price = open_price * (1 + desired_stop) + stoploss = stoploss_from_open(desired_stop, current_profit) + stop_price = current_price * (1 - stoploss) else: - # 1 is not a valid current_profit for shorts, should return 1 - assert stoploss_from_open(desired_stop, 1, True) == 1 + current_price = open_price * (1 - current_profit) + expected_stop_price = open_price * (1 - desired_stop) + stoploss = stoploss_from_open(desired_stop, current_profit, True) + stop_price = current_price * (1 + stoploss) - for current_profit in np.linspace(*current_profit_range): - if side == 'long': - current_price = open_price * (1 + current_profit) - expected_stop_price = open_price * (1 + desired_stop) - stoploss = stoploss_from_open(desired_stop, current_profit) - stop_price = current_price * (1 - stoploss) - else: - current_price = open_price * (1 - current_profit) - expected_stop_price = open_price * (1 - desired_stop) - stoploss = stoploss_from_open(desired_stop, current_profit, True) - stop_price = current_price * (1 + stoploss) + assert stoploss >= 0 + # Technically the formula can yield values greater than 1 for shorts + # eventhough it doesn't make sense because the position would be liquidated + if side == 'long': + assert stoploss <= 1 - assert stoploss >= 0 - # Technically the formula can yield values greater than 1 for shorts - # eventhough it doesn't make sense because the position would be liquidated - if side == 'long': - assert stoploss <= 1 - - # there is no correct answer if the expected stop price is above - # the current price - if ((side == 'long' and expected_stop_price > current_price) - or (side == 'short' and expected_stop_price < current_price)): - assert stoploss == 0 - else: - assert pytest.approx(stop_price) == expected_stop_price + # there is no correct answer if the expected stop price is above + # the current price + if ((side == 'long' and expected_stop_price > current_price) + or (side == 'short' and expected_stop_price < current_price)): + assert stoploss == 0 + else: + assert pytest.approx(stop_price) == expected_stop_price def test_stoploss_from_absolute(): diff --git a/tests/test_configuration.py b/tests/test_configuration.py index a2a1b72cc..4a94a3c2e 100644 --- a/tests/test_configuration.py +++ b/tests/test_configuration.py @@ -701,15 +701,16 @@ def test_set_loggers_journald(mocker): 'logfile': 'journald', } + setup_logging_pre() setup_logging(config) - assert len(logger.handlers) == 2 + assert len(logger.handlers) == 3 assert [x for x in logger.handlers if type(x).__name__ == "JournaldLogHandler"] assert [x for x in logger.handlers if type(x) == logging.StreamHandler] # reset handlers to not break pytest logger.handlers = orig_handlers -def test_set_loggers_journald_importerror(mocker, import_fails): +def test_set_loggers_journald_importerror(import_fails): logger = logging.getLogger() orig_handlers = logger.handlers logger.handlers = [] @@ -718,7 +719,7 @@ def test_set_loggers_journald_importerror(mocker, import_fails): 'logfile': 'journald', } with pytest.raises(OperationalException, - match=r'You need the systemd python package.*'): + match=r'You need the cysystemd python package.*'): setup_logging(config) logger.handlers = orig_handlers diff --git a/tests/test_freqtradebot.py b/tests/test_freqtradebot.py index 7efd0393d..dc4539401 100644 --- a/tests/test_freqtradebot.py +++ b/tests/test_freqtradebot.py @@ -272,7 +272,7 @@ def test_total_open_trades_stakes(mocker, default_conf_usdt, ticker_usdt, fee) - 'freqtrade.exchange.Exchange', fetch_ticker=ticker_usdt, get_fee=fee, - _is_dry_limit_order_filled=MagicMock(return_value=False), + _dry_is_price_crossed=MagicMock(return_value=False), ) freqtrade = FreqtradeBot(default_conf_usdt) patch_get_signal(freqtrade) @@ -307,7 +307,7 @@ def test_create_trade(default_conf_usdt, ticker_usdt, limit_order, 'freqtrade.exchange.Exchange', fetch_ticker=ticker_usdt, get_fee=fee, - _is_dry_limit_order_filled=MagicMock(return_value=False), + _dry_is_price_crossed=MagicMock(return_value=False), ) # Save state of current whitelist @@ -737,20 +737,22 @@ def test_process_informative_pairs_added(default_conf_usdt, ticker_usdt, mocker) @pytest.mark.parametrize("is_short,trading_mode,exchange_name,margin_mode,liq_buffer,liq_price", [ (False, 'spot', 'binance', None, 0.0, None), (True, 'spot', 'binance', None, 0.0, None), - (False, 'spot', 'gateio', None, 0.0, None), - (True, 'spot', 'gateio', None, 0.0, None), + (False, 'spot', 'gate', None, 0.0, None), + (True, 'spot', 'gate', None, 0.0, None), (False, 'spot', 'okx', None, 0.0, None), (True, 'spot', 'okx', None, 0.0, None), (True, 'futures', 'binance', 'isolated', 0.0, 11.88151815181518), (False, 'futures', 'binance', 'isolated', 0.0, 8.080471380471382), - (True, 'futures', 'gateio', 'isolated', 0.0, 11.87413417771621), - (False, 'futures', 'gateio', 'isolated', 0.0, 8.085708510208207), + (True, 'futures', 'gate', 'isolated', 0.0, 11.87413417771621), + (False, 'futures', 'gate', 'isolated', 0.0, 8.085708510208207), (True, 'futures', 'binance', 'isolated', 0.05, 11.7874422442244), (False, 'futures', 'binance', 'isolated', 0.05, 8.17644781144781), - (True, 'futures', 'gateio', 'isolated', 0.05, 11.7804274688304), - (False, 'futures', 'gateio', 'isolated', 0.05, 8.181423084697796), + (True, 'futures', 'gate', 'isolated', 0.05, 11.7804274688304), + (False, 'futures', 'gate', 'isolated', 0.05, 8.181423084697796), (True, 'futures', 'okx', 'isolated', 0.0, 11.87413417771621), (False, 'futures', 'okx', 'isolated', 0.0, 8.085708510208207), + (True, 'futures', 'bybit', 'isolated', 0.0, 11.9), + (False, 'futures', 'bybit', 'isolated', 0.0, 8.1), ]) def test_execute_entry(mocker, default_conf_usdt, fee, limit_order, limit_order_open, is_short, trading_mode, @@ -766,11 +768,11 @@ def test_execute_entry(mocker, default_conf_usdt, fee, limit_order, ((wb + cum_b) - (side_1 * position * ep1)) / ((position * mmr_b) - (side_1 * position)) ((2 + 0.01) - (1 * 1 * 10)) / ((1 * 0.01) - (1 * 1)) = 8.070707070707071 - exchange_name = gateio/okx, is_short = true + exchange_name = gate/okx, is_short = true (open_rate + (wallet_balance / position)) / (1 + (mm_ratio + taker_fee_rate)) (10 + (2 / 1)) / (1 + (0.01 + 0.0006)) = 11.87413417771621 - exchange_name = gateio/okx, is_short = false + exchange_name = gate/okx, is_short = false (open_rate - (wallet_balance / position)) / (1 - (mm_ratio + taker_fee_rate)) (10 - (2 / 1)) / (1 - (0.01 + 0.0006)) = 8.085708510208207 """ @@ -783,7 +785,7 @@ def test_execute_entry(mocker, default_conf_usdt, fee, limit_order, default_conf_usdt['exchange']['name'] = exchange_name if margin_mode: default_conf_usdt['margin_mode'] = margin_mode - mocker.patch('freqtrade.exchange.Gateio.validate_ordertypes') + mocker.patch('freqtrade.exchange.Gate.validate_ordertypes') patch_RPCManager(mocker) patch_exchange(mocker, id=exchange_name) freqtrade = FreqtradeBot(default_conf_usdt) @@ -1068,7 +1070,7 @@ def test_add_stoploss_on_exchange(mocker, default_conf_usdt, limit_order, is_sho mocker.patch('freqtrade.exchange.Exchange.get_trades_for_order', return_value=[]) stoploss = MagicMock(return_value={'id': 13434334}) - mocker.patch('freqtrade.exchange.Binance.stoploss', stoploss) + mocker.patch('freqtrade.exchange.Binance.create_stoploss', stoploss) freqtrade = FreqtradeBot(default_conf_usdt) freqtrade.strategy.order_types['stoploss_on_exchange'] = True @@ -1107,7 +1109,7 @@ def test_handle_stoploss_on_exchange(mocker, default_conf_usdt, fee, caplog, is_ exit_order, ]), get_fee=fee, - stoploss=stoploss + create_stoploss=stoploss ) freqtrade = FreqtradeBot(default_conf_usdt) patch_get_signal(freqtrade, enter_short=is_short, enter_long=not is_short) @@ -1189,7 +1191,7 @@ def test_handle_stoploss_on_exchange(mocker, default_conf_usdt, fee, caplog, is_ caplog.clear() mocker.patch( - 'freqtrade.exchange.Exchange.stoploss', + 'freqtrade.exchange.Exchange.create_stoploss', side_effect=ExchangeError() ) trade.is_open = True @@ -1203,7 +1205,7 @@ def test_handle_stoploss_on_exchange(mocker, default_conf_usdt, fee, caplog, is_ stoploss.reset_mock() mocker.patch('freqtrade.exchange.Exchange.fetch_stoploss_order', side_effect=InvalidOrderException()) - mocker.patch('freqtrade.exchange.Exchange.stoploss', stoploss) + mocker.patch('freqtrade.exchange.Exchange.create_stoploss', stoploss) freqtrade.handle_stoploss_on_exchange(trade) assert stoploss.call_count == 1 @@ -1213,7 +1215,7 @@ def test_handle_stoploss_on_exchange(mocker, default_conf_usdt, fee, caplog, is_ trade.is_open = False stoploss.reset_mock() mocker.patch('freqtrade.exchange.Exchange.fetch_order') - mocker.patch('freqtrade.exchange.Exchange.stoploss', stoploss) + mocker.patch('freqtrade.exchange.Exchange.create_stoploss', stoploss) assert freqtrade.handle_stoploss_on_exchange(trade) is False assert stoploss.call_count == 0 @@ -1238,7 +1240,7 @@ def test_handle_stoploss_on_exchange(mocker, default_conf_usdt, fee, caplog, is_ mocker.patch('freqtrade.exchange.Exchange.cancel_stoploss_order_with_result', side_effect=InvalidOrderException()) mocker.patch('freqtrade.exchange.Exchange.fetch_stoploss_order', stoploss_order_cancelled) - mocker.patch('freqtrade.exchange.Exchange.stoploss', stoploss) + mocker.patch('freqtrade.exchange.Exchange.create_stoploss', stoploss) assert freqtrade.handle_stoploss_on_exchange(trade) is False assert trade.stoploss_order_id is None assert trade.is_open is False @@ -1269,7 +1271,7 @@ def test_handle_sle_cancel_cant_recreate(mocker, default_conf_usdt, fee, caplog, mocker.patch.multiple( 'freqtrade.exchange.Binance', fetch_stoploss_order=MagicMock(return_value={'status': 'canceled', 'id': 100}), - stoploss=MagicMock(side_effect=ExchangeError()), + create_stoploss=MagicMock(side_effect=ExchangeError()), ) freqtrade = FreqtradeBot(default_conf_usdt) patch_get_signal(freqtrade, enter_short=is_short, enter_long=not is_short) @@ -1313,7 +1315,7 @@ def test_create_stoploss_order_invalid_order( mocker.patch.multiple( 'freqtrade.exchange.Binance', fetch_order=MagicMock(return_value={'status': 'canceled'}), - stoploss=MagicMock(side_effect=InvalidOrderException()), + create_stoploss=MagicMock(side_effect=InvalidOrderException()), ) freqtrade = FreqtradeBot(default_conf_usdt) patch_get_signal(freqtrade, enter_short=is_short, enter_long=not is_short) @@ -1365,7 +1367,7 @@ def test_create_stoploss_order_insufficient_funds( ) mocker.patch.multiple( 'freqtrade.exchange.Binance', - stoploss=MagicMock(side_effect=InsufficientFundsError()), + create_stoploss=MagicMock(side_effect=InsufficientFundsError()), ) patch_get_signal(freqtrade, enter_short=is_short, enter_long=not is_short) freqtrade.strategy.order_types['stoploss_on_exchange'] = True @@ -1415,7 +1417,7 @@ def test_handle_stoploss_on_exchange_trailing( ) mocker.patch.multiple( 'freqtrade.exchange.Binance', - stoploss=stoploss, + create_stoploss=stoploss, stoploss_adjust=MagicMock(return_value=True), ) @@ -1476,7 +1478,7 @@ def test_handle_stoploss_on_exchange_trailing( cancel_order_mock = MagicMock() stoploss_order_mock = MagicMock(return_value={'id': 'so1'}) mocker.patch('freqtrade.exchange.Binance.cancel_stoploss_order', cancel_order_mock) - mocker.patch('freqtrade.exchange.Binance.stoploss', stoploss_order_mock) + mocker.patch('freqtrade.exchange.Binance.create_stoploss', stoploss_order_mock) # stoploss should not be updated as the interval is 60 seconds assert freqtrade.handle_trade(trade) is False @@ -1540,7 +1542,7 @@ def test_handle_stoploss_on_exchange_trailing_error( ) mocker.patch.multiple( 'freqtrade.exchange.Binance', - stoploss=stoploss, + create_stoploss=stoploss, stoploss_adjust=MagicMock(return_value=True), ) @@ -1591,7 +1593,7 @@ def test_handle_stoploss_on_exchange_trailing_error( trade.stoploss_last_update = arrow.utcnow().shift(minutes=-601).datetime caplog.clear() cancel_mock = mocker.patch("freqtrade.exchange.Binance.cancel_stoploss_order", MagicMock()) - mocker.patch("freqtrade.exchange.Binance.stoploss", side_effect=ExchangeError()) + mocker.patch("freqtrade.exchange.Binance.create_stoploss", side_effect=ExchangeError()) freqtrade.handle_trailing_stoploss_on_exchange(trade, stoploss_order_hanging) assert cancel_mock.call_count == 1 assert log_has_re(r"Could not create trailing stoploss order for pair ETH/USDT\..*", caplog) @@ -1609,7 +1611,7 @@ def test_stoploss_on_exchange_price_rounding( adjust_mock = MagicMock(return_value=False) mocker.patch.multiple( 'freqtrade.exchange.Binance', - stoploss=stoploss_mock, + create_stoploss=stoploss_mock, stoploss_adjust=adjust_mock, price_to_precision=price_mock, ) @@ -1648,7 +1650,7 @@ def test_handle_stoploss_on_exchange_custom_stop( ) mocker.patch.multiple( 'freqtrade.exchange.Binance', - stoploss=stoploss, + create_stoploss=stoploss, stoploss_adjust=MagicMock(return_value=True), ) @@ -1708,7 +1710,7 @@ def test_handle_stoploss_on_exchange_custom_stop( cancel_order_mock = MagicMock() stoploss_order_mock = MagicMock(return_value={'id': 'so1'}) mocker.patch('freqtrade.exchange.Binance.cancel_stoploss_order', cancel_order_mock) - mocker.patch('freqtrade.exchange.Binance.stoploss', stoploss_order_mock) + mocker.patch('freqtrade.exchange.Binance.create_stoploss', stoploss_order_mock) # stoploss should not be updated as the interval is 60 seconds assert freqtrade.handle_trade(trade) is False @@ -1773,7 +1775,7 @@ def test_tsl_on_exchange_compatible_with_edge(mocker, edge_conf, fee, limit_orde {'id': exit_order['id']}, ]), get_fee=fee, - stoploss=stoploss, + create_stoploss=stoploss, ) # enabling TSL @@ -1825,7 +1827,7 @@ def test_tsl_on_exchange_compatible_with_edge(mocker, edge_conf, fee, limit_orde cancel_order_mock = MagicMock() stoploss_order_mock = MagicMock() mocker.patch('freqtrade.exchange.Exchange.cancel_stoploss_order', cancel_order_mock) - mocker.patch('freqtrade.exchange.Binance.stoploss', stoploss_order_mock) + mocker.patch('freqtrade.exchange.Binance.create_stoploss', stoploss_order_mock) # price goes down 5% mocker.patch('freqtrade.exchange.Exchange.fetch_ticker', MagicMock(return_value={ @@ -3255,7 +3257,7 @@ def test_execute_trade_exit_up(default_conf_usdt, ticker_usdt, fee, ticker_usdt_ 'freqtrade.exchange.Exchange', fetch_ticker=ticker_usdt, get_fee=fee, - _is_dry_limit_order_filled=MagicMock(return_value=False), + _dry_is_price_crossed=MagicMock(return_value=False), ) patch_whitelist(mocker, default_conf_usdt) freqtrade = FreqtradeBot(default_conf_usdt) @@ -3338,7 +3340,7 @@ def test_execute_trade_exit_down(default_conf_usdt, ticker_usdt, fee, ticker_usd 'freqtrade.exchange.Exchange', fetch_ticker=ticker_usdt, get_fee=fee, - _is_dry_limit_order_filled=MagicMock(return_value=False), + _dry_is_price_crossed=MagicMock(return_value=False), ) patch_whitelist(mocker, default_conf_usdt) freqtrade = FreqtradeBot(default_conf_usdt) @@ -3407,7 +3409,7 @@ def test_execute_trade_exit_custom_exit_price( 'freqtrade.exchange.Exchange', fetch_ticker=ticker_usdt, get_fee=fee, - _is_dry_limit_order_filled=MagicMock(return_value=False), + _dry_is_price_crossed=MagicMock(return_value=False), ) config = deepcopy(default_conf_usdt) config['custom_price_max_distance_ratio'] = 0.1 @@ -3488,7 +3490,7 @@ def test_execute_trade_exit_down_stoploss_on_exchange_dry_run( 'freqtrade.exchange.Exchange', fetch_ticker=ticker_usdt, get_fee=fee, - _is_dry_limit_order_filled=MagicMock(return_value=False), + _dry_is_price_crossed=MagicMock(return_value=False), ) patch_whitelist(mocker, default_conf_usdt) freqtrade = FreqtradeBot(default_conf_usdt) @@ -3605,9 +3607,9 @@ def test_execute_trade_exit_with_stoploss_on_exchange( get_fee=fee, amount_to_precision=lambda s, x, y: y, price_to_precision=lambda s, x, y: y, - stoploss=stoploss, + create_stoploss=stoploss, cancel_stoploss_order=cancel_order, - _is_dry_limit_order_filled=MagicMock(side_effect=[True, False]), + _dry_is_price_crossed=MagicMock(side_effect=[True, False]), ) freqtrade = FreqtradeBot(default_conf_usdt) @@ -3656,7 +3658,7 @@ def test_may_execute_trade_exit_after_stoploss_on_exchange_hit( get_fee=fee, amount_to_precision=lambda s, x, y: y, price_to_precision=lambda s, x, y: y, - _is_dry_limit_order_filled=MagicMock(side_effect=[False, True]), + _dry_is_price_crossed=MagicMock(side_effect=[False, True]), ) stoploss = MagicMock(return_value={ @@ -3666,7 +3668,7 @@ def test_may_execute_trade_exit_after_stoploss_on_exchange_hit( } }) - mocker.patch('freqtrade.exchange.Binance.stoploss', stoploss) + mocker.patch('freqtrade.exchange.Binance.create_stoploss', stoploss) freqtrade = FreqtradeBot(default_conf_usdt) freqtrade.strategy.order_types['stoploss_on_exchange'] = True @@ -3751,7 +3753,7 @@ def test_execute_trade_exit_market_order( 'freqtrade.exchange.Exchange', fetch_ticker=ticker_usdt, get_fee=fee, - _is_dry_limit_order_filled=MagicMock(return_value=True), + _dry_is_price_crossed=MagicMock(return_value=True), get_funding_fees=MagicMock(side_effect=ExchangeError()), ) patch_whitelist(mocker, default_conf_usdt) @@ -3769,7 +3771,7 @@ def test_execute_trade_exit_market_order( mocker.patch.multiple( 'freqtrade.exchange.Exchange', fetch_ticker=ticker_usdt_sell_up, - _is_dry_limit_order_filled=MagicMock(return_value=False), + _dry_is_price_crossed=MagicMock(return_value=False), ) freqtrade.config['order_types']['exit'] = 'market' @@ -3900,7 +3902,7 @@ def test_exit_profit_only( if exit_type == ExitType.EXIT_SIGNAL.value: freqtrade.strategy.min_roi_reached = MagicMock(return_value=False) else: - freqtrade.strategy.stop_loss_reached = MagicMock(return_value=ExitCheckTuple( + freqtrade.strategy.ft_stoploss_reached = MagicMock(return_value=ExitCheckTuple( exit_type=ExitType.NONE)) freqtrade.enter_positions() @@ -4281,7 +4283,7 @@ def test_disable_ignore_roi_if_entry_signal(default_conf_usdt, limit_order, limi {'id': 1234553383} ]), get_fee=fee, - _is_dry_limit_order_filled=MagicMock(return_value=False), + _dry_is_price_crossed=MagicMock(return_value=False), ) default_conf_usdt['exit_pricing'] = { 'ignore_roi_if_entry_signal': False @@ -5026,7 +5028,7 @@ def test_startup_update_open_orders(mocker, default_conf_usdt, fee, caplog, is_s assert log_has_re(r"Error updating Order .*", caplog) mocker.patch('freqtrade.exchange.Exchange.fetch_order', side_effect=InvalidOrderException) - hto_mock = mocker.patch('freqtrade.freqtradebot.FreqtradeBot.handle_timedout_order') + hto_mock = mocker.patch('freqtrade.freqtradebot.FreqtradeBot.handle_cancel_order') # Orders which are no longer found after X days should be assumed as canceled. freqtrade.startup_update_open_orders() assert log_has_re(r"Order is older than \d days.*", caplog) diff --git a/tests/test_integration.py b/tests/test_integration.py index 01a2801ad..4d8b282c9 100644 --- a/tests/test_integration.py +++ b/tests/test_integration.py @@ -56,7 +56,7 @@ def test_may_execute_exit_stoploss_on_exchange_multi(default_conf, ticker, fee, [ExitCheckTuple(exit_type=ExitType.EXIT_SIGNAL)]] ) cancel_order_mock = MagicMock() - mocker.patch('freqtrade.exchange.Binance.stoploss', stoploss) + mocker.patch('freqtrade.exchange.Binance.create_stoploss', stoploss) mocker.patch.multiple( 'freqtrade.exchange.Exchange', fetch_ticker=ticker, @@ -367,7 +367,7 @@ def test_dca_order_adjust(default_conf_usdt, ticker_usdt, leverage, fee, mocker) amount_to_precision=lambda s, x, y: y, price_to_precision=lambda s, x, y: y, ) - mocker.patch('freqtrade.exchange.Exchange._is_dry_limit_order_filled', return_value=False) + mocker.patch('freqtrade.exchange.Exchange._dry_is_price_crossed', return_value=False) mocker.patch("freqtrade.exchange.Exchange.get_max_leverage", return_value=10) mocker.patch("freqtrade.exchange.Exchange.get_funding_fees", return_value=0) mocker.patch("freqtrade.exchange.Exchange.get_maintenance_ratio_and_amt", return_value=(0, 0)) @@ -413,7 +413,7 @@ def test_dca_order_adjust(default_conf_usdt, ticker_usdt, leverage, fee, mocker) assert trade.initial_stop_loss_pct is None # Fill order - mocker.patch('freqtrade.exchange.Exchange._is_dry_limit_order_filled', return_value=True) + mocker.patch('freqtrade.exchange.Exchange._dry_is_price_crossed', return_value=True) freqtrade.process() trade = Trade.get_trades().first() assert len(trade.orders) == 2 @@ -428,7 +428,7 @@ def test_dca_order_adjust(default_conf_usdt, ticker_usdt, leverage, fee, mocker) # 2nd order - not filling freqtrade.strategy.adjust_trade_position = MagicMock(return_value=120) - mocker.patch('freqtrade.exchange.Exchange._is_dry_limit_order_filled', return_value=False) + mocker.patch('freqtrade.exchange.Exchange._dry_is_price_crossed', return_value=False) freqtrade.process() trade = Trade.get_trades().first() @@ -452,7 +452,7 @@ def test_dca_order_adjust(default_conf_usdt, ticker_usdt, leverage, fee, mocker) # Fill DCA order freqtrade.strategy.adjust_trade_position = MagicMock(return_value=None) - mocker.patch('freqtrade.exchange.Exchange._is_dry_limit_order_filled', return_value=True) + mocker.patch('freqtrade.exchange.Exchange._dry_is_price_crossed', return_value=True) freqtrade.strategy.adjust_entry_price = MagicMock(side_effect=ValueError) freqtrade.process()