diff --git a/.github/dependabot.yml b/.github/dependabot.yml
index ddea42684..d6c20a6bc 100644
--- a/.github/dependabot.yml
+++ b/.github/dependabot.yml
@@ -1,9 +1,14 @@
version: 2
updates:
- package-ecosystem: docker
- directory: "/"
+ directories:
+ - "/"
+ - "/docker"
schedule:
interval: daily
+ ignore:
+ - dependency-name: "*"
+ update-types: ["version-update:semver-major"]
open-pull-requests-limit: 10
- package-ecosystem: pip
diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml
index 3e4ef2faa..db11ba833 100644
--- a/.github/workflows/ci.yml
+++ b/.github/workflows/ci.yml
@@ -55,7 +55,7 @@ jobs:
- name: Installation - *nix
run: |
- python -m pip install --upgrade "pip<=24.0" wheel
+ python -m pip install --upgrade pip wheel
export LD_LIBRARY_PATH=${HOME}/dependencies/lib:$LD_LIBRARY_PATH
export TA_LIBRARY_PATH=${HOME}/dependencies/lib
export TA_INCLUDE_PATH=${HOME}/dependencies/include
@@ -197,7 +197,7 @@ jobs:
- name: Installation (python)
run: |
- python -m pip install --upgrade "pip<=24.0" wheel
+ python -m pip install --upgrade pip wheel
export LD_LIBRARY_PATH=${HOME}/dependencies/lib:$LD_LIBRARY_PATH
export TA_LIBRARY_PATH=${HOME}/dependencies/lib
export TA_INCLUDE_PATH=${HOME}/dependencies/include
@@ -384,7 +384,6 @@ jobs:
- name: Documentation build
run: |
pip install -r docs/requirements-docs.txt
- pip install mkdocs
mkdocs build
- name: Discord notification
@@ -427,7 +426,7 @@ jobs:
- name: Installation - *nix
run: |
- python -m pip install --upgrade "pip<=24.0" wheel
+ python -m pip install --upgrade pip wheel
export LD_LIBRARY_PATH=${HOME}/dependencies/lib:$LD_LIBRARY_PATH
export TA_LIBRARY_PATH=${HOME}/dependencies/lib
export TA_INCLUDE_PATH=${HOME}/dependencies/include
diff --git a/.github/workflows/deploy-docs.yml b/.github/workflows/deploy-docs.yml
new file mode 100644
index 000000000..c97934a51
--- /dev/null
+++ b/.github/workflows/deploy-docs.yml
@@ -0,0 +1,55 @@
+name: Build Documentation
+
+on:
+ push:
+ branches:
+ - develop
+ release:
+ types: [published]
+
+
+# disable permissions for all of the available permissions
+permissions: {}
+
+
+jobs:
+ build-docs:
+ permissions:
+ contents: write # for mike to push
+ name: Deploy Docs through mike
+ runs-on: ubuntu-latest
+ steps:
+ - uses: actions/checkout@v4
+
+ - name: Set up Python
+ uses: actions/setup-python@v5
+ with:
+ python-version: '3.12'
+
+ - name: Install dependencies
+ run: |
+ python -m pip install --upgrade pip
+ pip install -r docs/requirements-docs.txt
+
+ - name: Fetch gh-pages branch
+ run: |
+ git fetch origin gh-pages --depth=1
+
+ - name: Configure Git user
+ run: |
+ git config --local user.email "github-actions[bot]@users.noreply.github.com"
+ git config --local user.name "github-actions[bot]"
+
+ - name: Build and push Mike
+ if: ${{ github.event_name == 'push' }}
+ run: |
+ mike deploy ${{ github.ref_name }} latest --push --update-aliases
+
+ - name: Build and push Mike - Release
+ if: ${{ github.event_name == 'release' }}
+ run: |
+ mike deploy ${{ github.ref_name }} stable --push --update-aliases
+
+ - name: Show mike versions
+ run: |
+ mike list
diff --git a/.gitignore b/.gitignore
index c818981ee..d371c9dd9 100644
--- a/.gitignore
+++ b/.gitignore
@@ -114,3 +114,5 @@ target/
!config_examples/config_full.example.json
!config_examples/config_kraken.example.json
!config_examples/config_freqai.example.json
+
+docker-compose-*.yml
diff --git a/.pre-commit-config.yaml b/.pre-commit-config.yaml
index c78811f30..f69ac5737 100644
--- a/.pre-commit-config.yaml
+++ b/.pre-commit-config.yaml
@@ -2,24 +2,24 @@
# See https://pre-commit.com/hooks.html for more hooks
repos:
- repo: https://github.com/pycqa/flake8
- rev: "7.1.0"
+ rev: "7.1.1"
hooks:
- id: flake8
additional_dependencies: [Flake8-pyproject]
# stages: [push]
- repo: https://github.com/pre-commit/mirrors-mypy
- rev: "v1.11.0"
+ rev: "v1.11.2"
hooks:
- id: mypy
exclude: build_helpers
additional_dependencies:
- - types-cachetools==5.4.0.20240717
+ - types-cachetools==5.5.0.20240820
- types-filelock==3.2.7
- types-requests==2.32.0.20240712
- types-tabulate==0.9.0.20240106
- - types-python-dateutil==2.9.0.20240316
- - SQLAlchemy==2.0.31
+ - types-python-dateutil==2.9.0.20240821
+ - SQLAlchemy==2.0.32
# stages: [push]
- repo: https://github.com/pycqa/isort
@@ -31,7 +31,7 @@ repos:
- repo: https://github.com/charliermarsh/ruff-pre-commit
# Ruff version.
- rev: 'v0.5.4'
+ rev: 'v0.6.2'
hooks:
- id: ruff
diff --git a/Dockerfile b/Dockerfile
index cedeafbe6..fbe1de165 100644
--- a/Dockerfile
+++ b/Dockerfile
@@ -1,4 +1,4 @@
-FROM python:3.12.4-slim-bookworm as base
+FROM python:3.12.5-slim-bookworm as base
# Setup env
ENV LANG C.UTF-8
@@ -25,7 +25,7 @@ FROM base as python-deps
RUN apt-get update \
&& apt-get -y install build-essential libssl-dev git libffi-dev libgfortran5 pkg-config cmake gcc \
&& apt-get clean \
- && pip install --upgrade "pip<=24.0" wheel
+ && pip install --upgrade pip wheel
# Install TA-lib
COPY build_helpers/* /tmp/
diff --git a/README.md b/README.md
index d7ab7c05c..137078214 100644
--- a/README.md
+++ b/README.md
@@ -86,41 +86,50 @@ For further (native) installation methods, please refer to the [Installation doc
```
usage: freqtrade [-h] [-V]
- {trade,create-userdir,new-config,new-strategy,download-data,convert-data,convert-trade-data,list-data,backtesting,edge,hyperopt,hyperopt-list,hyperopt-show,list-exchanges,list-hyperopts,list-markets,list-pairs,list-strategies,list-timeframes,show-trades,test-pairlist,install-ui,plot-dataframe,plot-profit,webserver}
+ {trade,create-userdir,new-config,show-config,new-strategy,download-data,convert-data,convert-trade-data,trades-to-ohlcv,list-data,backtesting,backtesting-show,backtesting-analysis,edge,hyperopt,hyperopt-list,hyperopt-show,list-exchanges,list-markets,list-pairs,list-strategies,list-freqaimodels,list-timeframes,show-trades,test-pairlist,convert-db,install-ui,plot-dataframe,plot-profit,webserver,strategy-updater,lookahead-analysis,recursive-analysis}
...
Free, open source crypto trading bot
positional arguments:
- {trade,create-userdir,new-config,new-strategy,download-data,convert-data,convert-trade-data,list-data,backtesting,edge,hyperopt,hyperopt-list,hyperopt-show,list-exchanges,list-hyperopts,list-markets,list-pairs,list-strategies,list-timeframes,show-trades,test-pairlist,install-ui,plot-dataframe,plot-profit,webserver}
+ {trade,create-userdir,new-config,show-config,new-strategy,download-data,convert-data,convert-trade-data,trades-to-ohlcv,list-data,backtesting,backtesting-show,backtesting-analysis,edge,hyperopt,hyperopt-list,hyperopt-show,list-exchanges,list-markets,list-pairs,list-strategies,list-freqaimodels,list-timeframes,show-trades,test-pairlist,convert-db,install-ui,plot-dataframe,plot-profit,webserver,strategy-updater,lookahead-analysis,recursive-analysis}
trade Trade module.
create-userdir Create user-data directory.
new-config Create new config
+ show-config Show resolved config
new-strategy Create new strategy
download-data Download backtesting data.
convert-data Convert candle (OHLCV) data from one format to
another.
convert-trade-data Convert trade data from one format to another.
+ trades-to-ohlcv Convert trade data to OHLCV data.
list-data List downloaded data.
backtesting Backtesting module.
+ backtesting-show Show past Backtest results
+ backtesting-analysis
+ Backtest Analysis module.
edge Edge module.
hyperopt Hyperopt module.
hyperopt-list List Hyperopt results
hyperopt-show Show details of Hyperopt results
list-exchanges Print available exchanges.
- list-hyperopts Print available hyperopt classes.
list-markets Print markets on exchange.
list-pairs Print pairs on exchange.
list-strategies Print available strategies.
+ list-freqaimodels Print available freqAI models.
list-timeframes Print available timeframes for the exchange.
show-trades Show trades.
test-pairlist Test your pairlist configuration.
+ convert-db Migrate database to different system
install-ui Install FreqUI
plot-dataframe Plot candles with indicators.
plot-profit Generate plot showing profits.
webserver Webserver module.
+ strategy-updater updates outdated strategy files to the current version
+ lookahead-analysis Check for potential look ahead bias.
+ recursive-analysis Check for potential recursive formula issue.
-optional arguments:
+options:
-h, --help show this help message and exit
-V, --version show program's version number and exit
diff --git a/build_helpers/install_windows.ps1 b/build_helpers/install_windows.ps1
index 5f0c643ac..4aa070992 100644
--- a/build_helpers/install_windows.ps1
+++ b/build_helpers/install_windows.ps1
@@ -1,6 +1,6 @@
# vendored Wheels compiled via https://github.com/xmatthias/ta-lib-python/tree/ta_bundled_040
-python -m pip install --upgrade "pip<=24.0" wheel
+python -m pip install --upgrade pip wheel
$pyv = python -c "import sys; print(f'{sys.version_info.major}.{sys.version_info.minor}')"
diff --git a/build_helpers/schema.json b/build_helpers/schema.json
index c0933d9f8..8438dc3a0 100644
--- a/build_helpers/schema.json
+++ b/build_helpers/schema.json
@@ -9,11 +9,6 @@
],
"minimum": -1
},
- "new_pairs_days": {
- "description": "Download data of new pairs for given number of days",
- "type": "integer",
- "default": 30
- },
"timeframe": {
"description": "The timeframe to use (e.g `1m`, `5m`, `15m`, `30m`, `1h` ...). \nUsually specified in the strategy and missing in the configuration.",
"type": "string"
@@ -562,6 +557,7 @@
"enum": [
"StaticPairList",
"VolumePairList",
+ "PercentChangePairList",
"ProducerPairList",
"RemotePairList",
"MarketCapPairList",
@@ -609,6 +605,10 @@
"type": "number",
"minimum": 0
},
+ "unlock_at": {
+ "description": "Time when trading will be unlocked regularly. Format: HH:MM",
+ "type": "string"
+ },
"trade_limit": {
"description": "Minimum number of trades required during lookback period.",
"type": "number",
@@ -1064,7 +1064,7 @@
"default": {},
"properties": {
"process_throttle_secs": {
- "description": "Throttle time in seconds for processing.",
+ "description": "Minimum loop duration for one bot iteration in seconds.",
"type": "integer"
},
"interval": {
@@ -1105,6 +1105,15 @@
"description": "Enable position adjustment. \nUsually specified in the strategy and missing in the configuration.",
"type": "boolean"
},
+ "new_pairs_days": {
+ "description": "Download data of new pairs for given number of days",
+ "type": "integer",
+ "default": 30
+ },
+ "download_trades": {
+ "description": "Download trades data by default (instead of ohlcv data).",
+ "type": "boolean"
+ },
"max_entry_position_adjustment": {
"description": "Maximum entry position adjustment allowed. \nUsually specified in the strategy and missing in the configuration.",
"type": [
@@ -1113,6 +1122,13 @@
],
"minimum": -1
},
+ "add_config_files": {
+ "description": "Additional configuration files to load.",
+ "type": "array",
+ "items": {
+ "type": "string"
+ }
+ },
"orderflow": {
"description": "Settings related to order flow.",
"type": "object",
@@ -1208,6 +1224,11 @@
},
"uniqueItems": true
},
+ "log_responses": {
+ "description": "Log responses from the exchange.Useful/required to debug issues with order processing.",
+ "type": "boolean",
+ "default": false
+ },
"unknown_fee_rate": {
"description": "Fee rate for unknown markets.",
"type": "number"
diff --git a/config_examples/config_binance.example.json b/config_examples/config_binance.example.json
index 3a2cea530..6c513f064 100644
--- a/config_examples/config_binance.example.json
+++ b/config_examples/config_binance.example.json
@@ -1,4 +1,5 @@
{
+ "$schema": "https://schema.freqtrade.io/schema.json",
"max_open_trades": 3,
"stake_currency": "USDT",
"stake_amount": 0.05,
diff --git a/config_examples/config_freqai.example.json b/config_examples/config_freqai.example.json
index 27bc4532c..6751002e4 100644
--- a/config_examples/config_freqai.example.json
+++ b/config_examples/config_freqai.example.json
@@ -1,4 +1,5 @@
{
+ "$schema": "https://schema.freqtrade.io/schema.json",
"trading_mode": "futures",
"margin_mode": "isolated",
"max_open_trades": 5,
diff --git a/config_examples/config_full.example.json b/config_examples/config_full.example.json
index cb2d4797e..04137ed80 100644
--- a/config_examples/config_full.example.json
+++ b/config_examples/config_full.example.json
@@ -1,4 +1,5 @@
{
+ "$schema": "https://schema.freqtrade.io/schema.json",
"max_open_trades": 3,
"stake_currency": "BTC",
"stake_amount": 0.05,
diff --git a/config_examples/config_kraken.example.json b/config_examples/config_kraken.example.json
index 420047627..72f5e6b5f 100644
--- a/config_examples/config_kraken.example.json
+++ b/config_examples/config_kraken.example.json
@@ -1,4 +1,5 @@
{
+ "$schema": "https://schema.freqtrade.io/schema.json",
"max_open_trades": 5,
"stake_currency": "EUR",
"stake_amount": 10,
diff --git a/docker/Dockerfile.armhf b/docker/Dockerfile.armhf
index fbd952111..ed3c5fbde 100644
--- a/docker/Dockerfile.armhf
+++ b/docker/Dockerfile.armhf
@@ -1,4 +1,4 @@
-FROM python:3.11.8-slim-bookworm as base
+FROM python:3.11.9-slim-bookworm as base
# Setup env
ENV LANG C.UTF-8
@@ -17,7 +17,7 @@ RUN mkdir /freqtrade \
&& chown ftuser:ftuser /freqtrade \
# Allow sudoers
&& echo "ftuser ALL=(ALL) NOPASSWD: /bin/chown" >> /etc/sudoers \
- && pip install --upgrade "pip<=24.0"
+ && pip install --upgrade pip
WORKDIR /freqtrade
diff --git a/docs/advanced-hyperopt.md b/docs/advanced-hyperopt.md
index eb8bf3f84..e276bed94 100644
--- a/docs/advanced-hyperopt.md
+++ b/docs/advanced-hyperopt.md
@@ -30,11 +30,17 @@ class SuperDuperHyperOptLoss(IHyperOptLoss):
"""
@staticmethod
- def hyperopt_loss_function(results: DataFrame, trade_count: int,
- min_date: datetime, max_date: datetime,
- config: Config, processed: Dict[str, DataFrame],
- backtest_stats: Dict[str, Any],
- *args, **kwargs) -> float:
+ def hyperopt_loss_function(
+ *,
+ results: DataFrame,
+ trade_count: int,
+ min_date: datetime,
+ max_date: datetime,
+ config: Config,
+ processed: Dict[str, DataFrame],
+ backtest_stats: Dict[str, Any],
+ **kwargs,
+ ) -> float:
"""
Objective function, returns smaller number for better results
This is the legacy algorithm (used until now in freqtrade).
diff --git a/docs/backtesting.md b/docs/backtesting.md
index 5adeae54b..2feba7ada 100644
--- a/docs/backtesting.md
+++ b/docs/backtesting.md
@@ -530,10 +530,10 @@ You can then load the trades to perform further analysis as shown in the [data a
Since backtesting lacks some detailed information about what happens within a candle, it needs to take a few assumptions:
- Exchange [trading limits](#trading-limits-in-backtesting) are respected
-- Entries happen at open-price
+- Entries happen at open-price unless a custom price logic has been specified
- All orders are filled at the requested price (no slippage) as long as the price is within the candle's high/low range
- Exit-signal exits happen at open-price of the consecutive candle
-- Exits don't free their trade slot for a new trade until the next candle
+- Exits free their trade slot for a new trade with a different pair
- Exit-signal is favored over Stoploss, because exit-signals are assumed to trigger on candle's open
- ROI
- Exits are compared to high - but the ROI value is used (e.g. ROI = 2%, high=5% - so the exit will be at 2%)
diff --git a/docs/bot-usage.md b/docs/bot-usage.md
index 7aeda0c42..8ebc82552 100644
--- a/docs/bot-usage.md
+++ b/docs/bot-usage.md
@@ -12,41 +12,50 @@ This page explains the different parameters of the bot and how to run it.
```
usage: freqtrade [-h] [-V]
- {trade,create-userdir,new-config,new-strategy,download-data,convert-data,convert-trade-data,list-data,backtesting,edge,hyperopt,hyperopt-list,hyperopt-show,list-exchanges,list-hyperopts,list-markets,list-pairs,list-strategies,list-timeframes,show-trades,test-pairlist,install-ui,plot-dataframe,plot-profit,webserver}
+ {trade,create-userdir,new-config,show-config,new-strategy,download-data,convert-data,convert-trade-data,trades-to-ohlcv,list-data,backtesting,backtesting-show,backtesting-analysis,edge,hyperopt,hyperopt-list,hyperopt-show,list-exchanges,list-markets,list-pairs,list-strategies,list-freqaimodels,list-timeframes,show-trades,test-pairlist,convert-db,install-ui,plot-dataframe,plot-profit,webserver,strategy-updater,lookahead-analysis,recursive-analysis}
...
Free, open source crypto trading bot
positional arguments:
- {trade,create-userdir,new-config,new-strategy,download-data,convert-data,convert-trade-data,list-data,backtesting,edge,hyperopt,hyperopt-list,hyperopt-show,list-exchanges,list-hyperopts,list-markets,list-pairs,list-strategies,list-timeframes,show-trades,test-pairlist,install-ui,plot-dataframe,plot-profit,webserver}
+ {trade,create-userdir,new-config,show-config,new-strategy,download-data,convert-data,convert-trade-data,trades-to-ohlcv,list-data,backtesting,backtesting-show,backtesting-analysis,edge,hyperopt,hyperopt-list,hyperopt-show,list-exchanges,list-markets,list-pairs,list-strategies,list-freqaimodels,list-timeframes,show-trades,test-pairlist,convert-db,install-ui,plot-dataframe,plot-profit,webserver,strategy-updater,lookahead-analysis,recursive-analysis}
trade Trade module.
create-userdir Create user-data directory.
new-config Create new config
+ show-config Show resolved config
new-strategy Create new strategy
download-data Download backtesting data.
convert-data Convert candle (OHLCV) data from one format to
another.
convert-trade-data Convert trade data from one format to another.
+ trades-to-ohlcv Convert trade data to OHLCV data.
list-data List downloaded data.
backtesting Backtesting module.
+ backtesting-show Show past Backtest results
+ backtesting-analysis
+ Backtest Analysis module.
edge Edge module.
hyperopt Hyperopt module.
hyperopt-list List Hyperopt results
hyperopt-show Show details of Hyperopt results
list-exchanges Print available exchanges.
- list-hyperopts Print available hyperopt classes.
list-markets Print markets on exchange.
list-pairs Print pairs on exchange.
list-strategies Print available strategies.
+ list-freqaimodels Print available freqAI models.
list-timeframes Print available timeframes for the exchange.
show-trades Show trades.
test-pairlist Test your pairlist configuration.
+ convert-db Migrate database to different system
install-ui Install FreqUI
plot-dataframe Plot candles with indicators.
plot-profit Generate plot showing profits.
webserver Webserver module.
+ strategy-updater updates outdated strategy files to the current version
+ lookahead-analysis Check for potential look ahead bias.
+ recursive-analysis Check for potential recursive formula issue.
-optional arguments:
+options:
-h, --help show this help message and exit
-V, --version show program's version number and exit
diff --git a/docs/configuration.md b/docs/configuration.md
index ec8134281..aed34762b 100644
--- a/docs/configuration.md
+++ b/docs/configuration.md
@@ -123,6 +123,19 @@ This is similar to using multiple `--config` parameters, but simpler in usage as
If multiple files are in the `add_config_files` section, then they will be assumed to be at identical levels, having the last occurrence override the earlier config (unless a parent already defined such a key).
+## Editor autocomplete and validation
+
+If you are using an editor that supports JSON schema, you can use the schema provided by Freqtrade to get autocompletion and validation of your configuration file by adding the following line to the top of your configuration file:
+
+``` json
+{
+ "$schema": "https://schema.freqtrade.io/schema.json",
+}
+```
+
+??? Note "Develop version"
+ The develop schema is available as `https://schema.freqtrade.io/schema_dev.json` - though we recommend to stick to the stable version for the best experience.
+
## Configuration parameters
The table below will list all configuration parameters available.
diff --git a/docs/data-download.md b/docs/data-download.md
index 2a51edb0b..59fa23b97 100644
--- a/docs/data-download.md
+++ b/docs/data-download.md
@@ -423,7 +423,8 @@ You can get a list of downloaded data using the `list-data` sub-command.
usage: freqtrade list-data [-h] [-v] [--logfile FILE] [-V] [-c PATH] [-d PATH]
[--userdir PATH] [--exchange EXCHANGE]
[--data-format-ohlcv {json,jsongz,hdf5,feather,parquet}]
- [-p PAIRS [PAIRS ...]]
+ [--data-format-trades {json,jsongz,hdf5,feather,parquet}]
+ [--trades] [-p PAIRS [PAIRS ...]]
[--trading-mode {spot,margin,futures}]
[--show-timerange]
@@ -433,6 +434,10 @@ options:
--data-format-ohlcv {json,jsongz,hdf5,feather,parquet}
Storage format for downloaded candle (OHLCV) data.
(default: `feather`).
+ --data-format-trades {json,jsongz,hdf5,feather,parquet}
+ Storage format for downloaded trades data. (default:
+ `feather`).
+ --trades Work on trades data instead of OHLCV data.
-p PAIRS [PAIRS ...], --pairs PAIRS [PAIRS ...]
Limit command to these pairs. Pairs are space-
separated.
@@ -465,13 +470,29 @@ Common arguments:
```bash
> freqtrade list-data --userdir ~/.freqtrade/user_data/
-Found 33 pair / timeframe combinations.
-pairs timeframe
----------- -----------------------------------------
-ADA/BTC 5m, 15m, 30m, 1h, 2h, 4h, 6h, 12h, 1d
-ADA/ETH 5m, 15m, 30m, 1h, 2h, 4h, 6h, 12h, 1d
-ETH/BTC 5m, 15m, 30m, 1h, 2h, 4h, 6h, 12h, 1d
-ETH/USDT 5m, 15m, 30m, 1h, 2h, 4h
+ Found 33 pair / timeframe combinations.
+┏━━━━━━━━━━━━━━━┳━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━┳━━━━━━┓
+┃ Pair ┃ Timeframe ┃ Type ┃
+┡━━━━━━━━━━━━━━━╇━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━╇━━━━━━┩
+│ ADA/BTC │ 5m, 15m, 30m, 1h, 2h, 4h, 6h, 12h, 1d │ spot │
+│ ADA/ETH │ 5m, 15m, 30m, 1h, 2h, 4h, 6h, 12h, 1d │ spot │
+│ ETH/BTC │ 5m, 15m, 30m, 1h, 2h, 4h, 6h, 12h, 1d │ spot │
+│ ETH/USDT │ 5m, 15m, 30m, 1h, 2h, 4h │ spot │
+└───────────────┴───────────────────────────────────────────┴──────┘
+
+```
+
+Show all trades data including from/to timerange
+
+``` bash
+> freqtrade list-data --show --trades
+ Found trades data for 1 pair.
+┏━━━━━━━━━┳━━━━━━┳━━━━━━━━━━━━━━━━━━━━━┳━━━━━━━━━━━━━━━━━━━━━┳━━━━━━━━┓
+┃ Pair ┃ Type ┃ From ┃ To ┃ Trades ┃
+┡━━━━━━━━━╇━━━━━━╇━━━━━━━━━━━━━━━━━━━━━╇━━━━━━━━━━━━━━━━━━━━━╇━━━━━━━━┩
+│ XRP/ETH │ spot │ 2019-10-11 00:00:11 │ 2019-10-13 11:19:28 │ 12477 │
+└─────────┴──────┴─────────────────────┴─────────────────────┴────────┘
+
```
## Trades (tick) data
diff --git a/docs/includes/pairlists.md b/docs/includes/pairlists.md
index fbf8f4be0..b3b69f996 100644
--- a/docs/includes/pairlists.md
+++ b/docs/includes/pairlists.md
@@ -2,11 +2,11 @@
Pairlist Handlers define the list of pairs (pairlist) that the bot should trade. They are configured in the `pairlists` section of the configuration settings.
-In your configuration, you can use Static Pairlist (defined by the [`StaticPairList`](#static-pair-list) Pairlist Handler) and Dynamic Pairlist (defined by the [`VolumePairList`](#volume-pair-list) Pairlist Handler).
+In your configuration, you can use Static Pairlist (defined by the [`StaticPairList`](#static-pair-list) Pairlist Handler) and Dynamic Pairlist (defined by the [`VolumePairList`](#volume-pair-list) and [`PercentChangePairList`](#percent-change-pair-list) Pairlist Handlers).
Additionally, [`AgeFilter`](#agefilter), [`PrecisionFilter`](#precisionfilter), [`PriceFilter`](#pricefilter), [`ShuffleFilter`](#shufflefilter), [`SpreadFilter`](#spreadfilter) and [`VolatilityFilter`](#volatilityfilter) act as Pairlist Filters, removing certain pairs and/or moving their positions in the pairlist.
-If multiple Pairlist Handlers are used, they are chained and a combination of all Pairlist Handlers forms the resulting pairlist the bot uses for trading and backtesting. Pairlist Handlers are executed in the sequence they are configured. You can define either `StaticPairList`, `VolumePairList`, `ProducerPairList`, `RemotePairList` or `MarketCapPairList` as the starting Pairlist Handler.
+If multiple Pairlist Handlers are used, they are chained and a combination of all Pairlist Handlers forms the resulting pairlist the bot uses for trading and backtesting. Pairlist Handlers are executed in the sequence they are configured. You can define either `StaticPairList`, `VolumePairList`, `ProducerPairList`, `RemotePairList`, `MarketCapPairList` or `PercentChangePairList` as the starting Pairlist Handler.
Inactive markets are always removed from the resulting pairlist. Explicitly blacklisted pairs (those in the `pair_blacklist` configuration setting) are also always removed from the resulting pairlist.
@@ -22,6 +22,7 @@ You may also use something like `.*DOWN/BTC` or `.*UP/BTC` to exclude leveraged
* [`StaticPairList`](#static-pair-list) (default, if not configured differently)
* [`VolumePairList`](#volume-pair-list)
+* [`PercentChangePairList`](#percent-change-pair-list)
* [`ProducerPairList`](#producerpairlist)
* [`RemotePairList`](#remotepairlist)
* [`MarketCapPairList`](#marketcappairlist)
@@ -152,6 +153,89 @@ More sophisticated approach can be used, by using `lookback_timeframe` for candl
!!! Note
`VolumePairList` does not support backtesting mode.
+#### Percent Change Pair List
+
+`PercentChangePairList` filters and sorts pairs based on the percentage change in their price over the last 24 hours or any defined timeframe as part of advanced options. This allows traders to focus on assets that have experienced significant price movements, either positive or negative.
+
+**Configuration Options**
+
+* `number_assets`: Specifies the number of top pairs to select based on the 24-hour percentage change.
+* `min_value`: Sets a minimum percentage change threshold. Pairs with a percentage change below this value will be filtered out.
+* `max_value`: Sets a maximum percentage change threshold. Pairs with a percentage change above this value will be filtered out.
+* `sort_direction`: Specifies the order in which pairs are sorted based on their percentage change. Accepts two values: `asc` for ascending order and `desc` for descending order.
+* `refresh_period`: Defines the interval (in seconds) at which the pairlist will be refreshed. The default is 1800 seconds (30 minutes).
+* `lookback_days`: Number of days to look back. When `lookback_days` is selected, the `lookback_timeframe` is defaulted to 1 day.
+* `lookback_timeframe`: Timeframe to use for the lookback period.
+* `lookback_period`: Number of periods to look back at.
+
+When PercentChangePairList is used after other Pairlist Handlers, it will operate on the outputs of those handlers. If it is the leading Pairlist Handler, it will select pairs from all available markets with the specified stake currency.
+
+`PercentChangePairList` uses ticker data from the exchange, provided via the ccxt library:
+The percentage change is calculated as the change in price over the last 24 hours.
+
+??? Note "Unsupported exchanges"
+ On some exchanges (like HTX), regular PercentChangePairList does not work as the api does not natively provide 24h percent change in price. This can be worked around by using candle data to calculate the percentage change. To roughly simulate 24h percent change, you can use the following configuration. Please note that these pairlists will only refresh once per day.
+ ```json
+ "pairlists": [
+ {
+ "method": "PercentChangePairList",
+ "number_assets": 20,
+ "min_value": 0,
+ "refresh_period": 86400,
+ "lookback_days": 1
+ }
+ ],
+ ```
+
+**Example Configuration to Read from Ticker**
+
+```json
+"pairlists": [
+ {
+ "method": "PercentChangePairList",
+ "number_assets": 15,
+ "min_value": -10,
+ "max_value": 50
+ }
+],
+```
+
+In this configuration:
+
+1. The top 15 pairs are selected based on the highest percentage change in price over the last 24 hours.
+2. Only pairs with a percentage change between -10% and 50% are considered.
+
+**Example Configuration to Read from Candles**
+
+```json
+"pairlists": [
+ {
+ "method": "PercentChangePairList",
+ "number_assets": 15,
+ "sort_key": "percentage",
+ "min_value": 0,
+ "refresh_period": 3600,
+ "lookback_timeframe": "1h",
+ "lookback_period": 72
+ }
+],
+```
+
+This example builds the percent change pairs based on a rolling period of 3 days of 1-hour candles by using `lookback_timeframe` for candle size and `lookback_period` which specifies the number of candles.
+
+The percent change in price is calculated using the following formula, which expresses the percentage difference between the current candle's close price and the previous candle's close price, as defined by the specified timeframe and lookback period:
+
+$$ Percent Change = (\frac{Current Close - Previous Close}{Previous Close}) * 100 $$
+
+!!! Warning "Range look back and refresh period"
+ When used in conjunction with `lookback_days` and `lookback_timeframe` the `refresh_period` can not be smaller than the candle size in seconds. As this will result in unnecessary requests to the exchanges API.
+
+!!! Warning "Performance implications when using lookback range"
+ If used in first position in combination with lookback, the computation of the range-based percent change can be time and resource consuming, as it downloads candles for all tradable pairs. Hence it's highly advised to use the standard approach with `PercentChangePairList` to narrow the pairlist down for further percent-change calculation.
+
+!!! Note "Backtesting"
+ `PercentChangePairList` does not support backtesting mode.
+
#### ProducerPairList
With `ProducerPairList`, you can reuse the pairlist from a [Producer](producer-consumer.md) without explicitly defining the pairlist on each consumer.
diff --git a/docs/includes/protections.md b/docs/includes/protections.md
index 12af081c0..a4cb9d3cc 100644
--- a/docs/includes/protections.md
+++ b/docs/includes/protections.md
@@ -36,6 +36,7 @@ All protection end times are rounded up to the next candle to avoid sudden, unex
| `lookback_period_candles` | Only trades that completed within the last `lookback_period_candles` candles will be considered. This setting may be ignored by some Protections.
**Datatype:** Positive integer (in candles).
| `lookback_period` | Only trades that completed after `current_time - lookback_period` will be considered.
Cannot be used together with `lookback_period_candles`.
This setting may be ignored by some Protections.
**Datatype:** Float (in minutes)
| `trade_limit` | Number of trades required at minimum (not used by all Protections).
**Datatype:** Positive integer
+| `unlock_at` | Time when trading will be unlocked regularly (not used by all Protections).
**Datatype:** string
**Input Format:** "HH:MM" (24-hours)
!!! Note "Durations"
Durations (`stop_duration*` and `lookback_period*` can be defined in either minutes or candles).
@@ -44,7 +45,7 @@ All protection end times are rounded up to the next candle to avoid sudden, unex
#### Stoploss Guard
`StoplossGuard` selects all trades within `lookback_period` in minutes (or in candles when using `lookback_period_candles`).
-If `trade_limit` or more trades resulted in stoploss, trading will stop for `stop_duration` in minutes (or in candles when using `stop_duration_candles`).
+If `trade_limit` or more trades resulted in stoploss, trading will stop for `stop_duration` in minutes (or in candles when using `stop_duration_candles`, or until the set time when using `unlock_at`).
This applies across all pairs, unless `only_per_pair` is set to true, which will then only look at one pair at a time.
@@ -97,7 +98,7 @@ def protections(self):
#### Low Profit Pairs
`LowProfitPairs` uses all trades for a pair within `lookback_period` in minutes (or in candles when using `lookback_period_candles`) to determine the overall profit ratio.
-If that ratio is below `required_profit`, that pair will be locked for `stop_duration` in minutes (or in candles when using `stop_duration_candles`).
+If that ratio is below `required_profit`, that pair will be locked for `stop_duration` in minutes (or in candles when using `stop_duration_candles`, or until the set time when using `unlock_at`).
For futures bots, setting `only_per_side` will make the bot only consider one side, and will then only lock this one side, allowing for example shorts to continue after a series of long losses.
@@ -120,7 +121,7 @@ def protections(self):
#### Cooldown Period
-`CooldownPeriod` locks a pair for `stop_duration` in minutes (or in candles when using `stop_duration_candles`) after selling, avoiding a re-entry for this pair for `stop_duration` minutes.
+`CooldownPeriod` locks a pair for `stop_duration` in minutes (or in candles when using `stop_duration_candles`, or until the set time when using `unlock_at`) after exiting, avoiding a re-entry for this pair for `stop_duration` minutes.
The below example will stop trading a pair for 2 candles after closing a trade, allowing this pair to "cool down".
diff --git a/docs/includes/strategy-imports.md b/docs/includes/strategy-imports.md
new file mode 100644
index 000000000..63cda329c
--- /dev/null
+++ b/docs/includes/strategy-imports.md
@@ -0,0 +1,45 @@
+## Imports necessary for a strategy
+
+When creating a strategy, you will need to import the necessary modules and classes. The following imports are required for a strategy:
+
+By default, we recommend the following imports as a base line for your strategy:
+This will cover all imports necessary for freqtrade functions to work.
+Obviously you can add more imports as needed for your strategy.
+
+``` python
+# flake8: noqa: F401
+# isort: skip_file
+# --- Do not remove these imports ---
+import numpy as np
+import pandas as pd
+from datetime import datetime, timedelta, timezone
+from pandas import DataFrame
+from typing import Dict, Optional, Union, Tuple
+
+from freqtrade.strategy import (
+ IStrategy,
+ Trade,
+ Order,
+ PairLocks,
+ informative, # @informative decorator
+ # Hyperopt Parameters
+ BooleanParameter,
+ CategoricalParameter,
+ DecimalParameter,
+ IntParameter,
+ RealParameter,
+ # timeframe helpers
+ timeframe_to_minutes,
+ timeframe_to_next_date,
+ timeframe_to_prev_date,
+ # Strategy helper functions
+ merge_informative_pair,
+ stoploss_from_absolute,
+ stoploss_from_open,
+)
+
+# --------------------------------
+# Add your lib to import here
+import talib.abstract as ta
+from technical import qtpylib
+```
diff --git a/docs/requirements-docs.txt b/docs/requirements-docs.txt
index 20beace35..91a2ad768 100644
--- a/docs/requirements-docs.txt
+++ b/docs/requirements-docs.txt
@@ -1,6 +1,7 @@
-markdown==3.6
+markdown==3.7
mkdocs==1.6.0
-mkdocs-material==9.5.29
+mkdocs-material==9.5.33
mdx_truly_sane_lists==1.3
-pymdown-extensions==10.8.1
+pymdown-extensions==10.9
jinja2==3.1.4
+mike==2.1.3
diff --git a/docs/strategy-callbacks.md b/docs/strategy-callbacks.md
index 74eef53c1..ce1b9907c 100644
--- a/docs/strategy-callbacks.md
+++ b/docs/strategy-callbacks.md
@@ -24,6 +24,8 @@ Currently available callbacks:
!!! Tip "Callback calling sequence"
You can find the callback calling sequence in [bot-basics](bot-basics.md#bot-execution-logic)
+--8<-- "includes/strategy-imports.md"
+
## Bot start
A simple callback which is called once when the strategy is loaded.
@@ -41,10 +43,10 @@ class AwesomeStrategy(IStrategy):
Called only once after bot instantiation.
:param **kwargs: Ensure to keep this here so updates to this won't break your strategy.
"""
- if self.config['runmode'].value in ('live', 'dry_run'):
+ if self.config["runmode"].value in ("live", "dry_run"):
# Assign this to the class by using self.*
# can then be used by populate_* methods
- self.custom_remote_data = requests.get('https://some_remote_source.example.com')
+ self.custom_remote_data = requests.get("https://some_remote_source.example.com")
```
@@ -57,6 +59,7 @@ seconds, unless configured differently) or once per candle in backtest/hyperopt
This can be used to perform calculations which are pair independent (apply to all pairs), loading of external data, etc.
``` python
+# Default imports
import requests
class AwesomeStrategy(IStrategy):
@@ -71,10 +74,10 @@ class AwesomeStrategy(IStrategy):
:param current_time: datetime object, containing the current datetime
:param **kwargs: Ensure to keep this here so updates to this won't break your strategy.
"""
- if self.config['runmode'].value in ('live', 'dry_run'):
+ if self.config["runmode"].value in ("live", "dry_run"):
# Assign this to the class by using self.*
# can then be used by populate_* methods
- self.remote_data = requests.get('https://some_remote_source.example.com')
+ self.remote_data = requests.get("https://some_remote_source.example.com")
```
@@ -83,6 +86,8 @@ class AwesomeStrategy(IStrategy):
Called before entering a trade, makes it possible to manage your position size when placing a new trade.
```python
+# Default imports
+
class AwesomeStrategy(IStrategy):
def custom_stake_amount(self, pair: str, current_time: datetime, current_rate: float,
proposed_stake: float, min_stake: Optional[float], max_stake: float,
@@ -92,13 +97,13 @@ class AwesomeStrategy(IStrategy):
dataframe, _ = self.dp.get_analyzed_dataframe(pair=pair, timeframe=self.timeframe)
current_candle = dataframe.iloc[-1].squeeze()
- if current_candle['fastk_rsi_1h'] > current_candle['fastd_rsi_1h']:
- if self.config['stake_amount'] == 'unlimited':
+ if current_candle["fastk_rsi_1h"] > current_candle["fastd_rsi_1h"]:
+ if self.config["stake_amount"] == "unlimited":
# Use entire available wallet during favorable conditions when in compounding mode.
return max_stake
else:
# Compound profits during favorable conditions instead of using a static stake.
- return self.wallets.get_total_stake_amount() / self.config['max_open_trades']
+ return self.wallets.get_total_stake_amount() / self.config["max_open_trades"]
# Use default stake amount.
return proposed_stake
@@ -129,25 +134,27 @@ Using `custom_exit()` signals in place of stoploss though *is not recommended*.
An example of how we can use different indicators depending on the current profit and also exit trades that were open longer than one day:
``` python
+# Default imports
+
class AwesomeStrategy(IStrategy):
- def custom_exit(self, pair: str, trade: 'Trade', current_time: 'datetime', current_rate: float,
+ def custom_exit(self, pair: str, trade: Trade, current_time: datetime, current_rate: float,
current_profit: float, **kwargs):
dataframe, _ = self.dp.get_analyzed_dataframe(pair, self.timeframe)
last_candle = dataframe.iloc[-1].squeeze()
# Above 20% profit, sell when rsi < 80
if current_profit > 0.2:
- if last_candle['rsi'] < 80:
- return 'rsi_below_80'
+ if last_candle["rsi"] < 80:
+ return "rsi_below_80"
# Between 2% and 10%, sell if EMA-long above EMA-short
if 0.02 < current_profit < 0.1:
- if last_candle['emalong'] > last_candle['emashort']:
- return 'ema_long_below_80'
+ if last_candle["emalong"] > last_candle["emashort"]:
+ return "ema_long_below_80"
# Sell any positions at a loss if they are held for more than one day.
if current_profit < 0.0 and (current_time - trade.open_date_utc).days >= 1:
- return 'unclog'
+ return "unclog"
```
See [Dataframe access](strategy-advanced.md#dataframe-access) for more information about dataframe use in strategy callbacks.
@@ -168,7 +175,6 @@ The absolute value of the return value is used (the sign is ignored), so returni
Returning `None` will be interpreted as "no desire to change", and is the only safe way to return when you'd like to not modify the stoploss.
`NaN` and `inf` values are considered invalid and will be ignored (identical to `None`).
-
Stoploss on exchange works similar to `trailing_stop`, and the stoploss on exchange is updated as configured in `stoploss_on_exchange_interval` ([More details about stoploss on exchange](stoploss.md#stop-loss-on-exchangefreqtrade)).
!!! Note "Use of dates"
@@ -196,9 +202,7 @@ Of course, many more things are possible, and all examples can be combined at wi
To simulate a regular trailing stoploss of 4% (trailing 4% behind the maximum reached price) you would use the following very simple method:
``` python
-# additional imports required
-from datetime import datetime
-from freqtrade.persistence import Trade
+# Default imports
class AwesomeStrategy(IStrategy):
@@ -206,7 +210,7 @@ class AwesomeStrategy(IStrategy):
use_custom_stoploss = True
- def custom_stoploss(self, pair: str, trade: 'Trade', current_time: datetime,
+ def custom_stoploss(self, pair: str, trade: Trade, current_time: datetime,
current_rate: float, current_profit: float, after_fill: bool,
**kwargs) -> Optional[float]:
"""
@@ -236,8 +240,7 @@ class AwesomeStrategy(IStrategy):
Use the initial stoploss for the first 60 minutes, after this change to 10% trailing stoploss, and after 2 hours (120 minutes) we use a 5% trailing stoploss.
``` python
-from datetime import datetime, timedelta
-from freqtrade.persistence import Trade
+# Default imports
class AwesomeStrategy(IStrategy):
@@ -245,7 +248,7 @@ class AwesomeStrategy(IStrategy):
use_custom_stoploss = True
- def custom_stoploss(self, pair: str, trade: 'Trade', current_time: datetime,
+ def custom_stoploss(self, pair: str, trade: Trade, current_time: datetime,
current_rate: float, current_profit: float, after_fill: bool,
**kwargs) -> Optional[float]:
@@ -263,8 +266,7 @@ Use the initial stoploss for the first 60 minutes, after this change to 10% trai
If an additional order fills, set stoploss to -10% below the new `open_rate` ([Averaged across all entries](#position-adjust-calculations)).
``` python
-from datetime import datetime, timedelta
-from freqtrade.persistence import Trade
+# Default imports
class AwesomeStrategy(IStrategy):
@@ -272,7 +274,7 @@ class AwesomeStrategy(IStrategy):
use_custom_stoploss = True
- def custom_stoploss(self, pair: str, trade: 'Trade', current_time: datetime,
+ def custom_stoploss(self, pair: str, trade: Trade, current_time: datetime,
current_rate: float, current_profit: float, after_fill: bool,
**kwargs) -> Optional[float]:
@@ -293,8 +295,7 @@ Use a different stoploss depending on the pair.
In this example, we'll trail the highest price with 10% trailing stoploss for `ETH/BTC` and `XRP/BTC`, with 5% trailing stoploss for `LTC/BTC` and with 15% for all other pairs.
``` python
-from datetime import datetime
-from freqtrade.persistence import Trade
+# Default imports
class AwesomeStrategy(IStrategy):
@@ -302,13 +303,13 @@ class AwesomeStrategy(IStrategy):
use_custom_stoploss = True
- def custom_stoploss(self, pair: str, trade: 'Trade', current_time: datetime,
+ def custom_stoploss(self, pair: str, trade: Trade, current_time: datetime,
current_rate: float, current_profit: float, after_fill: bool,
**kwargs) -> Optional[float]:
- if pair in ('ETH/BTC', 'XRP/BTC'):
+ if pair in ("ETH/BTC", "XRP/BTC"):
return -0.10
- elif pair in ('LTC/BTC'):
+ elif pair in ("LTC/BTC"):
return -0.05
return -0.15
```
@@ -320,8 +321,7 @@ Use the initial stoploss until the profit is above 4%, then use a trailing stopl
Please note that the stoploss can only increase, values lower than the current stoploss are ignored.
``` python
-from datetime import datetime, timedelta
-from freqtrade.persistence import Trade
+# Default imports
class AwesomeStrategy(IStrategy):
@@ -329,7 +329,7 @@ class AwesomeStrategy(IStrategy):
use_custom_stoploss = True
- def custom_stoploss(self, pair: str, trade: 'Trade', current_time: datetime,
+ def custom_stoploss(self, pair: str, trade: Trade, current_time: datetime,
current_rate: float, current_profit: float, after_fill: bool,
**kwargs) -> Optional[float]:
@@ -353,9 +353,7 @@ Instead of continuously trailing behind the current price, this example sets fix
* Once profit is > 40% - set stoploss to 25% above open price.
``` python
-from datetime import datetime
-from freqtrade.persistence import Trade
-from freqtrade.strategy import stoploss_from_open
+# Default imports
class AwesomeStrategy(IStrategy):
@@ -363,7 +361,7 @@ class AwesomeStrategy(IStrategy):
use_custom_stoploss = True
- def custom_stoploss(self, pair: str, trade: 'Trade', current_time: datetime,
+ def custom_stoploss(self, pair: str, trade: Trade, current_time: datetime,
current_rate: float, current_profit: float, after_fill: bool,
**kwargs) -> Optional[float]:
@@ -384,15 +382,17 @@ class AwesomeStrategy(IStrategy):
Absolute stoploss value may be derived from indicators stored in dataframe. Example uses parabolic SAR below the price as stoploss.
``` python
+# Default imports
+
class AwesomeStrategy(IStrategy):
def populate_indicators(self, dataframe: DataFrame, metadata: dict) -> DataFrame:
# <...>
- dataframe['sar'] = ta.SAR(dataframe)
+ dataframe["sar"] = ta.SAR(dataframe)
use_custom_stoploss = True
- def custom_stoploss(self, pair: str, trade: 'Trade', current_time: datetime,
+ def custom_stoploss(self, pair: str, trade: Trade, current_time: datetime,
current_rate: float, current_profit: float, after_fill: bool,
**kwargs) -> Optional[float]:
@@ -400,7 +400,7 @@ class AwesomeStrategy(IStrategy):
last_candle = dataframe.iloc[-1].squeeze()
# Use parabolic sar as absolute stoploss price
- stoploss_price = last_candle['sar']
+ stoploss_price = last_candle["sar"]
# Convert absolute price to percentage relative to current_rate
if stoploss_price < current_rate:
@@ -429,10 +429,7 @@ Stoploss values returned from `custom_stoploss()` must specify a percentage rela
``` python
-
- from datetime import datetime
- from freqtrade.persistence import Trade
- from freqtrade.strategy import IStrategy, stoploss_from_open
+ # Default imports
class AwesomeStrategy(IStrategy):
@@ -440,7 +437,7 @@ Stoploss values returned from `custom_stoploss()` must specify a percentage rela
use_custom_stoploss = True
- def custom_stoploss(self, pair: str, trade: 'Trade', current_time: datetime,
+ def custom_stoploss(self, pair: str, trade: Trade, current_time: datetime,
current_rate: float, current_profit: float, after_fill: bool,
**kwargs) -> Optional[float]:
@@ -469,38 +466,34 @@ The helper function `stoploss_from_absolute()` can be used to convert from an ab
??? Example "Returning a stoploss using absolute price from the custom stoploss function"
- If we want to trail a stop price at 2xATR below current price we can call `stoploss_from_absolute(current_rate + (side * candle['atr'] * 2), current_rate=current_rate, is_short=trade.is_short, leverage=trade.leverage)`.
+ If we want to trail a stop price at 2xATR below current price we can call `stoploss_from_absolute(current_rate + (side * candle["atr"] * 2), current_rate=current_rate, is_short=trade.is_short, leverage=trade.leverage)`.
For futures, we need to adjust the direction (up or down), as well as adjust for leverage, since the [`custom_stoploss`](strategy-callbacks.md#custom-stoploss) callback returns the ["risk for this trade"](stoploss.md#stoploss-and-leverage) - not the relative price movement.
``` python
-
- from datetime import datetime
- from freqtrade.persistence import Trade
- from freqtrade.strategy import IStrategy, stoploss_from_absolute, timeframe_to_prev_date
+ # Default imports
class AwesomeStrategy(IStrategy):
use_custom_stoploss = True
- def populate_indicators_1h(self, dataframe: DataFrame, metadata: dict) -> DataFrame:
- dataframe['atr'] = ta.ATR(dataframe, timeperiod=14)
+ def populate_indicators(self, dataframe: DataFrame, metadata: dict) -> DataFrame:
+ dataframe["atr"] = ta.ATR(dataframe, timeperiod=14)
return dataframe
- def custom_stoploss(self, pair: str, trade: 'Trade', current_time: datetime,
+ def custom_stoploss(self, pair: str, trade: Trade, current_time: datetime,
current_rate: float, current_profit: float, after_fill: bool,
**kwargs) -> Optional[float]:
dataframe, _ = self.dp.get_analyzed_dataframe(pair, self.timeframe)
trade_date = timeframe_to_prev_date(self.timeframe, trade.open_date_utc)
candle = dataframe.iloc[-1].squeeze()
side = 1 if trade.is_short else -1
- return stoploss_from_absolute(current_rate + (side * candle['atr'] * 2),
+ return stoploss_from_absolute(current_rate + (side * candle["atr"] * 2),
current_rate=current_rate,
is_short=trade.is_short,
leverage=trade.leverage)
```
-
---
## Custom order price rules
@@ -520,19 +513,18 @@ Each of these methods are called right before placing an order on the exchange.
### Custom order entry and exit price example
``` python
-from datetime import datetime, timedelta, timezone
-from freqtrade.persistence import Trade
+# Default imports
class AwesomeStrategy(IStrategy):
# ... populate_* methods
- def custom_entry_price(self, pair: str, trade: Optional['Trade'], current_time: datetime, proposed_rate: float,
+ def custom_entry_price(self, pair: str, trade: Optional[Trade], current_time: datetime, proposed_rate: float,
entry_tag: Optional[str], side: str, **kwargs) -> float:
dataframe, last_updated = self.dp.get_analyzed_dataframe(pair=pair,
timeframe=self.timeframe)
- new_entryprice = dataframe['bollinger_10_lowerband'].iat[-1]
+ new_entryprice = dataframe["bollinger_10_lowerband"].iat[-1]
return new_entryprice
@@ -542,7 +534,7 @@ class AwesomeStrategy(IStrategy):
dataframe, last_updated = self.dp.get_analyzed_dataframe(pair=pair,
timeframe=self.timeframe)
- new_exitprice = dataframe['bollinger_10_upperband'].iat[-1]
+ new_exitprice = dataframe["bollinger_10_upperband"].iat[-1]
return new_exitprice
@@ -579,8 +571,7 @@ It applies a tight timeout for higher priced assets, while allowing more time to
The function must return either `True` (cancel order) or `False` (keep order alive).
``` python
-from datetime import datetime, timedelta
-from freqtrade.persistence import Trade, Order
+ # Default imports
class AwesomeStrategy(IStrategy):
@@ -588,11 +579,11 @@ class AwesomeStrategy(IStrategy):
# Set unfilledtimeout to 25 hours, since the maximum timeout from below is 24 hours.
unfilledtimeout = {
- 'entry': 60 * 25,
- 'exit': 60 * 25
+ "entry": 60 * 25,
+ "exit": 60 * 25
}
- def check_entry_timeout(self, pair: str, trade: 'Trade', order: 'Order',
+ def check_entry_timeout(self, pair: str, trade: Trade, order: Order,
current_time: datetime, **kwargs) -> bool:
if trade.open_rate > 100 and trade.open_date_utc < current_time - timedelta(minutes=5):
return True
@@ -603,7 +594,7 @@ class AwesomeStrategy(IStrategy):
return False
- def check_exit_timeout(self, pair: str, trade: Trade, order: 'Order',
+ def check_exit_timeout(self, pair: str, trade: Trade, order: Order,
current_time: datetime, **kwargs) -> bool:
if trade.open_rate > 100 and trade.open_date_utc < current_time - timedelta(minutes=5):
return True
@@ -620,8 +611,7 @@ class AwesomeStrategy(IStrategy):
### Custom order timeout example (using additional data)
``` python
-from datetime import datetime
-from freqtrade.persistence import Trade, Order
+ # Default imports
class AwesomeStrategy(IStrategy):
@@ -629,24 +619,24 @@ class AwesomeStrategy(IStrategy):
# Set unfilledtimeout to 25 hours, since the maximum timeout from below is 24 hours.
unfilledtimeout = {
- 'entry': 60 * 25,
- 'exit': 60 * 25
+ "entry": 60 * 25,
+ "exit": 60 * 25
}
- def check_entry_timeout(self, pair: str, trade: 'Trade', order: 'Order',
+ def check_entry_timeout(self, pair: str, trade: Trade, order: Order,
current_time: datetime, **kwargs) -> bool:
ob = self.dp.orderbook(pair, 1)
- current_price = ob['bids'][0][0]
+ current_price = ob["bids"][0][0]
# Cancel buy order if price is more than 2% above the order.
if current_price > order.price * 1.02:
return True
return False
- def check_exit_timeout(self, pair: str, trade: 'Trade', order: 'Order',
+ def check_exit_timeout(self, pair: str, trade: Trade, order: Order,
current_time: datetime, **kwargs) -> bool:
ob = self.dp.orderbook(pair, 1)
- current_price = ob['asks'][0][0]
+ current_price = ob["asks"][0][0]
# Cancel sell order if price is more than 2% below the order.
if current_price < order.price * 0.98:
return True
@@ -665,6 +655,8 @@ This are the last methods that will be called before an order is placed.
`confirm_trade_entry()` can be used to abort a trade entry at the latest second (maybe because the price is not what we expect).
``` python
+# Default imports
+
class AwesomeStrategy(IStrategy):
# ... populate_* methods
@@ -689,7 +681,7 @@ class AwesomeStrategy(IStrategy):
:param time_in_force: Time in force. Defaults to GTC (Good-til-cancelled).
:param current_time: datetime object, containing the current datetime
:param entry_tag: Optional entry_tag (buy_tag) if provided with the buy signal.
- :param side: 'long' or 'short' - indicating the direction of the proposed trade
+ :param side: "long" or "short" - indicating the direction of the proposed trade
:param **kwargs: Ensure to keep this here so updates to this won't break your strategy.
:return bool: When True is returned, then the buy-order is placed on the exchange.
False aborts the process
@@ -711,8 +703,7 @@ The exit-reasons (if applicable) will be in the following sequence:
* `trailing_stop_loss`
``` python
-from freqtrade.persistence import Trade
-
+# Default imports
class AwesomeStrategy(IStrategy):
@@ -738,14 +729,14 @@ class AwesomeStrategy(IStrategy):
or current rate for market orders.
:param time_in_force: Time in force. Defaults to GTC (Good-til-cancelled).
:param exit_reason: Exit reason.
- Can be any of ['roi', 'stop_loss', 'stoploss_on_exchange', 'trailing_stop_loss',
- 'exit_signal', 'force_exit', 'emergency_exit']
+ Can be any of ["roi", "stop_loss", "stoploss_on_exchange", "trailing_stop_loss",
+ "exit_signal", "force_exit", "emergency_exit"]
:param current_time: datetime object, containing the current datetime
:param **kwargs: Ensure to keep this here so updates to this won't break your strategy.
:return bool: When True, then the exit-order is placed on the exchange.
False aborts the process
"""
- if exit_reason == 'force_exit' and trade.calc_profit_ratio(rate) < 0:
+ if exit_reason == "force_exit" and trade.calc_profit_ratio(rate) < 0:
# Reject force-sells with negative profit
# This is just a sample, please adjust to your needs
# (this does not necessarily make sense, assuming you know when you're force-selling)
@@ -771,7 +762,7 @@ This callback is **not** called when there is an open order (either buy or sell)
`adjust_trade_position()` is called very frequently for the duration of a trade, so you must keep your implementation as performant as possible.
Position adjustments will always be applied in the direction of the trade, so a positive value will always increase your position (negative values will decrease your position), no matter if it's a long or short trade.
-Adjustment orders can be assigned with a tag by returning a 2 element Tuple, with the first element being the adjustment amount, and the 2nd element the tag (e.g. `return 250, 'increase_favorable_conditions'`).
+Adjustment orders can be assigned with a tag by returning a 2 element Tuple, with the first element being the adjustment amount, and the 2nd element the tag (e.g. `return 250, "increase_favorable_conditions"`).
Modifications to leverage are not possible, and the stake-amount returned is assumed to be before applying leverage.
@@ -793,7 +784,7 @@ Returning a value more than the above (so remaining stake_amount would become ne
!!! Note "About stake size"
Using fixed stake size means it will be the amount used for the first order, just like without position adjustment.
If you wish to buy additional orders with DCA, then make sure to leave enough funds in the wallet for that.
- Using 'unlimited' stake amount with DCA orders requires you to also implement the `custom_stake_amount()` callback to avoid allocating all funds to the initial order.
+ Using `"unlimited"` stake amount with DCA orders requires you to also implement the `custom_stake_amount()` callback to avoid allocating all funds to the initial order.
!!! Warning "Stoploss calculation"
Stoploss is still calculated from the initial opening price, not averaged price.
@@ -811,9 +802,7 @@ Returning a value more than the above (so remaining stake_amount would become ne
Trades with long duration and 10s or even 100ds of position adjustments are therefore not recommended, and should be closed at regular intervals to not affect performance.
``` python
-from freqtrade.persistence import Trade
-from typing import Optional, Tuple, Union
-
+# Default imports
class DigDeeperStrategy(IStrategy):
@@ -876,7 +865,7 @@ class DigDeeperStrategy(IStrategy):
if current_profit > 0.05 and trade.nr_of_successful_exits == 0:
# Take half of the profit at +5%
- return -(trade.stake_amount / 2), 'half_profit_5%'
+ return -(trade.stake_amount / 2), "half_profit_5%"
if current_profit > -0.05:
return None
@@ -886,7 +875,7 @@ class DigDeeperStrategy(IStrategy):
# Only buy when not actively falling price.
last_candle = dataframe.iloc[-1].squeeze()
previous_candle = dataframe.iloc[-2].squeeze()
- if last_candle['close'] < previous_candle['close']:
+ if last_candle["close"] < previous_candle["close"]:
return None
filled_entries = trade.select_filled_orders(trade.entry_side)
@@ -904,7 +893,7 @@ class DigDeeperStrategy(IStrategy):
stake_amount = filled_entries[0].stake_amount
# This then calculates current safety order size
stake_amount = stake_amount * (1 + (count_of_entries * 0.25))
- return stake_amount, '1/3rd_increase'
+ return stake_amount, "1/3rd_increase"
except Exception as exception:
return None
@@ -951,8 +940,7 @@ If the cancellation of the original order fails, then the order will not be repl
Entry Orders that are cancelled via the above methods will not have this callback called. Be sure to update timeout values to match your expectations.
```python
-from freqtrade.persistence import Trade
-from datetime import timedelta, datetime
+# Default imports
class AwesomeStrategy(IStrategy):
@@ -977,13 +965,18 @@ class AwesomeStrategy(IStrategy):
:param proposed_rate: Rate, calculated based on pricing settings in entry_pricing.
:param current_order_rate: Rate of the existing order in place.
:param entry_tag: Optional entry_tag (buy_tag) if provided with the buy signal.
- :param side: 'long' or 'short' - indicating the direction of the proposed trade
+ :param side: "long" or "short" - indicating the direction of the proposed trade
:param **kwargs: Ensure to keep this here so updates to this won't break your strategy.
:return float: New entry price value if provided
"""
# Limit orders to use and follow SMA200 as price target for the first 10 minutes since entry trigger for BTC/USDT pair.
- if pair == 'BTC/USDT' and entry_tag == 'long_sma200' and side == 'long' and (current_time - timedelta(minutes=10)) > trade.open_date_utc:
+ if (
+ pair == "BTC/USDT"
+ and entry_tag == "long_sma200"
+ and side == "long"
+ and (current_time - timedelta(minutes=10)) > trade.open_date_utc
+ ):
# just cancel the order if it has been filled more than half of the amount
if order.filled > order.remaining:
return None
@@ -991,7 +984,7 @@ class AwesomeStrategy(IStrategy):
dataframe, _ = self.dp.get_analyzed_dataframe(pair=pair, timeframe=self.timeframe)
current_candle = dataframe.iloc[-1].squeeze()
# desired price
- return current_candle['sma_200']
+ return current_candle["sma_200"]
# default: maintain existing order
return current_order_rate
```
@@ -1006,6 +999,8 @@ Values that are above `max_leverage` will be adjusted to `max_leverage`.
For markets / exchanges that don't support leverage, this method is ignored.
``` python
+# Default imports
+
class AwesomeStrategy(IStrategy):
def leverage(self, pair: str, current_time: datetime, current_rate: float,
proposed_leverage: float, max_leverage: float, entry_tag: Optional[str], side: str,
@@ -1019,7 +1014,7 @@ class AwesomeStrategy(IStrategy):
:param proposed_leverage: A leverage proposed by the bot.
:param max_leverage: Max leverage allowed on this pair
:param entry_tag: Optional entry_tag (buy_tag) if provided with the buy signal.
- :param side: 'long' or 'short' - indicating the direction of the proposed trade
+ :param side: "long" or "short" - indicating the direction of the proposed trade
:return: A leverage amount, which is between 1.0 and max_leverage.
"""
return 1.0
@@ -1036,6 +1031,8 @@ It will be called independent of the order type (entry, exit, stoploss or positi
Assuming that your strategy needs to store the high value of the candle at trade entry, this is possible with this callback as the following example show.
``` python
+# Default imports
+
class AwesomeStrategy(IStrategy):
def order_filled(self, pair: str, trade: Trade, order: Order, current_time: datetime, **kwargs) -> None:
"""
@@ -1052,7 +1049,7 @@ class AwesomeStrategy(IStrategy):
last_candle = dataframe.iloc[-1].squeeze()
if (trade.nr_of_successful_entries == 1) and (order.ft_order_side == trade.entry_side):
- trade.set_custom_data(key='entry_candle_high', value=last_candle['high'])
+ trade.set_custom_data(key="entry_candle_high", value=last_candle["high"])
return None
diff --git a/docs/strategy-customization.md b/docs/strategy-customization.md
index 48f629df5..a8b9dcb4c 100644
--- a/docs/strategy-customization.md
+++ b/docs/strategy-customization.md
@@ -158,7 +158,7 @@ Out of the box, freqtrade installs the following technical libraries:
- [ta-lib](https://ta-lib.github.io/ta-lib-python/)
- [pandas-ta](https://twopirllc.github.io/pandas-ta/)
-- [technical](https://github.com/freqtrade/technical/)
+- [technical](https://technical.freqtrade.io)
Additional technical libraries can be installed as necessary, or custom indicators may be written / invented by the strategy author.
@@ -407,6 +407,8 @@ Currently this is `pair`, which can be accessed using `metadata['pair']` - and w
The Metadata-dict should not be modified and does not persist information across multiple calls.
Instead, have a look at the [Storing information](strategy-advanced.md#storing-information-persistent) section.
+--8<-- "includes/strategy-imports.md"
+
## Strategy file loading
By default, freqtrade will attempt to load strategies from all `.py` files within `user_data/strategies`.
diff --git a/docs/strategy_analysis_example.md b/docs/strategy_analysis_example.md
index 22828b899..9f37b2975 100644
--- a/docs/strategy_analysis_example.md
+++ b/docs/strategy_analysis_example.md
@@ -13,19 +13,22 @@ Please follow the [documentation](https://www.freqtrade.io/en/stable/data-downlo
import os
from pathlib import Path
+
# Change directory
# Modify this cell to insure that the output shows the correct path.
# Define all paths relative to the project root shown in the cell output
project_root = "somedir/freqtrade"
-i=0
+i = 0
try:
os.chdir(project_root)
- assert Path('LICENSE').is_file()
-except:
- while i<4 and (not Path('LICENSE').is_file()):
- os.chdir(Path(Path.cwd(), '../'))
- i+=1
- project_root = Path.cwd()
+ if not Path("LICENSE").is_file():
+ i = 0
+ while i < 4 and (not Path("LICENSE").is_file()):
+ os.chdir(Path(Path.cwd(), "../"))
+ i += 1
+ project_root = Path.cwd()
+except FileNotFoundError:
+ print("Please define the project root relative to the current directory")
print(Path.cwd())
```
@@ -35,6 +38,7 @@ print(Path.cwd())
```python
from freqtrade.configuration import Configuration
+
# Customize these according to your needs.
# Initialize empty configuration object
@@ -58,12 +62,14 @@ pair = "BTC/USDT"
from freqtrade.data.history import load_pair_history
from freqtrade.enums import CandleType
-candles = load_pair_history(datadir=data_location,
- timeframe=config["timeframe"],
- pair=pair,
- data_format = "json", # Make sure to update this to your data
- candle_type=CandleType.SPOT,
- )
+
+candles = load_pair_history(
+ datadir=data_location,
+ timeframe=config["timeframe"],
+ pair=pair,
+ data_format="json", # Make sure to update this to your data
+ candle_type=CandleType.SPOT,
+)
# Confirm success
print(f"Loaded {len(candles)} rows of data for {pair} from {data_location}")
@@ -76,14 +82,16 @@ candles.head()
```python
# Load strategy using values set above
-from freqtrade.resolvers import StrategyResolver
from freqtrade.data.dataprovider import DataProvider
+from freqtrade.resolvers import StrategyResolver
+
+
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})
+df = strategy.analyze_ticker(candles, {"pair": pair})
df.tail()
```
@@ -102,7 +110,7 @@ df.tail()
```python
# Report results
print(f"Generated {df['enter_long'].sum()} entry signals")
-data = df.set_index('date', drop=False)
+data = df.set_index("date", drop=False)
data.tail()
```
@@ -119,10 +127,13 @@ Analyze a trades dataframe (also used below for plotting)
```python
from freqtrade.data.btanalysis import load_backtest_data, load_backtest_stats
+
# if backtest_dir points to a directory, it'll automatically load the last backtest file.
backtest_dir = config["user_data_dir"] / "backtest_results"
-# backtest_dir can also point to a specific file
-# backtest_dir = config["user_data_dir"] / "backtest_results/backtest-result-2020-07-01_20-04-22.json"
+# backtest_dir can also point to a specific file
+# backtest_dir = (
+# config["user_data_dir"] / "backtest_results/backtest-result-2020-07-01_20-04-22.json"
+# )
```
@@ -131,24 +142,24 @@ backtest_dir = config["user_data_dir"] / "backtest_results"
# This contains all information used to generate the backtest result.
stats = load_backtest_stats(backtest_dir)
-strategy = 'SampleStrategy'
-# All statistics are available per strategy, so if `--strategy-list` was used during backtest, this will be reflected here as well.
+strategy = "SampleStrategy"
+# All statistics are available per strategy, so if `--strategy-list` was used during backtest,
+# this will be reflected here as well.
# Example usages:
-print(stats['strategy'][strategy]['results_per_pair'])
+print(stats["strategy"][strategy]["results_per_pair"])
# Get pairlist used for this backtest
-print(stats['strategy'][strategy]['pairlist'])
+print(stats["strategy"][strategy]["pairlist"])
# Get market change (average change of all pairs from start to end of the backtest period)
-print(stats['strategy'][strategy]['market_change'])
+print(stats["strategy"][strategy]["market_change"])
# Maximum drawdown ()
-print(stats['strategy'][strategy]['max_drawdown'])
+print(stats["strategy"][strategy]["max_drawdown"])
# Maximum drawdown start and end
-print(stats['strategy'][strategy]['drawdown_start'])
-print(stats['strategy'][strategy]['drawdown_end'])
+print(stats["strategy"][strategy]["drawdown_start"])
+print(stats["strategy"][strategy]["drawdown_end"])
# Get strategy comparison (only relevant if multiple strategies were compared)
-print(stats['strategy_comparison'])
-
+print(stats["strategy_comparison"])
```
@@ -166,24 +177,25 @@ trades.groupby("pair")["exit_reason"].value_counts()
```python
# Plotting equity line (starting with 0 on day 1 and adding daily profit for each backtested day)
+import pandas as pd
+import plotly.express as px
+
from freqtrade.configuration import Configuration
from freqtrade.data.btanalysis import load_backtest_stats
-import plotly.express as px
-import pandas as pd
+
# strategy = 'SampleStrategy'
# config = Configuration.from_files(["user_data/config.json"])
# backtest_dir = config["user_data_dir"] / "backtest_results"
stats = load_backtest_stats(backtest_dir)
-strategy_stats = stats['strategy'][strategy]
+strategy_stats = stats["strategy"][strategy]
-df = pd.DataFrame(columns=['dates','equity'], data=strategy_stats['daily_profit'])
-df['equity_daily'] = df['equity'].cumsum()
+df = pd.DataFrame(columns=["dates", "equity"], data=strategy_stats["daily_profit"])
+df["equity_daily"] = df["equity"].cumsum()
fig = px.line(df, x="dates", y="equity_daily")
fig.show()
-
```
### Load live trading results into a pandas dataframe
@@ -194,6 +206,7 @@ In case you did already some trading and want to analyze your performance
```python
from freqtrade.data.btanalysis import load_trades_from_db
+
# Fetch trades from database
trades = load_trades_from_db("sqlite:///tradesv3.sqlite")
@@ -210,8 +223,9 @@ This can be useful to find the best `max_open_trades` parameter, when used with
```python
from freqtrade.data.btanalysis import analyze_trade_parallelism
+
# Analyze the above
-parallel_trades = analyze_trade_parallelism(trades, '5m')
+parallel_trades = analyze_trade_parallelism(trades, "5m")
parallel_trades.plot()
```
@@ -222,23 +236,23 @@ Freqtrade offers interactive plotting capabilities based on plotly.
```python
-from freqtrade.plot.plotting import generate_candlestick_graph
+from freqtrade.plot.plotting import generate_candlestick_graph
+
+
# Limit graph period to keep plotly quick and reactive
# Filter trades to one pair
-trades_red = trades.loc[trades['pair'] == pair]
+trades_red = trades.loc[trades["pair"] == pair]
-data_red = data['2019-06-01':'2019-06-10']
+data_red = data["2019-06-01":"2019-06-10"]
# Generate candlestick graph
-graph = generate_candlestick_graph(pair=pair,
- data=data_red,
- trades=trades_red,
- indicators1=['sma20', 'ema50', 'ema55'],
- indicators2=['rsi', 'macd', 'macdsignal', 'macdhist']
- )
-
-
-
+graph = generate_candlestick_graph(
+ pair=pair,
+ data=data_red,
+ trades=trades_red,
+ indicators1=["sma20", "ema50", "ema55"],
+ indicators2=["rsi", "macd", "macdsignal", "macdhist"],
+)
```
@@ -248,7 +262,6 @@ graph = generate_candlestick_graph(pair=pair,
# Render graph in a separate window
graph.show(renderer="browser")
-
```
## Plot average profit per trade as distribution graph
@@ -257,12 +270,12 @@ graph.show(renderer="browser")
```python
import plotly.figure_factory as ff
+
hist_data = [trades.profit_ratio]
-group_labels = ['profit_ratio'] # name of the dataset
+group_labels = ["profit_ratio"] # name of the dataset
fig = ff.create_distplot(hist_data, group_labels, bin_size=0.01)
fig.show()
-
```
Feel free to submit an issue or Pull Request enhancing this document if you would like to share ideas on how to best analyze the data.
diff --git a/docs/stylesheets/ft.extra.css b/docs/stylesheets/ft.extra.css
index 3369fa177..18985baf0 100644
--- a/docs/stylesheets/ft.extra.css
+++ b/docs/stylesheets/ft.extra.css
@@ -11,3 +11,7 @@
.rst-versions .rst-other-versions {
color: white;
}
+
+.md-version__list {
+ font-weight: 500 !important;
+}
diff --git a/docs/utils.md b/docs/utils.md
index 78688a7f7..5be380b40 100644
--- a/docs/utils.md
+++ b/docs/utils.md
@@ -418,8 +418,9 @@ Common arguments:
```
-By default, only active pairs/markets are shown. Active pairs/markets are those that can currently be traded
-on the exchange. The see the list of all pairs/markets (not only the active ones), use the `-a`/`-all` option.
+By default, only active pairs/markets are shown. Active pairs/markets are those that can currently be traded on the exchange.
+You can use the `-a`/`-all` option to see the list of all pairs/markets, including the inactive ones.
+Pairs may be listed as untradeable if the smallest tradeable price for the market is very small, i.e. less than `1e-11` (`0.00000000001`)
Pairs/markets are sorted by its symbol string in the printed output.
diff --git a/freqtrade/__init__.py b/freqtrade/__init__.py
index 1ebfc7f93..b1cd1c9cb 100644
--- a/freqtrade/__init__.py
+++ b/freqtrade/__init__.py
@@ -1,6 +1,6 @@
"""Freqtrade bot"""
-__version__ = "2024.7.1"
+__version__ = "2024.8"
if "dev" in __version__:
from pathlib import Path
diff --git a/freqtrade/commands/__init__.py b/freqtrade/commands/__init__.py
index 48ee18e93..ec145eb5f 100644
--- a/freqtrade/commands/__init__.py
+++ b/freqtrade/commands/__init__.py
@@ -15,6 +15,7 @@ from freqtrade.commands.data_commands import (
start_convert_trades,
start_download_data,
start_list_data,
+ start_list_trades_data,
)
from freqtrade.commands.db_commands import start_convert_db
from freqtrade.commands.deploy_commands import (
diff --git a/freqtrade/commands/arguments.py b/freqtrade/commands/arguments.py
index 0c93af78a..62a79b0e8 100755
--- a/freqtrade/commands/arguments.py
+++ b/freqtrade/commands/arguments.py
@@ -132,7 +132,15 @@ ARGS_CONVERT_TRADES = [
"trading_mode",
]
-ARGS_LIST_DATA = ["exchange", "dataformat_ohlcv", "pairs", "trading_mode", "show_timerange"]
+ARGS_LIST_DATA = [
+ "exchange",
+ "dataformat_ohlcv",
+ "dataformat_trades",
+ "trades",
+ "pairs",
+ "trading_mode",
+ "show_timerange",
+]
ARGS_DOWNLOAD_DATA = [
"pairs",
diff --git a/freqtrade/commands/cli_options.py b/freqtrade/commands/cli_options.py
index b9236a0ab..54e139443 100755
--- a/freqtrade/commands/cli_options.py
+++ b/freqtrade/commands/cli_options.py
@@ -446,8 +446,12 @@ AVAILABLE_CLI_OPTIONS = {
),
"download_trades": Arg(
"--dl-trades",
- help="Download trades instead of OHLCV data. The bot will resample trades to the "
- "desired timeframe as specified as --timeframes/-t.",
+ help="Download trades instead of OHLCV data.",
+ action="store_true",
+ ),
+ "trades": Arg(
+ "--trades",
+ help="Work on trades data instead of OHLCV data.",
action="store_true",
),
"convert_trades": Arg(
diff --git a/freqtrade/commands/data_commands.py b/freqtrade/commands/data_commands.py
index f3f56c7b2..a114444b3 100644
--- a/freqtrade/commands/data_commands.py
+++ b/freqtrade/commands/data_commands.py
@@ -14,6 +14,7 @@ from freqtrade.data.history import download_data_main
from freqtrade.enums import CandleType, RunMode, TradingMode
from freqtrade.exceptions import ConfigurationError
from freqtrade.exchange import timeframe_to_minutes
+from freqtrade.misc import plural
from freqtrade.plugins.pairlist.pairlist_helpers import dynamic_expand_pairlist
from freqtrade.resolvers import ExchangeResolver
from freqtrade.util import print_rich_table
@@ -115,9 +116,13 @@ def start_convert_data(args: Dict[str, Any], ohlcv: bool = True) -> None:
def start_list_data(args: Dict[str, Any]) -> None:
"""
- List available backtest data
+ List available OHLCV data
"""
+ if args["trades"]:
+ start_list_trades_data(args)
+ return
+
config = setup_utils_configuration(args, RunMode.UTIL_NO_EXCHANGE)
from freqtrade.data.history import get_datahandler
@@ -127,7 +132,6 @@ def start_list_data(args: Dict[str, Any]) -> None:
paircombs = dhc.ohlcv_get_available_data(
config["datadir"], config.get("trading_mode", TradingMode.SPOT)
)
-
if args["pairs"]:
paircombs = [comb for comb in paircombs if comb[0] in args["pairs"]]
title = f"Found {len(paircombs)} pair / timeframe combinations."
@@ -171,3 +175,51 @@ def start_list_data(args: Dict[str, Any]) -> None:
summary=title,
table_kwargs={"min_width": 50},
)
+
+
+def start_list_trades_data(args: Dict[str, Any]) -> None:
+ """
+ List available Trades data
+ """
+
+ config = setup_utils_configuration(args, RunMode.UTIL_NO_EXCHANGE)
+
+ from freqtrade.data.history import get_datahandler
+
+ dhc = get_datahandler(config["datadir"], config["dataformat_trades"])
+
+ paircombs = dhc.trades_get_available_data(
+ config["datadir"], config.get("trading_mode", TradingMode.SPOT)
+ )
+
+ if args["pairs"]:
+ paircombs = [comb for comb in paircombs if comb in args["pairs"]]
+
+ title = f"Found trades data for {len(paircombs)} {plural(len(paircombs), 'pair')}."
+ if not config.get("show_timerange"):
+ print_rich_table(
+ [(pair, config.get("candle_type_def", CandleType.SPOT)) for pair in sorted(paircombs)],
+ ("Pair", "Type"),
+ title,
+ table_kwargs={"min_width": 50},
+ )
+ else:
+ paircombs1 = [
+ (pair, *dhc.trades_data_min_max(pair, config.get("trading_mode", TradingMode.SPOT)))
+ for pair in paircombs
+ ]
+ print_rich_table(
+ [
+ (
+ pair,
+ config.get("candle_type_def", CandleType.SPOT),
+ start.strftime(DATETIME_PRINT_FORMAT),
+ end.strftime(DATETIME_PRINT_FORMAT),
+ str(length),
+ )
+ for pair, start, end, length in sorted(paircombs1, key=lambda x: (x[0]))
+ ],
+ ("Pair", "Type", "From", "To", "Trades"),
+ summary=title,
+ table_kwargs={"min_width": 50},
+ )
diff --git a/freqtrade/commands/list_commands.py b/freqtrade/commands/list_commands.py
index 1696fc8f0..494ee87fa 100644
--- a/freqtrade/commands/list_commands.py
+++ b/freqtrade/commands/list_commands.py
@@ -32,7 +32,7 @@ def start_list_exchanges(args: Dict[str, Any]) -> None:
)
if args["print_one_column"]:
- print("\n".join([e["name"] for e in available_exchanges]))
+ print("\n".join([e["classname"] for e in available_exchanges]))
else:
if args["list_exchanges_all"]:
title = (
@@ -46,14 +46,20 @@ def start_list_exchanges(args: Dict[str, Any]) -> None:
table = Table(title=title)
table.add_column("Exchange Name")
+ table.add_column("Class Name")
table.add_column("Markets")
table.add_column("Reason")
for exchange in available_exchanges:
name = Text(exchange["name"])
if exchange["supported"]:
- name.append(" (Official)", style="italic")
+ name.append(" (Supported)", style="italic")
name.stylize("green bold")
+ classname = Text(exchange["classname"])
+ if exchange["is_alias"]:
+ name.stylize("strike")
+ classname.stylize("strike")
+ classname.append(f" (use {exchange['alias_for']})", style="italic")
trade_modes = Text(
", ".join(
@@ -68,6 +74,7 @@ def start_list_exchanges(args: Dict[str, Any]) -> None:
table.add_row(
name,
+ classname,
trade_modes,
exchange["comment"],
style=None if exchange["valid"] else "red",
diff --git a/freqtrade/configuration/__init__.py b/freqtrade/configuration/__init__.py
index aa06a70c9..8fe65a9b0 100644
--- a/freqtrade/configuration/__init__.py
+++ b/freqtrade/configuration/__init__.py
@@ -1,5 +1,6 @@
# flake8: noqa: F401
+from freqtrade.configuration.asyncio_config import asyncio_setup
from freqtrade.configuration.config_secrets import sanitize_config
from freqtrade.configuration.config_setup import setup_utils_configuration
from freqtrade.configuration.config_validation import validate_config_consistency
diff --git a/freqtrade/configuration/asyncio_config.py b/freqtrade/configuration/asyncio_config.py
new file mode 100644
index 000000000..c670cce79
--- /dev/null
+++ b/freqtrade/configuration/asyncio_config.py
@@ -0,0 +1,10 @@
+import sys
+
+
+def asyncio_setup() -> None: # pragma: no cover
+ # Set eventloop for win32 setups
+
+ if sys.platform == "win32":
+ import asyncio
+
+ asyncio.set_event_loop_policy(asyncio.WindowsSelectorEventLoopPolicy())
diff --git a/freqtrade/configuration/config_schema.py b/freqtrade/configuration/config_schema.py
index 7beed69e6..cd349daed 100644
--- a/freqtrade/configuration/config_schema.py
+++ b/freqtrade/configuration/config_schema.py
@@ -36,11 +36,6 @@ CONF_SCHEMA = {
"type": ["integer", "number"],
"minimum": -1,
},
- "new_pairs_days": {
- "description": "Download data of new pairs for given number of days",
- "type": "integer",
- "default": 30,
- },
"timeframe": {
"description": (
f"The timeframe to use (e.g `1m`, `5m`, `15m`, `30m`, `1h` ...). {__IN_STRATEGY}"
@@ -185,6 +180,7 @@ CONF_SCHEMA = {
"type": "boolean",
"default": False,
},
+ # Lookahead analysis section
"minimum_trade_amount": {
"description": "Minimum amount for a trade - only used for lookahead-analysis",
"type": "number",
@@ -480,6 +476,12 @@ CONF_SCHEMA = {
"type": "number",
"minimum": 0,
},
+ "unlock_at": {
+ "description": (
+ "Time when trading will be unlocked regularly. Format: HH:MM"
+ ),
+ "type": "string",
+ },
"trade_limit": {
"description": "Minimum number of trades required during lookback period.",
"type": "number",
@@ -501,6 +503,7 @@ CONF_SCHEMA = {
"required": ["method"],
},
},
+ # RPC section
"telegram": {
"description": "Telegram settings.",
"type": "object",
@@ -701,6 +704,7 @@ CONF_SCHEMA = {
},
"required": ["enabled", "listen_ip_address", "listen_port", "username", "password"],
},
+ # end of RPC section
"db_url": {
"description": "Database connection URL.",
"type": "string",
@@ -734,7 +738,7 @@ CONF_SCHEMA = {
"default": {},
"properties": {
"process_throttle_secs": {
- "description": "Throttle time in seconds for processing.",
+ "description": "Minimum loop duration for one bot iteration in seconds.",
"type": "integer",
},
"interval": {
@@ -763,11 +767,26 @@ CONF_SCHEMA = {
"description": f"Enable position adjustment. {__IN_STRATEGY}",
"type": "boolean",
},
+ # Download data section
+ "new_pairs_days": {
+ "description": "Download data of new pairs for given number of days",
+ "type": "integer",
+ "default": 30,
+ },
+ "download_trades": {
+ "description": "Download trades data by default (instead of ohlcv data).",
+ "type": "boolean",
+ },
"max_entry_position_adjustment": {
"description": f"Maximum entry position adjustment allowed. {__IN_STRATEGY}",
"type": ["integer", "number"],
"minimum": -1,
},
+ "add_config_files": {
+ "description": "Additional configuration files to load.",
+ "type": "array",
+ "items": {"type": "string"},
+ },
"orderflow": {
"description": "Settings related to order flow.",
"type": "object",
@@ -853,6 +872,14 @@ CONF_SCHEMA = {
"items": {"type": "string"},
"uniqueItems": True,
},
+ "log_responses": {
+ "description": (
+ "Log responses from the exchange."
+ "Useful/required to debug issues with order processing."
+ ),
+ "type": "boolean",
+ "default": False,
+ },
"unknown_fee_rate": {
"description": "Fee rate for unknown markets.",
"type": "number",
diff --git a/freqtrade/configuration/config_secrets.py b/freqtrade/configuration/config_secrets.py
index 427e09088..2bb0d97f8 100644
--- a/freqtrade/configuration/config_secrets.py
+++ b/freqtrade/configuration/config_secrets.py
@@ -14,12 +14,16 @@ def sanitize_config(config: Config, *, show_sensitive: bool = False) -> Config:
return config
keys_to_remove = [
"exchange.key",
+ "exchange.api_key",
"exchange.apiKey",
"exchange.secret",
"exchange.password",
"exchange.uid",
+ "exchange.account_id",
"exchange.accountId",
+ "exchange.wallet_address",
"exchange.walletAddress",
+ "exchange.private_key",
"exchange.privateKey",
"telegram.token",
"telegram.chat_id",
@@ -33,8 +37,10 @@ def sanitize_config(config: Config, *, show_sensitive: bool = False) -> Config:
nested_config = config
for nested_key in nested_keys[:-1]:
nested_config = nested_config.get(nested_key, {})
- nested_config[nested_keys[-1]] = "REDACTED"
+ if nested_keys[-1] in nested_config:
+ nested_config[nested_keys[-1]] = "REDACTED"
else:
- config[key] = "REDACTED"
+ if key in config:
+ config[key] = "REDACTED"
return config
diff --git a/freqtrade/configuration/config_validation.py b/freqtrade/configuration/config_validation.py
index f7f021b9c..6a14841ff 100644
--- a/freqtrade/configuration/config_validation.py
+++ b/freqtrade/configuration/config_validation.py
@@ -1,6 +1,7 @@
import logging
from collections import Counter
from copy import deepcopy
+from datetime import datetime
from typing import Any, Dict
from jsonschema import Draft4Validator, validators
@@ -201,16 +202,32 @@ def _validate_protections(conf: Dict[str, Any]) -> None:
"""
for prot in conf.get("protections", []):
+ parsed_unlock_at = None
+ if (config_unlock_at := prot.get("unlock_at")) is not None:
+ try:
+ parsed_unlock_at = datetime.strptime(config_unlock_at, "%H:%M")
+ except ValueError:
+ raise ConfigurationError(f"Invalid date format for unlock_at: {config_unlock_at}.")
+
if "stop_duration" in prot and "stop_duration_candles" in prot:
raise ConfigurationError(
"Protections must specify either `stop_duration` or `stop_duration_candles`.\n"
- f"Please fix the protection {prot.get('method')}"
+ f"Please fix the protection {prot.get('method')}."
)
if "lookback_period" in prot and "lookback_period_candles" in prot:
raise ConfigurationError(
"Protections must specify either `lookback_period` or `lookback_period_candles`.\n"
- f"Please fix the protection {prot.get('method')}"
+ f"Please fix the protection {prot.get('method')}."
+ )
+
+ if parsed_unlock_at is not None and (
+ "stop_duration" in prot or "stop_duration_candles" in prot
+ ):
+ raise ConfigurationError(
+ "Protections must specify either `unlock_at`, `stop_duration` or "
+ "`stop_duration_candles`.\n"
+ f"Please fix the protection {prot.get('method')}."
)
diff --git a/freqtrade/constants.py b/freqtrade/constants.py
index c8990a02a..120f463f3 100644
--- a/freqtrade/constants.py
+++ b/freqtrade/constants.py
@@ -42,6 +42,7 @@ HYPEROPT_LOSS_BUILTIN = [
AVAILABLE_PAIRLISTS = [
"StaticPairList",
"VolumePairList",
+ "PercentChangePairList",
"ProducerPairList",
"RemotePairList",
"MarketCapPairList",
diff --git a/freqtrade/data/btanalysis.py b/freqtrade/data/btanalysis.py
index 2895b4181..a237b10f1 100644
--- a/freqtrade/data/btanalysis.py
+++ b/freqtrade/data/btanalysis.py
@@ -401,7 +401,15 @@ def analyze_trade_parallelism(results: pd.DataFrame, timeframe: str) -> pd.DataF
timeframe_freq = timeframe_to_resample_freq(timeframe)
dates = [
- pd.Series(pd.date_range(row[1]["open_date"], row[1]["close_date"], freq=timeframe_freq))
+ pd.Series(
+ pd.date_range(
+ row[1]["open_date"],
+ row[1]["close_date"],
+ freq=timeframe_freq,
+ # Exclude right boundary - the date is the candle open date.
+ inclusive="left",
+ )
+ )
for row in results[["open_date", "close_date"]].iterrows()
]
deltas = [len(x) for x in dates]
diff --git a/freqtrade/data/converter/orderflow.py b/freqtrade/data/converter/orderflow.py
index 11d17e52f..ca19a2622 100644
--- a/freqtrade/data/converter/orderflow.py
+++ b/freqtrade/data/converter/orderflow.py
@@ -78,6 +78,8 @@ def populate_dataframe_with_trades(
# create columns for trades
_init_dataframe_with_trades_columns(dataframe)
+ if trades is None or trades.empty:
+ return dataframe, cached_grouped_trades
try:
start_time = time.time()
@@ -88,7 +90,7 @@ def populate_dataframe_with_trades(
max_candles = config_orderflow["max_candles"]
start_date = dataframe.tail(max_candles).date.iat[0]
# slice of trades that are before current ohlcv candles to make groupby faster
- trades = trades.loc[trades.candle_start >= start_date]
+ trades = trades.loc[trades["candle_start"] >= start_date]
trades.reset_index(inplace=True, drop=True)
# group trades by candle start
diff --git a/freqtrade/data/dataprovider.py b/freqtrade/data/dataprovider.py
index 809dc5c02..6db9831b3 100644
--- a/freqtrade/data/dataprovider.py
+++ b/freqtrade/data/dataprovider.py
@@ -521,15 +521,12 @@ class DataProvider:
(pair, timeframe or self._config["timeframe"], _candle_type), copy=copy
)
elif self.runmode in (RunMode.BACKTEST, RunMode.HYPEROPT):
- _candle_type = (
- CandleType.from_string(candle_type)
- if candle_type != ""
- else self._config["candle_type_def"]
- )
data_handler = get_datahandler(
self._config["datadir"], data_format=self._config["dataformat_trades"]
)
- trades_df = data_handler.trades_load(pair, TradingMode.FUTURES)
+ trades_df = data_handler.trades_load(
+ pair, self._config.get("trading_mode", TradingMode.SPOT)
+ )
return trades_df
else:
diff --git a/freqtrade/data/history/datahandlers/idatahandler.py b/freqtrade/data/history/datahandlers/idatahandler.py
index e335ea770..db1660dc8 100644
--- a/freqtrade/data/history/datahandlers/idatahandler.py
+++ b/freqtrade/data/history/datahandlers/idatahandler.py
@@ -12,7 +12,7 @@ from datetime import datetime, timezone
from pathlib import Path
from typing import List, Optional, Tuple, Type
-from pandas import DataFrame
+from pandas import DataFrame, to_datetime
from freqtrade import misc
from freqtrade.configuration import TimeRange
@@ -32,6 +32,7 @@ logger = logging.getLogger(__name__)
class IDataHandler(ABC):
_OHLCV_REGEX = r"^([a-zA-Z_\d-]+)\-(\d+[a-zA-Z]{1,2})\-?([a-zA-Z_]*)?(?=\.)"
+ _TRADES_REGEX = r"^([a-zA-Z_\d-]+)\-(trades)?(?=\.)"
def __init__(self, datadir: Path) -> None:
self._datadir = datadir
@@ -166,6 +167,50 @@ class IDataHandler(ABC):
:param candle_type: Any of the enum CandleType (must match trading mode!)
"""
+ @classmethod
+ def trades_get_available_data(cls, datadir: Path, trading_mode: TradingMode) -> List[str]:
+ """
+ Returns a list of all pairs with ohlcv data available in this datadir
+ :param datadir: Directory to search for ohlcv files
+ :param trading_mode: trading-mode to be used
+ :return: List of Tuples of (pair, timeframe, CandleType)
+ """
+ if trading_mode == TradingMode.FUTURES:
+ datadir = datadir.joinpath("futures")
+ _tmp = [
+ re.search(cls._TRADES_REGEX, p.name)
+ for p in datadir.glob(f"*.{cls._get_file_extension()}")
+ ]
+ return [
+ cls.rebuild_pair_from_filename(match[1])
+ for match in _tmp
+ if match and len(match.groups()) > 1
+ ]
+
+ def trades_data_min_max(
+ self,
+ pair: str,
+ trading_mode: TradingMode,
+ ) -> Tuple[datetime, datetime, int]:
+ """
+ Returns the min and max timestamp for the given pair's trades data.
+ :param pair: Pair to get min/max for
+ :param trading_mode: Trading mode to use (used to determine the filename)
+ :return: (min, max, len)
+ """
+ df = self._trades_load(pair, trading_mode)
+ if df.empty:
+ return (
+ datetime.fromtimestamp(0, tz=timezone.utc),
+ datetime.fromtimestamp(0, tz=timezone.utc),
+ 0,
+ )
+ return (
+ to_datetime(df.iloc[0]["timestamp"], unit="ms", utc=True).to_pydatetime(),
+ to_datetime(df.iloc[-1]["timestamp"], unit="ms", utc=True).to_pydatetime(),
+ len(df),
+ )
+
@classmethod
def trades_get_pairs(cls, datadir: Path) -> List[str]:
"""
@@ -247,9 +292,13 @@ class IDataHandler(ABC):
:param timerange: Timerange to load trades for - currently not implemented
:return: List of trades
"""
- trades = trades_df_remove_duplicates(
- self._trades_load(pair, trading_mode, timerange=timerange)
- )
+ try:
+ trades = self._trades_load(pair, trading_mode, timerange=timerange)
+ except Exception:
+ logger.exception(f"Error loading trades for {pair}")
+ return DataFrame(columns=DEFAULT_TRADES_COLUMNS)
+
+ trades = trades_df_remove_duplicates(trades)
trades = trades_convert_types(trades)
return trades
diff --git a/freqtrade/exchange/__init__.py b/freqtrade/exchange/__init__.py
index fc92ed5a9..d52f94293 100644
--- a/freqtrade/exchange/__init__.py
+++ b/freqtrade/exchange/__init__.py
@@ -39,6 +39,7 @@ from freqtrade.exchange.exchange_utils_timeframe import (
from freqtrade.exchange.gate import Gate
from freqtrade.exchange.hitbtc import Hitbtc
from freqtrade.exchange.htx import Htx
+from freqtrade.exchange.hyperliquid import Hyperliquid
from freqtrade.exchange.idex import Idex
from freqtrade.exchange.kraken import Kraken
from freqtrade.exchange.kucoin import Kucoin
diff --git a/freqtrade/exchange/binance_leverage_tiers.json b/freqtrade/exchange/binance_leverage_tiers.json
index 91775fede..303b66eb8 100644
--- a/freqtrade/exchange/binance_leverage_tiers.json
+++ b/freqtrade/exchange/binance_leverage_tiers.json
@@ -638,128 +638,144 @@
"tier": 1.0,
"currency": "USDT",
"minNotional": 0.0,
- "maxNotional": 5000.0,
- "maintenanceMarginRate": 0.015,
- "maxLeverage": 50.0,
+ "maxNotional": 20000.0,
+ "maintenanceMarginRate": 0.0065,
+ "maxLeverage": 75.0,
"info": {
"bracket": "1",
- "initialLeverage": "50",
- "notionalCap": "5000",
+ "initialLeverage": "75",
+ "notionalCap": "20000",
"notionalFloor": "0",
- "maintMarginRatio": "0.015",
+ "maintMarginRatio": "0.0065",
"cum": "0.0"
}
},
{
"tier": 2.0,
"currency": "USDT",
- "minNotional": 5000.0,
- "maxNotional": 50000.0,
- "maintenanceMarginRate": 0.02,
- "maxLeverage": 25.0,
+ "minNotional": 20000.0,
+ "maxNotional": 200000.0,
+ "maintenanceMarginRate": 0.01,
+ "maxLeverage": 50.0,
"info": {
"bracket": "2",
- "initialLeverage": "25",
- "notionalCap": "50000",
- "notionalFloor": "5000",
- "maintMarginRatio": "0.02",
- "cum": "25.0"
+ "initialLeverage": "50",
+ "notionalCap": "200000",
+ "notionalFloor": "20000",
+ "maintMarginRatio": "0.01",
+ "cum": "70.0"
}
},
{
"tier": 3.0,
"currency": "USDT",
- "minNotional": 50000.0,
- "maxNotional": 200000.0,
- "maintenanceMarginRate": 0.025,
- "maxLeverage": 20.0,
+ "minNotional": 200000.0,
+ "maxNotional": 1000000.0,
+ "maintenanceMarginRate": 0.02,
+ "maxLeverage": 25.0,
"info": {
"bracket": "3",
- "initialLeverage": "20",
- "notionalCap": "200000",
- "notionalFloor": "50000",
- "maintMarginRatio": "0.025",
- "cum": "275.0"
+ "initialLeverage": "25",
+ "notionalCap": "1000000",
+ "notionalFloor": "200000",
+ "maintMarginRatio": "0.02",
+ "cum": "2070.0"
}
},
{
"tier": 4.0,
"currency": "USDT",
- "minNotional": 200000.0,
+ "minNotional": 1000000.0,
"maxNotional": 2000000.0,
- "maintenanceMarginRate": 0.05,
- "maxLeverage": 10.0,
+ "maintenanceMarginRate": 0.025,
+ "maxLeverage": 20.0,
"info": {
"bracket": "4",
- "initialLeverage": "10",
+ "initialLeverage": "20",
"notionalCap": "2000000",
- "notionalFloor": "200000",
- "maintMarginRatio": "0.05",
- "cum": "5275.0"
+ "notionalFloor": "1000000",
+ "maintMarginRatio": "0.025",
+ "cum": "7070.0"
}
},
{
"tier": 5.0,
"currency": "USDT",
"minNotional": 2000000.0,
- "maxNotional": 4000000.0,
- "maintenanceMarginRate": 0.1,
- "maxLeverage": 5.0,
+ "maxNotional": 10000000.0,
+ "maintenanceMarginRate": 0.05,
+ "maxLeverage": 10.0,
"info": {
"bracket": "5",
- "initialLeverage": "5",
- "notionalCap": "4000000",
+ "initialLeverage": "10",
+ "notionalCap": "10000000",
"notionalFloor": "2000000",
- "maintMarginRatio": "0.1",
- "cum": "105275.0"
+ "maintMarginRatio": "0.05",
+ "cum": "57070.0"
}
},
{
"tier": 6.0,
"currency": "USDT",
- "minNotional": 4000000.0,
- "maxNotional": 5000000.0,
- "maintenanceMarginRate": 0.125,
- "maxLeverage": 4.0,
+ "minNotional": 10000000.0,
+ "maxNotional": 20000000.0,
+ "maintenanceMarginRate": 0.1,
+ "maxLeverage": 5.0,
"info": {
"bracket": "6",
- "initialLeverage": "4",
- "notionalCap": "5000000",
- "notionalFloor": "4000000",
- "maintMarginRatio": "0.125",
- "cum": "205275.0"
+ "initialLeverage": "5",
+ "notionalCap": "20000000",
+ "notionalFloor": "10000000",
+ "maintMarginRatio": "0.1",
+ "cum": "557070.0"
}
},
{
"tier": 7.0,
"currency": "USDT",
- "minNotional": 5000000.0,
- "maxNotional": 10000000.0,
- "maintenanceMarginRate": 0.25,
- "maxLeverage": 2.0,
+ "minNotional": 20000000.0,
+ "maxNotional": 25000000.0,
+ "maintenanceMarginRate": 0.125,
+ "maxLeverage": 4.0,
"info": {
"bracket": "7",
- "initialLeverage": "2",
- "notionalCap": "10000000",
- "notionalFloor": "5000000",
- "maintMarginRatio": "0.25",
- "cum": "830275.0"
+ "initialLeverage": "4",
+ "notionalCap": "25000000",
+ "notionalFloor": "20000000",
+ "maintMarginRatio": "0.125",
+ "cum": "1057070.0"
}
},
{
"tier": 8.0,
"currency": "USDT",
- "minNotional": 10000000.0,
- "maxNotional": 20000000.0,
+ "minNotional": 25000000.0,
+ "maxNotional": 50000000.0,
+ "maintenanceMarginRate": 0.25,
+ "maxLeverage": 2.0,
+ "info": {
+ "bracket": "8",
+ "initialLeverage": "2",
+ "notionalCap": "50000000",
+ "notionalFloor": "25000000",
+ "maintMarginRatio": "0.25",
+ "cum": "4182070.0"
+ }
+ },
+ {
+ "tier": 9.0,
+ "currency": "USDT",
+ "minNotional": 50000000.0,
+ "maxNotional": 100000000.0,
"maintenanceMarginRate": 0.5,
"maxLeverage": 1.0,
"info": {
- "bracket": "8",
+ "bracket": "9",
"initialLeverage": "1",
- "notionalCap": "20000000",
- "notionalFloor": "10000000",
+ "notionalCap": "100000000",
+ "notionalFloor": "50000000",
"maintMarginRatio": "0.5",
- "cum": "3330275.0"
+ "cum": "16682070.0"
}
}
],
@@ -898,128 +914,144 @@
"tier": 1.0,
"currency": "USDT",
"minNotional": 0.0,
- "maxNotional": 5000.0,
- "maintenanceMarginRate": 0.015,
- "maxLeverage": 50.0,
+ "maxNotional": 10000.0,
+ "maintenanceMarginRate": 0.01,
+ "maxLeverage": 75.0,
"info": {
"bracket": "1",
- "initialLeverage": "50",
- "notionalCap": "5000",
+ "initialLeverage": "75",
+ "notionalCap": "10000",
"notionalFloor": "0",
- "maintMarginRatio": "0.015",
+ "maintMarginRatio": "0.01",
"cum": "0.0"
}
},
{
"tier": 2.0,
"currency": "USDT",
- "minNotional": 5000.0,
- "maxNotional": 25000.0,
- "maintenanceMarginRate": 0.02,
- "maxLeverage": 25.0,
+ "minNotional": 10000.0,
+ "maxNotional": 60000.0,
+ "maintenanceMarginRate": 0.015,
+ "maxLeverage": 50.0,
"info": {
"bracket": "2",
- "initialLeverage": "25",
- "notionalCap": "25000",
- "notionalFloor": "5000",
- "maintMarginRatio": "0.02",
- "cum": "25.0"
+ "initialLeverage": "50",
+ "notionalCap": "60000",
+ "notionalFloor": "10000",
+ "maintMarginRatio": "0.015",
+ "cum": "50.0"
}
},
{
"tier": 3.0,
"currency": "USDT",
- "minNotional": 25000.0,
- "maxNotional": 80000.0,
- "maintenanceMarginRate": 0.025,
- "maxLeverage": 20.0,
+ "minNotional": 60000.0,
+ "maxNotional": 300000.0,
+ "maintenanceMarginRate": 0.02,
+ "maxLeverage": 25.0,
"info": {
"bracket": "3",
- "initialLeverage": "20",
- "notionalCap": "80000",
- "notionalFloor": "25000",
- "maintMarginRatio": "0.025",
- "cum": "150.0"
+ "initialLeverage": "25",
+ "notionalCap": "300000",
+ "notionalFloor": "60000",
+ "maintMarginRatio": "0.02",
+ "cum": "350.0"
}
},
{
"tier": 4.0,
"currency": "USDT",
- "minNotional": 80000.0,
- "maxNotional": 800000.0,
- "maintenanceMarginRate": 0.05,
- "maxLeverage": 10.0,
+ "minNotional": 300000.0,
+ "maxNotional": 600000.0,
+ "maintenanceMarginRate": 0.025,
+ "maxLeverage": 20.0,
"info": {
"bracket": "4",
- "initialLeverage": "10",
- "notionalCap": "800000",
- "notionalFloor": "80000",
- "maintMarginRatio": "0.05",
- "cum": "2150.0"
+ "initialLeverage": "20",
+ "notionalCap": "600000",
+ "notionalFloor": "300000",
+ "maintMarginRatio": "0.025",
+ "cum": "1850.0"
}
},
{
"tier": 5.0,
"currency": "USDT",
- "minNotional": 800000.0,
- "maxNotional": 1600000.0,
- "maintenanceMarginRate": 0.1,
- "maxLeverage": 5.0,
+ "minNotional": 600000.0,
+ "maxNotional": 3000000.0,
+ "maintenanceMarginRate": 0.05,
+ "maxLeverage": 10.0,
"info": {
"bracket": "5",
- "initialLeverage": "5",
- "notionalCap": "1600000",
- "notionalFloor": "800000",
- "maintMarginRatio": "0.1",
- "cum": "42150.0"
+ "initialLeverage": "10",
+ "notionalCap": "3000000",
+ "notionalFloor": "600000",
+ "maintMarginRatio": "0.05",
+ "cum": "16850.0"
}
},
{
"tier": 6.0,
"currency": "USDT",
- "minNotional": 1600000.0,
- "maxNotional": 2000000.0,
- "maintenanceMarginRate": 0.125,
- "maxLeverage": 4.0,
+ "minNotional": 3000000.0,
+ "maxNotional": 6000000.0,
+ "maintenanceMarginRate": 0.1,
+ "maxLeverage": 5.0,
"info": {
"bracket": "6",
- "initialLeverage": "4",
- "notionalCap": "2000000",
- "notionalFloor": "1600000",
- "maintMarginRatio": "0.125",
- "cum": "82150.0"
+ "initialLeverage": "5",
+ "notionalCap": "6000000",
+ "notionalFloor": "3000000",
+ "maintMarginRatio": "0.1",
+ "cum": "166850.0"
}
},
{
"tier": 7.0,
"currency": "USDT",
- "minNotional": 2000000.0,
- "maxNotional": 4000000.0,
- "maintenanceMarginRate": 0.25,
- "maxLeverage": 2.0,
+ "minNotional": 6000000.0,
+ "maxNotional": 7500000.0,
+ "maintenanceMarginRate": 0.125,
+ "maxLeverage": 4.0,
"info": {
"bracket": "7",
- "initialLeverage": "2",
- "notionalCap": "4000000",
- "notionalFloor": "2000000",
- "maintMarginRatio": "0.25",
- "cum": "332150.0"
+ "initialLeverage": "4",
+ "notionalCap": "7500000",
+ "notionalFloor": "6000000",
+ "maintMarginRatio": "0.125",
+ "cum": "316850.0"
}
},
{
"tier": 8.0,
"currency": "USDT",
- "minNotional": 4000000.0,
- "maxNotional": 8000000.0,
+ "minNotional": 7500000.0,
+ "maxNotional": 15000000.0,
+ "maintenanceMarginRate": 0.25,
+ "maxLeverage": 2.0,
+ "info": {
+ "bracket": "8",
+ "initialLeverage": "2",
+ "notionalCap": "15000000",
+ "notionalFloor": "7500000",
+ "maintMarginRatio": "0.25",
+ "cum": "1254350.0"
+ }
+ },
+ {
+ "tier": 9.0,
+ "currency": "USDT",
+ "minNotional": 15000000.0,
+ "maxNotional": 30000000.0,
"maintenanceMarginRate": 0.5,
"maxLeverage": 1.0,
"info": {
- "bracket": "8",
+ "bracket": "9",
"initialLeverage": "1",
- "notionalCap": "8000000",
- "notionalFloor": "4000000",
+ "notionalCap": "30000000",
+ "notionalFloor": "15000000",
"maintMarginRatio": "0.5",
- "cum": "1332150.0"
+ "cum": "5004350.0"
}
}
],
@@ -2795,6 +2827,152 @@
}
}
],
+ "ALPACA/USDT:USDT": [
+ {
+ "tier": 1.0,
+ "currency": "USDT",
+ "minNotional": 0.0,
+ "maxNotional": 5000.0,
+ "maintenanceMarginRate": 0.01,
+ "maxLeverage": 75.0,
+ "info": {
+ "bracket": "1",
+ "initialLeverage": "75",
+ "notionalCap": "5000",
+ "notionalFloor": "0",
+ "maintMarginRatio": "0.01",
+ "cum": "0.0"
+ }
+ },
+ {
+ "tier": 2.0,
+ "currency": "USDT",
+ "minNotional": 5000.0,
+ "maxNotional": 10000.0,
+ "maintenanceMarginRate": 0.015,
+ "maxLeverage": 50.0,
+ "info": {
+ "bracket": "2",
+ "initialLeverage": "50",
+ "notionalCap": "10000",
+ "notionalFloor": "5000",
+ "maintMarginRatio": "0.015",
+ "cum": "25.0"
+ }
+ },
+ {
+ "tier": 3.0,
+ "currency": "USDT",
+ "minNotional": 10000.0,
+ "maxNotional": 20000.0,
+ "maintenanceMarginRate": 0.02,
+ "maxLeverage": 25.0,
+ "info": {
+ "bracket": "3",
+ "initialLeverage": "25",
+ "notionalCap": "20000",
+ "notionalFloor": "10000",
+ "maintMarginRatio": "0.02",
+ "cum": "75.0"
+ }
+ },
+ {
+ "tier": 4.0,
+ "currency": "USDT",
+ "minNotional": 20000.0,
+ "maxNotional": 40000.0,
+ "maintenanceMarginRate": 0.025,
+ "maxLeverage": 20.0,
+ "info": {
+ "bracket": "4",
+ "initialLeverage": "20",
+ "notionalCap": "40000",
+ "notionalFloor": "20000",
+ "maintMarginRatio": "0.025",
+ "cum": "175.0"
+ }
+ },
+ {
+ "tier": 5.0,
+ "currency": "USDT",
+ "minNotional": 40000.0,
+ "maxNotional": 200000.0,
+ "maintenanceMarginRate": 0.05,
+ "maxLeverage": 10.0,
+ "info": {
+ "bracket": "5",
+ "initialLeverage": "10",
+ "notionalCap": "200000",
+ "notionalFloor": "40000",
+ "maintMarginRatio": "0.05",
+ "cum": "1175.0"
+ }
+ },
+ {
+ "tier": 6.0,
+ "currency": "USDT",
+ "minNotional": 200000.0,
+ "maxNotional": 400000.0,
+ "maintenanceMarginRate": 0.1,
+ "maxLeverage": 5.0,
+ "info": {
+ "bracket": "6",
+ "initialLeverage": "5",
+ "notionalCap": "400000",
+ "notionalFloor": "200000",
+ "maintMarginRatio": "0.1",
+ "cum": "11175.0"
+ }
+ },
+ {
+ "tier": 7.0,
+ "currency": "USDT",
+ "minNotional": 400000.0,
+ "maxNotional": 500000.0,
+ "maintenanceMarginRate": 0.125,
+ "maxLeverage": 4.0,
+ "info": {
+ "bracket": "7",
+ "initialLeverage": "4",
+ "notionalCap": "500000",
+ "notionalFloor": "400000",
+ "maintMarginRatio": "0.125",
+ "cum": "21175.0"
+ }
+ },
+ {
+ "tier": 8.0,
+ "currency": "USDT",
+ "minNotional": 500000.0,
+ "maxNotional": 1000000.0,
+ "maintenanceMarginRate": 0.25,
+ "maxLeverage": 2.0,
+ "info": {
+ "bracket": "8",
+ "initialLeverage": "2",
+ "notionalCap": "1000000",
+ "notionalFloor": "500000",
+ "maintMarginRatio": "0.25",
+ "cum": "83675.0"
+ }
+ },
+ {
+ "tier": 9.0,
+ "currency": "USDT",
+ "minNotional": 1000000.0,
+ "maxNotional": 2000000.0,
+ "maintenanceMarginRate": 0.5,
+ "maxLeverage": 1.0,
+ "info": {
+ "bracket": "9",
+ "initialLeverage": "1",
+ "notionalCap": "2000000",
+ "notionalFloor": "1000000",
+ "maintMarginRatio": "0.5",
+ "cum": "333675.0"
+ }
+ }
+ ],
"ALPHA/USDT:USDT": [
{
"tier": 1.0,
@@ -5835,6 +6013,136 @@
}
}
],
+ "BANANA/USDT:USDT": [
+ {
+ "tier": 1.0,
+ "currency": "USDT",
+ "minNotional": 0.0,
+ "maxNotional": 5000.0,
+ "maintenanceMarginRate": 0.015,
+ "maxLeverage": 50.0,
+ "info": {
+ "bracket": "1",
+ "initialLeverage": "50",
+ "notionalCap": "5000",
+ "notionalFloor": "0",
+ "maintMarginRatio": "0.015",
+ "cum": "0.0"
+ }
+ },
+ {
+ "tier": 2.0,
+ "currency": "USDT",
+ "minNotional": 5000.0,
+ "maxNotional": 20000.0,
+ "maintenanceMarginRate": 0.02,
+ "maxLeverage": 25.0,
+ "info": {
+ "bracket": "2",
+ "initialLeverage": "25",
+ "notionalCap": "20000",
+ "notionalFloor": "5000",
+ "maintMarginRatio": "0.02",
+ "cum": "25.0"
+ }
+ },
+ {
+ "tier": 3.0,
+ "currency": "USDT",
+ "minNotional": 20000.0,
+ "maxNotional": 25000.0,
+ "maintenanceMarginRate": 0.025,
+ "maxLeverage": 20.0,
+ "info": {
+ "bracket": "3",
+ "initialLeverage": "20",
+ "notionalCap": "25000",
+ "notionalFloor": "20000",
+ "maintMarginRatio": "0.025",
+ "cum": "125.0"
+ }
+ },
+ {
+ "tier": 4.0,
+ "currency": "USDT",
+ "minNotional": 25000.0,
+ "maxNotional": 200000.0,
+ "maintenanceMarginRate": 0.05,
+ "maxLeverage": 10.0,
+ "info": {
+ "bracket": "4",
+ "initialLeverage": "10",
+ "notionalCap": "200000",
+ "notionalFloor": "25000",
+ "maintMarginRatio": "0.05",
+ "cum": "750.0"
+ }
+ },
+ {
+ "tier": 5.0,
+ "currency": "USDT",
+ "minNotional": 200000.0,
+ "maxNotional": 400000.0,
+ "maintenanceMarginRate": 0.1,
+ "maxLeverage": 5.0,
+ "info": {
+ "bracket": "5",
+ "initialLeverage": "5",
+ "notionalCap": "400000",
+ "notionalFloor": "200000",
+ "maintMarginRatio": "0.1",
+ "cum": "10750.0"
+ }
+ },
+ {
+ "tier": 6.0,
+ "currency": "USDT",
+ "minNotional": 400000.0,
+ "maxNotional": 500000.0,
+ "maintenanceMarginRate": 0.125,
+ "maxLeverage": 4.0,
+ "info": {
+ "bracket": "6",
+ "initialLeverage": "4",
+ "notionalCap": "500000",
+ "notionalFloor": "400000",
+ "maintMarginRatio": "0.125",
+ "cum": "20750.0"
+ }
+ },
+ {
+ "tier": 7.0,
+ "currency": "USDT",
+ "minNotional": 500000.0,
+ "maxNotional": 1000000.0,
+ "maintenanceMarginRate": 0.25,
+ "maxLeverage": 2.0,
+ "info": {
+ "bracket": "7",
+ "initialLeverage": "2",
+ "notionalCap": "1000000",
+ "notionalFloor": "500000",
+ "maintMarginRatio": "0.25",
+ "cum": "83250.0"
+ }
+ },
+ {
+ "tier": 8.0,
+ "currency": "USDT",
+ "minNotional": 1000000.0,
+ "maxNotional": 2000000.0,
+ "maintenanceMarginRate": 0.5,
+ "maxLeverage": 1.0,
+ "info": {
+ "bracket": "8",
+ "initialLeverage": "1",
+ "notionalCap": "2000000",
+ "notionalFloor": "1000000",
+ "maintMarginRatio": "0.5",
+ "cum": "333250.0"
+ }
+ }
+ ],
"BAND/USDT:USDT": [
{
"tier": 1.0,
@@ -7416,13 +7724,13 @@
"tier": 3.0,
"currency": "USDT",
"minNotional": 50000.0,
- "maxNotional": 100000.0,
+ "maxNotional": 200000.0,
"maintenanceMarginRate": 0.01,
"maxLeverage": 40.0,
"info": {
"bracket": "3",
"initialLeverage": "40",
- "notionalCap": "100000",
+ "notionalCap": "200000",
"notionalFloor": "50000",
"maintMarginRatio": "0.01",
"cum": "210.0"
@@ -7431,113 +7739,113 @@
{
"tier": 4.0,
"currency": "USDT",
- "minNotional": 100000.0,
- "maxNotional": 500000.0,
+ "minNotional": 200000.0,
+ "maxNotional": 1000000.0,
"maintenanceMarginRate": 0.02,
"maxLeverage": 25.0,
"info": {
"bracket": "4",
"initialLeverage": "25",
- "notionalCap": "500000",
- "notionalFloor": "100000",
+ "notionalCap": "1000000",
+ "notionalFloor": "200000",
"maintMarginRatio": "0.02",
- "cum": "1210.0"
+ "cum": "2210.0"
}
},
{
"tier": 5.0,
"currency": "USDT",
- "minNotional": 500000.0,
+ "minNotional": 1000000.0,
"maxNotional": 2000000.0,
- "maintenanceMarginRate": 0.05,
- "maxLeverage": 10.0,
+ "maintenanceMarginRate": 0.025,
+ "maxLeverage": 20.0,
"info": {
"bracket": "5",
- "initialLeverage": "10",
+ "initialLeverage": "20",
"notionalCap": "2000000",
- "notionalFloor": "500000",
- "maintMarginRatio": "0.05",
- "cum": "16210.0"
+ "notionalFloor": "1000000",
+ "maintMarginRatio": "0.025",
+ "cum": "7210.0"
}
},
{
"tier": 6.0,
"currency": "USDT",
"minNotional": 2000000.0,
- "maxNotional": 6000000.0,
- "maintenanceMarginRate": 0.1,
- "maxLeverage": 5.0,
+ "maxNotional": 10000000.0,
+ "maintenanceMarginRate": 0.05,
+ "maxLeverage": 10.0,
"info": {
"bracket": "6",
- "initialLeverage": "5",
- "notionalCap": "6000000",
+ "initialLeverage": "10",
+ "notionalCap": "10000000",
"notionalFloor": "2000000",
- "maintMarginRatio": "0.1",
- "cum": "116210.0"
+ "maintMarginRatio": "0.05",
+ "cum": "57210.0"
}
},
{
"tier": 7.0,
"currency": "USDT",
- "minNotional": 6000000.0,
- "maxNotional": 10000000.0,
- "maintenanceMarginRate": 0.125,
- "maxLeverage": 4.0,
+ "minNotional": 10000000.0,
+ "maxNotional": 20000000.0,
+ "maintenanceMarginRate": 0.1,
+ "maxLeverage": 5.0,
"info": {
"bracket": "7",
- "initialLeverage": "4",
- "notionalCap": "10000000",
- "notionalFloor": "6000000",
- "maintMarginRatio": "0.125",
- "cum": "266210.0"
+ "initialLeverage": "5",
+ "notionalCap": "20000000",
+ "notionalFloor": "10000000",
+ "maintMarginRatio": "0.1",
+ "cum": "557210.0"
}
},
{
"tier": 8.0,
"currency": "USDT",
- "minNotional": 10000000.0,
- "maxNotional": 20000000.0,
- "maintenanceMarginRate": 0.15,
- "maxLeverage": 3.0,
+ "minNotional": 20000000.0,
+ "maxNotional": 25000000.0,
+ "maintenanceMarginRate": 0.125,
+ "maxLeverage": 4.0,
"info": {
"bracket": "8",
- "initialLeverage": "3",
- "notionalCap": "20000000",
- "notionalFloor": "10000000",
- "maintMarginRatio": "0.15",
- "cum": "516210.0"
+ "initialLeverage": "4",
+ "notionalCap": "25000000",
+ "notionalFloor": "20000000",
+ "maintMarginRatio": "0.125",
+ "cum": "1057210.0"
}
},
{
"tier": 9.0,
"currency": "USDT",
- "minNotional": 20000000.0,
- "maxNotional": 30000000.0,
+ "minNotional": 25000000.0,
+ "maxNotional": 50000000.0,
"maintenanceMarginRate": 0.25,
"maxLeverage": 2.0,
"info": {
"bracket": "9",
"initialLeverage": "2",
- "notionalCap": "30000000",
- "notionalFloor": "20000000",
+ "notionalCap": "50000000",
+ "notionalFloor": "25000000",
"maintMarginRatio": "0.25",
- "cum": "2516210.0"
+ "cum": "4182210.0"
}
},
{
"tier": 10.0,
"currency": "USDT",
- "minNotional": 30000000.0,
- "maxNotional": 50000000.0,
+ "minNotional": 50000000.0,
+ "maxNotional": 100000000.0,
"maintenanceMarginRate": 0.5,
"maxLeverage": 1.0,
"info": {
"bracket": "10",
"initialLeverage": "1",
- "notionalCap": "50000000",
- "notionalFloor": "30000000",
+ "notionalCap": "100000000",
+ "notionalFloor": "50000000",
"maintMarginRatio": "0.5",
- "cum": "10016210.0"
+ "cum": "16682210.0"
}
}
],
@@ -7888,128 +8196,144 @@
"tier": 1.0,
"currency": "USDT",
"minNotional": 0.0,
- "maxNotional": 5000.0,
- "maintenanceMarginRate": 0.015,
- "maxLeverage": 50.0,
+ "maxNotional": 10000.0,
+ "maintenanceMarginRate": 0.01,
+ "maxLeverage": 75.0,
"info": {
"bracket": "1",
- "initialLeverage": "50",
- "notionalCap": "5000",
+ "initialLeverage": "75",
+ "notionalCap": "10000",
"notionalFloor": "0",
- "maintMarginRatio": "0.015",
+ "maintMarginRatio": "0.01",
"cum": "0.0"
}
},
{
"tier": 2.0,
"currency": "USDT",
- "minNotional": 5000.0,
- "maxNotional": 50000.0,
- "maintenanceMarginRate": 0.02,
- "maxLeverage": 25.0,
+ "minNotional": 10000.0,
+ "maxNotional": 40000.0,
+ "maintenanceMarginRate": 0.015,
+ "maxLeverage": 50.0,
"info": {
"bracket": "2",
- "initialLeverage": "25",
- "notionalCap": "50000",
- "notionalFloor": "5000",
- "maintMarginRatio": "0.02",
- "cum": "25.0"
+ "initialLeverage": "50",
+ "notionalCap": "40000",
+ "notionalFloor": "10000",
+ "maintMarginRatio": "0.015",
+ "cum": "50.0"
}
},
{
"tier": 3.0,
"currency": "USDT",
- "minNotional": 50000.0,
- "maxNotional": 150000.0,
- "maintenanceMarginRate": 0.025,
- "maxLeverage": 20.0,
+ "minNotional": 40000.0,
+ "maxNotional": 200000.0,
+ "maintenanceMarginRate": 0.02,
+ "maxLeverage": 25.0,
"info": {
"bracket": "3",
- "initialLeverage": "20",
- "notionalCap": "150000",
- "notionalFloor": "50000",
- "maintMarginRatio": "0.025",
- "cum": "275.0"
+ "initialLeverage": "25",
+ "notionalCap": "200000",
+ "notionalFloor": "40000",
+ "maintMarginRatio": "0.02",
+ "cum": "250.0"
}
},
{
"tier": 4.0,
"currency": "USDT",
- "minNotional": 150000.0,
- "maxNotional": 1500000.0,
- "maintenanceMarginRate": 0.05,
- "maxLeverage": 10.0,
+ "minNotional": 200000.0,
+ "maxNotional": 400000.0,
+ "maintenanceMarginRate": 0.025,
+ "maxLeverage": 20.0,
"info": {
"bracket": "4",
- "initialLeverage": "10",
- "notionalCap": "1500000",
- "notionalFloor": "150000",
- "maintMarginRatio": "0.05",
- "cum": "4025.0"
+ "initialLeverage": "20",
+ "notionalCap": "400000",
+ "notionalFloor": "200000",
+ "maintMarginRatio": "0.025",
+ "cum": "1250.0"
}
},
{
"tier": 5.0,
"currency": "USDT",
- "minNotional": 1500000.0,
- "maxNotional": 3000000.0,
- "maintenanceMarginRate": 0.1,
- "maxLeverage": 5.0,
+ "minNotional": 400000.0,
+ "maxNotional": 2000000.0,
+ "maintenanceMarginRate": 0.05,
+ "maxLeverage": 10.0,
"info": {
"bracket": "5",
- "initialLeverage": "5",
- "notionalCap": "3000000",
- "notionalFloor": "1500000",
- "maintMarginRatio": "0.1",
- "cum": "79025.0"
+ "initialLeverage": "10",
+ "notionalCap": "2000000",
+ "notionalFloor": "400000",
+ "maintMarginRatio": "0.05",
+ "cum": "11250.0"
}
},
{
"tier": 6.0,
"currency": "USDT",
- "minNotional": 3000000.0,
- "maxNotional": 3750000.0,
- "maintenanceMarginRate": 0.125,
- "maxLeverage": 4.0,
+ "minNotional": 2000000.0,
+ "maxNotional": 4000000.0,
+ "maintenanceMarginRate": 0.1,
+ "maxLeverage": 5.0,
"info": {
"bracket": "6",
- "initialLeverage": "4",
- "notionalCap": "3750000",
- "notionalFloor": "3000000",
- "maintMarginRatio": "0.125",
- "cum": "154025.0"
+ "initialLeverage": "5",
+ "notionalCap": "4000000",
+ "notionalFloor": "2000000",
+ "maintMarginRatio": "0.1",
+ "cum": "111250.0"
}
},
{
"tier": 7.0,
"currency": "USDT",
- "minNotional": 3750000.0,
- "maxNotional": 7500000.0,
- "maintenanceMarginRate": 0.25,
- "maxLeverage": 2.0,
+ "minNotional": 4000000.0,
+ "maxNotional": 5000000.0,
+ "maintenanceMarginRate": 0.125,
+ "maxLeverage": 4.0,
"info": {
"bracket": "7",
- "initialLeverage": "2",
- "notionalCap": "7500000",
- "notionalFloor": "3750000",
- "maintMarginRatio": "0.25",
- "cum": "622775.0"
+ "initialLeverage": "4",
+ "notionalCap": "5000000",
+ "notionalFloor": "4000000",
+ "maintMarginRatio": "0.125",
+ "cum": "211250.0"
}
},
{
"tier": 8.0,
"currency": "USDT",
- "minNotional": 7500000.0,
- "maxNotional": 15000000.0,
+ "minNotional": 5000000.0,
+ "maxNotional": 10000000.0,
+ "maintenanceMarginRate": 0.25,
+ "maxLeverage": 2.0,
+ "info": {
+ "bracket": "8",
+ "initialLeverage": "2",
+ "notionalCap": "10000000",
+ "notionalFloor": "5000000",
+ "maintMarginRatio": "0.25",
+ "cum": "836250.0"
+ }
+ },
+ {
+ "tier": 9.0,
+ "currency": "USDT",
+ "minNotional": 10000000.0,
+ "maxNotional": 20000000.0,
"maintenanceMarginRate": 0.5,
"maxLeverage": 1.0,
"info": {
- "bracket": "8",
+ "bracket": "9",
"initialLeverage": "1",
- "notionalCap": "15000000",
- "notionalFloor": "7500000",
+ "notionalCap": "20000000",
+ "notionalFloor": "10000000",
"maintMarginRatio": "0.5",
- "cum": "2497775.0"
+ "cum": "3336250.0"
}
}
],
@@ -8127,6 +8451,136 @@
}
}
],
+ "BRETT/USDT:USDT": [
+ {
+ "tier": 1.0,
+ "currency": "USDT",
+ "minNotional": 0.0,
+ "maxNotional": 5000.0,
+ "maintenanceMarginRate": 0.015,
+ "maxLeverage": 50.0,
+ "info": {
+ "bracket": "1",
+ "initialLeverage": "50",
+ "notionalCap": "5000",
+ "notionalFloor": "0",
+ "maintMarginRatio": "0.015",
+ "cum": "0.0"
+ }
+ },
+ {
+ "tier": 2.0,
+ "currency": "USDT",
+ "minNotional": 5000.0,
+ "maxNotional": 20000.0,
+ "maintenanceMarginRate": 0.02,
+ "maxLeverage": 25.0,
+ "info": {
+ "bracket": "2",
+ "initialLeverage": "25",
+ "notionalCap": "20000",
+ "notionalFloor": "5000",
+ "maintMarginRatio": "0.02",
+ "cum": "25.0"
+ }
+ },
+ {
+ "tier": 3.0,
+ "currency": "USDT",
+ "minNotional": 20000.0,
+ "maxNotional": 25000.0,
+ "maintenanceMarginRate": 0.025,
+ "maxLeverage": 20.0,
+ "info": {
+ "bracket": "3",
+ "initialLeverage": "20",
+ "notionalCap": "25000",
+ "notionalFloor": "20000",
+ "maintMarginRatio": "0.025",
+ "cum": "125.0"
+ }
+ },
+ {
+ "tier": 4.0,
+ "currency": "USDT",
+ "minNotional": 25000.0,
+ "maxNotional": 200000.0,
+ "maintenanceMarginRate": 0.05,
+ "maxLeverage": 10.0,
+ "info": {
+ "bracket": "4",
+ "initialLeverage": "10",
+ "notionalCap": "200000",
+ "notionalFloor": "25000",
+ "maintMarginRatio": "0.05",
+ "cum": "750.0"
+ }
+ },
+ {
+ "tier": 5.0,
+ "currency": "USDT",
+ "minNotional": 200000.0,
+ "maxNotional": 400000.0,
+ "maintenanceMarginRate": 0.1,
+ "maxLeverage": 5.0,
+ "info": {
+ "bracket": "5",
+ "initialLeverage": "5",
+ "notionalCap": "400000",
+ "notionalFloor": "200000",
+ "maintMarginRatio": "0.1",
+ "cum": "10750.0"
+ }
+ },
+ {
+ "tier": 6.0,
+ "currency": "USDT",
+ "minNotional": 400000.0,
+ "maxNotional": 500000.0,
+ "maintenanceMarginRate": 0.125,
+ "maxLeverage": 4.0,
+ "info": {
+ "bracket": "6",
+ "initialLeverage": "4",
+ "notionalCap": "500000",
+ "notionalFloor": "400000",
+ "maintMarginRatio": "0.125",
+ "cum": "20750.0"
+ }
+ },
+ {
+ "tier": 7.0,
+ "currency": "USDT",
+ "minNotional": 500000.0,
+ "maxNotional": 1000000.0,
+ "maintenanceMarginRate": 0.25,
+ "maxLeverage": 2.0,
+ "info": {
+ "bracket": "7",
+ "initialLeverage": "2",
+ "notionalCap": "1000000",
+ "notionalFloor": "500000",
+ "maintMarginRatio": "0.25",
+ "cum": "83250.0"
+ }
+ },
+ {
+ "tier": 8.0,
+ "currency": "USDT",
+ "minNotional": 1000000.0,
+ "maxNotional": 2000000.0,
+ "maintenanceMarginRate": 0.5,
+ "maxLeverage": 1.0,
+ "info": {
+ "bracket": "8",
+ "initialLeverage": "1",
+ "notionalCap": "2000000",
+ "notionalFloor": "1000000",
+ "maintMarginRatio": "0.5",
+ "cum": "333250.0"
+ }
+ }
+ ],
"BSV/USDT:USDT": [
{
"tier": 1.0,
@@ -10426,112 +10880,144 @@
"tier": 1.0,
"currency": "USDT",
"minNotional": 0.0,
- "maxNotional": 50000.0,
- "maintenanceMarginRate": 0.01,
- "maxLeverage": 25.0,
+ "maxNotional": 10000.0,
+ "maintenanceMarginRate": 0.0065,
+ "maxLeverage": 75.0,
"info": {
"bracket": "1",
- "initialLeverage": "25",
- "notionalCap": "50000",
+ "initialLeverage": "75",
+ "notionalCap": "10000",
"notionalFloor": "0",
- "maintMarginRatio": "0.01",
+ "maintMarginRatio": "0.0065",
"cum": "0.0"
}
},
{
"tier": 2.0,
"currency": "USDT",
- "minNotional": 50000.0,
- "maxNotional": 150000.0,
- "maintenanceMarginRate": 0.025,
- "maxLeverage": 20.0,
+ "minNotional": 10000.0,
+ "maxNotional": 50000.0,
+ "maintenanceMarginRate": 0.01,
+ "maxLeverage": 50.0,
"info": {
"bracket": "2",
- "initialLeverage": "20",
- "notionalCap": "150000",
- "notionalFloor": "50000",
- "maintMarginRatio": "0.025",
- "cum": "750.0"
+ "initialLeverage": "50",
+ "notionalCap": "50000",
+ "notionalFloor": "10000",
+ "maintMarginRatio": "0.01",
+ "cum": "35.0"
}
},
{
"tier": 3.0,
"currency": "USDT",
- "minNotional": 150000.0,
- "maxNotional": 250000.0,
- "maintenanceMarginRate": 0.05,
- "maxLeverage": 10.0,
+ "minNotional": 50000.0,
+ "maxNotional": 200000.0,
+ "maintenanceMarginRate": 0.02,
+ "maxLeverage": 25.0,
"info": {
"bracket": "3",
- "initialLeverage": "10",
- "notionalCap": "250000",
- "notionalFloor": "150000",
- "maintMarginRatio": "0.05",
- "cum": "4500.0"
+ "initialLeverage": "25",
+ "notionalCap": "200000",
+ "notionalFloor": "50000",
+ "maintMarginRatio": "0.02",
+ "cum": "535.0"
}
},
{
"tier": 4.0,
"currency": "USDT",
- "minNotional": 250000.0,
- "maxNotional": 500000.0,
- "maintenanceMarginRate": 0.1,
- "maxLeverage": 5.0,
+ "minNotional": 200000.0,
+ "maxNotional": 400000.0,
+ "maintenanceMarginRate": 0.025,
+ "maxLeverage": 20.0,
"info": {
"bracket": "4",
- "initialLeverage": "5",
- "notionalCap": "500000",
- "notionalFloor": "250000",
- "maintMarginRatio": "0.1",
- "cum": "17000.0"
+ "initialLeverage": "20",
+ "notionalCap": "400000",
+ "notionalFloor": "200000",
+ "maintMarginRatio": "0.025",
+ "cum": "1535.0"
}
},
{
"tier": 5.0,
"currency": "USDT",
- "minNotional": 500000.0,
- "maxNotional": 1000000.0,
- "maintenanceMarginRate": 0.125,
- "maxLeverage": 4.0,
+ "minNotional": 400000.0,
+ "maxNotional": 2000000.0,
+ "maintenanceMarginRate": 0.05,
+ "maxLeverage": 10.0,
"info": {
"bracket": "5",
- "initialLeverage": "4",
- "notionalCap": "1000000",
- "notionalFloor": "500000",
- "maintMarginRatio": "0.125",
- "cum": "29500.0"
+ "initialLeverage": "10",
+ "notionalCap": "2000000",
+ "notionalFloor": "400000",
+ "maintMarginRatio": "0.05",
+ "cum": "11535.0"
}
},
{
"tier": 6.0,
"currency": "USDT",
- "minNotional": 1000000.0,
- "maxNotional": 3000000.0,
- "maintenanceMarginRate": 0.25,
- "maxLeverage": 2.0,
+ "minNotional": 2000000.0,
+ "maxNotional": 4000000.0,
+ "maintenanceMarginRate": 0.1,
+ "maxLeverage": 5.0,
"info": {
"bracket": "6",
- "initialLeverage": "2",
- "notionalCap": "3000000",
- "notionalFloor": "1000000",
- "maintMarginRatio": "0.25",
- "cum": "154500.0"
+ "initialLeverage": "5",
+ "notionalCap": "4000000",
+ "notionalFloor": "2000000",
+ "maintMarginRatio": "0.1",
+ "cum": "111535.0"
}
},
{
"tier": 7.0,
"currency": "USDT",
- "minNotional": 3000000.0,
+ "minNotional": 4000000.0,
"maxNotional": 5000000.0,
+ "maintenanceMarginRate": 0.125,
+ "maxLeverage": 4.0,
+ "info": {
+ "bracket": "7",
+ "initialLeverage": "4",
+ "notionalCap": "5000000",
+ "notionalFloor": "4000000",
+ "maintMarginRatio": "0.125",
+ "cum": "211535.0"
+ }
+ },
+ {
+ "tier": 8.0,
+ "currency": "USDT",
+ "minNotional": 5000000.0,
+ "maxNotional": 10000000.0,
+ "maintenanceMarginRate": 0.25,
+ "maxLeverage": 2.0,
+ "info": {
+ "bracket": "8",
+ "initialLeverage": "2",
+ "notionalCap": "10000000",
+ "notionalFloor": "5000000",
+ "maintMarginRatio": "0.25",
+ "cum": "836535.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": "5000000",
- "notionalFloor": "3000000",
+ "notionalCap": "20000000",
+ "notionalFloor": "10000000",
"maintMarginRatio": "0.5",
- "cum": "904500.0"
+ "cum": "3336535.0"
}
}
],
@@ -11860,160 +12346,306 @@
"tier": 1.0,
"currency": "USDT",
"minNotional": 0.0,
- "maxNotional": 5000.0,
+ "maxNotional": 10000.0,
"maintenanceMarginRate": 0.005,
"maxLeverage": 75.0,
"info": {
"bracket": "1",
"initialLeverage": "75",
- "notionalCap": "5000",
+ "notionalCap": "10000",
"notionalFloor": "0",
"maintMarginRatio": "0.005",
"cum": "0.0"
}
},
+ {
+ "tier": 2.0,
+ "currency": "USDT",
+ "minNotional": 10000.0,
+ "maxNotional": 50000.0,
+ "maintenanceMarginRate": 0.007,
+ "maxLeverage": 50.0,
+ "info": {
+ "bracket": "2",
+ "initialLeverage": "50",
+ "notionalCap": "50000",
+ "notionalFloor": "10000",
+ "maintMarginRatio": "0.007",
+ "cum": "20.0"
+ }
+ },
+ {
+ "tier": 3.0,
+ "currency": "USDT",
+ "minNotional": 50000.0,
+ "maxNotional": 750000.0,
+ "maintenanceMarginRate": 0.01,
+ "maxLeverage": 40.0,
+ "info": {
+ "bracket": "3",
+ "initialLeverage": "40",
+ "notionalCap": "750000",
+ "notionalFloor": "50000",
+ "maintMarginRatio": "0.01",
+ "cum": "170.0"
+ }
+ },
+ {
+ "tier": 4.0,
+ "currency": "USDT",
+ "minNotional": 750000.0,
+ "maxNotional": 800000.0,
+ "maintenanceMarginRate": 0.02,
+ "maxLeverage": 25.0,
+ "info": {
+ "bracket": "4",
+ "initialLeverage": "25",
+ "notionalCap": "800000",
+ "notionalFloor": "750000",
+ "maintMarginRatio": "0.02",
+ "cum": "7670.0"
+ }
+ },
+ {
+ "tier": 5.0,
+ "currency": "USDT",
+ "minNotional": 800000.0,
+ "maxNotional": 1600000.0,
+ "maintenanceMarginRate": 0.025,
+ "maxLeverage": 20.0,
+ "info": {
+ "bracket": "5",
+ "initialLeverage": "20",
+ "notionalCap": "1600000",
+ "notionalFloor": "800000",
+ "maintMarginRatio": "0.025",
+ "cum": "11670.0"
+ }
+ },
+ {
+ "tier": 6.0,
+ "currency": "USDT",
+ "minNotional": 1600000.0,
+ "maxNotional": 8000000.0,
+ "maintenanceMarginRate": 0.05,
+ "maxLeverage": 10.0,
+ "info": {
+ "bracket": "6",
+ "initialLeverage": "10",
+ "notionalCap": "8000000",
+ "notionalFloor": "1600000",
+ "maintMarginRatio": "0.05",
+ "cum": "51670.0"
+ }
+ },
+ {
+ "tier": 7.0,
+ "currency": "USDT",
+ "minNotional": 8000000.0,
+ "maxNotional": 16000000.0,
+ "maintenanceMarginRate": 0.1,
+ "maxLeverage": 5.0,
+ "info": {
+ "bracket": "7",
+ "initialLeverage": "5",
+ "notionalCap": "16000000",
+ "notionalFloor": "8000000",
+ "maintMarginRatio": "0.1",
+ "cum": "451670.0"
+ }
+ },
+ {
+ "tier": 8.0,
+ "currency": "USDT",
+ "minNotional": 16000000.0,
+ "maxNotional": 20000000.0,
+ "maintenanceMarginRate": 0.125,
+ "maxLeverage": 4.0,
+ "info": {
+ "bracket": "8",
+ "initialLeverage": "4",
+ "notionalCap": "20000000",
+ "notionalFloor": "16000000",
+ "maintMarginRatio": "0.125",
+ "cum": "851670.0"
+ }
+ },
+ {
+ "tier": 9.0,
+ "currency": "USDT",
+ "minNotional": 20000000.0,
+ "maxNotional": 40000000.0,
+ "maintenanceMarginRate": 0.25,
+ "maxLeverage": 2.0,
+ "info": {
+ "bracket": "9",
+ "initialLeverage": "2",
+ "notionalCap": "40000000",
+ "notionalFloor": "20000000",
+ "maintMarginRatio": "0.25",
+ "cum": "3351670.0"
+ }
+ },
+ {
+ "tier": 10.0,
+ "currency": "USDT",
+ "minNotional": 40000000.0,
+ "maxNotional": 80000000.0,
+ "maintenanceMarginRate": 0.5,
+ "maxLeverage": 1.0,
+ "info": {
+ "bracket": "10",
+ "initialLeverage": "1",
+ "notionalCap": "80000000",
+ "notionalFloor": "40000000",
+ "maintMarginRatio": "0.5",
+ "cum": "13351670.0"
+ }
+ }
+ ],
+ "DOGS/USDT:USDT": [
+ {
+ "tier": 1.0,
+ "currency": "USDT",
+ "minNotional": 0.0,
+ "maxNotional": 5000.0,
+ "maintenanceMarginRate": 0.01,
+ "maxLeverage": 75.0,
+ "info": {
+ "bracket": "1",
+ "initialLeverage": "75",
+ "notionalCap": "5000",
+ "notionalFloor": "0",
+ "maintMarginRatio": "0.01",
+ "cum": "0.0"
+ }
+ },
{
"tier": 2.0,
"currency": "USDT",
"minNotional": 5000.0,
"maxNotional": 10000.0,
- "maintenanceMarginRate": 0.006,
+ "maintenanceMarginRate": 0.015,
"maxLeverage": 50.0,
"info": {
"bracket": "2",
"initialLeverage": "50",
"notionalCap": "10000",
"notionalFloor": "5000",
- "maintMarginRatio": "0.006",
- "cum": "5.0"
+ "maintMarginRatio": "0.015",
+ "cum": "25.0"
}
},
{
"tier": 3.0,
"currency": "USDT",
"minNotional": 10000.0,
- "maxNotional": 50000.0,
- "maintenanceMarginRate": 0.007,
- "maxLeverage": 40.0,
+ "maxNotional": 20000.0,
+ "maintenanceMarginRate": 0.02,
+ "maxLeverage": 25.0,
"info": {
"bracket": "3",
- "initialLeverage": "40",
- "notionalCap": "50000",
+ "initialLeverage": "25",
+ "notionalCap": "20000",
"notionalFloor": "10000",
- "maintMarginRatio": "0.007",
- "cum": "15.0"
+ "maintMarginRatio": "0.02",
+ "cum": "75.0"
}
},
{
"tier": 4.0,
"currency": "USDT",
- "minNotional": 50000.0,
- "maxNotional": 750000.0,
- "maintenanceMarginRate": 0.01,
- "maxLeverage": 25.0,
+ "minNotional": 20000.0,
+ "maxNotional": 40000.0,
+ "maintenanceMarginRate": 0.025,
+ "maxLeverage": 20.0,
"info": {
"bracket": "4",
- "initialLeverage": "25",
- "notionalCap": "750000",
- "notionalFloor": "50000",
- "maintMarginRatio": "0.01",
- "cum": "165.0"
+ "initialLeverage": "20",
+ "notionalCap": "40000",
+ "notionalFloor": "20000",
+ "maintMarginRatio": "0.025",
+ "cum": "175.0"
}
},
{
"tier": 5.0,
"currency": "USDT",
- "minNotional": 750000.0,
- "maxNotional": 1100000.0,
- "maintenanceMarginRate": 0.025,
- "maxLeverage": 20.0,
+ "minNotional": 40000.0,
+ "maxNotional": 200000.0,
+ "maintenanceMarginRate": 0.05,
+ "maxLeverage": 10.0,
"info": {
"bracket": "5",
- "initialLeverage": "20",
- "notionalCap": "1100000",
- "notionalFloor": "750000",
- "maintMarginRatio": "0.025",
- "cum": "11415.0"
+ "initialLeverage": "10",
+ "notionalCap": "200000",
+ "notionalFloor": "40000",
+ "maintMarginRatio": "0.05",
+ "cum": "1175.0"
}
},
{
"tier": 6.0,
"currency": "USDT",
- "minNotional": 1100000.0,
- "maxNotional": 2200000.0,
- "maintenanceMarginRate": 0.05,
- "maxLeverage": 10.0,
+ "minNotional": 200000.0,
+ "maxNotional": 400000.0,
+ "maintenanceMarginRate": 0.1,
+ "maxLeverage": 5.0,
"info": {
"bracket": "6",
- "initialLeverage": "10",
- "notionalCap": "2200000",
- "notionalFloor": "1100000",
- "maintMarginRatio": "0.05",
- "cum": "38915.0"
+ "initialLeverage": "5",
+ "notionalCap": "400000",
+ "notionalFloor": "200000",
+ "maintMarginRatio": "0.1",
+ "cum": "11175.0"
}
},
{
"tier": 7.0,
"currency": "USDT",
- "minNotional": 2200000.0,
- "maxNotional": 5600000.0,
- "maintenanceMarginRate": 0.1,
- "maxLeverage": 5.0,
+ "minNotional": 400000.0,
+ "maxNotional": 500000.0,
+ "maintenanceMarginRate": 0.125,
+ "maxLeverage": 4.0,
"info": {
"bracket": "7",
- "initialLeverage": "5",
- "notionalCap": "5600000",
- "notionalFloor": "2200000",
- "maintMarginRatio": "0.1",
- "cum": "148915.0"
+ "initialLeverage": "4",
+ "notionalCap": "500000",
+ "notionalFloor": "400000",
+ "maintMarginRatio": "0.125",
+ "cum": "21175.0"
}
},
{
"tier": 8.0,
"currency": "USDT",
- "minNotional": 5600000.0,
- "maxNotional": 7000000.0,
- "maintenanceMarginRate": 0.125,
- "maxLeverage": 4.0,
+ "minNotional": 500000.0,
+ "maxNotional": 1000000.0,
+ "maintenanceMarginRate": 0.25,
+ "maxLeverage": 2.0,
"info": {
"bracket": "8",
- "initialLeverage": "4",
- "notionalCap": "7000000",
- "notionalFloor": "5600000",
- "maintMarginRatio": "0.125",
- "cum": "288915.0"
+ "initialLeverage": "2",
+ "notionalCap": "1000000",
+ "notionalFloor": "500000",
+ "maintMarginRatio": "0.25",
+ "cum": "83675.0"
}
},
{
"tier": 9.0,
"currency": "USDT",
- "minNotional": 7000000.0,
- "maxNotional": 18000000.0,
- "maintenanceMarginRate": 0.25,
- "maxLeverage": 2.0,
- "info": {
- "bracket": "9",
- "initialLeverage": "2",
- "notionalCap": "18000000",
- "notionalFloor": "7000000",
- "maintMarginRatio": "0.25",
- "cum": "1163915.0"
- }
- },
- {
- "tier": 10.0,
- "currency": "USDT",
- "minNotional": 18000000.0,
- "maxNotional": 30000000.0,
+ "minNotional": 1000000.0,
+ "maxNotional": 2000000.0,
"maintenanceMarginRate": 0.5,
"maxLeverage": 1.0,
"info": {
- "bracket": "10",
+ "bracket": "9",
"initialLeverage": "1",
- "notionalCap": "30000000",
- "notionalFloor": "18000000",
+ "notionalCap": "2000000",
+ "notionalFloor": "1000000",
"maintMarginRatio": "0.5",
- "cum": "5663915.0"
+ "cum": "333675.0"
}
}
],
@@ -15402,10 +16034,10 @@
"minNotional": 0.0,
"maxNotional": 5000.0,
"maintenanceMarginRate": 0.015,
- "maxLeverage": 50.0,
+ "maxLeverage": 21.0,
"info": {
"bracket": "1",
- "initialLeverage": "50",
+ "initialLeverage": "21",
"notionalCap": "5000",
"notionalFloor": "0",
"maintMarginRatio": "0.015",
@@ -15819,20 +16451,20 @@
}
}
],
- "GAL/USDT:USDT": [
+ "G/USDT:USDT": [
{
"tier": 1.0,
"currency": "USDT",
"minNotional": 0.0,
"maxNotional": 5000.0,
- "maintenanceMarginRate": 0.02,
- "maxLeverage": 11.0,
+ "maintenanceMarginRate": 0.015,
+ "maxLeverage": 50.0,
"info": {
"bracket": "1",
- "initialLeverage": "11",
+ "initialLeverage": "50",
"notionalCap": "5000",
"notionalFloor": "0",
- "maintMarginRatio": "0.02",
+ "maintMarginRatio": "0.015",
"cum": "0.0"
}
},
@@ -15840,80 +16472,112 @@
"tier": 2.0,
"currency": "USDT",
"minNotional": 5000.0,
- "maxNotional": 25000.0,
- "maintenanceMarginRate": 0.025,
- "maxLeverage": 10.0,
+ "maxNotional": 20000.0,
+ "maintenanceMarginRate": 0.02,
+ "maxLeverage": 25.0,
"info": {
"bracket": "2",
- "initialLeverage": "10",
- "notionalCap": "25000",
+ "initialLeverage": "25",
+ "notionalCap": "20000",
"notionalFloor": "5000",
- "maintMarginRatio": "0.025",
+ "maintMarginRatio": "0.02",
"cum": "25.0"
}
},
{
"tier": 3.0,
"currency": "USDT",
- "minNotional": 25000.0,
- "maxNotional": 100000.0,
- "maintenanceMarginRate": 0.05,
- "maxLeverage": 8.0,
+ "minNotional": 20000.0,
+ "maxNotional": 25000.0,
+ "maintenanceMarginRate": 0.025,
+ "maxLeverage": 20.0,
"info": {
"bracket": "3",
- "initialLeverage": "8",
- "notionalCap": "100000",
- "notionalFloor": "25000",
- "maintMarginRatio": "0.05",
- "cum": "650.0"
+ "initialLeverage": "20",
+ "notionalCap": "25000",
+ "notionalFloor": "20000",
+ "maintMarginRatio": "0.025",
+ "cum": "125.0"
}
},
{
"tier": 4.0,
"currency": "USDT",
- "minNotional": 100000.0,
- "maxNotional": 250000.0,
- "maintenanceMarginRate": 0.1,
- "maxLeverage": 5.0,
+ "minNotional": 25000.0,
+ "maxNotional": 200000.0,
+ "maintenanceMarginRate": 0.05,
+ "maxLeverage": 10.0,
"info": {
"bracket": "4",
- "initialLeverage": "5",
- "notionalCap": "250000",
- "notionalFloor": "100000",
- "maintMarginRatio": "0.1",
- "cum": "5650.0"
+ "initialLeverage": "10",
+ "notionalCap": "200000",
+ "notionalFloor": "25000",
+ "maintMarginRatio": "0.05",
+ "cum": "750.0"
}
},
{
"tier": 5.0,
"currency": "USDT",
- "minNotional": 250000.0,
- "maxNotional": 1000000.0,
- "maintenanceMarginRate": 0.125,
- "maxLeverage": 2.0,
+ "minNotional": 200000.0,
+ "maxNotional": 400000.0,
+ "maintenanceMarginRate": 0.1,
+ "maxLeverage": 5.0,
"info": {
"bracket": "5",
- "initialLeverage": "2",
- "notionalCap": "1000000",
- "notionalFloor": "250000",
- "maintMarginRatio": "0.125",
- "cum": "11900.0"
+ "initialLeverage": "5",
+ "notionalCap": "400000",
+ "notionalFloor": "200000",
+ "maintMarginRatio": "0.1",
+ "cum": "10750.0"
}
},
{
"tier": 6.0,
"currency": "USDT",
+ "minNotional": 400000.0,
+ "maxNotional": 500000.0,
+ "maintenanceMarginRate": 0.125,
+ "maxLeverage": 4.0,
+ "info": {
+ "bracket": "6",
+ "initialLeverage": "4",
+ "notionalCap": "500000",
+ "notionalFloor": "400000",
+ "maintMarginRatio": "0.125",
+ "cum": "20750.0"
+ }
+ },
+ {
+ "tier": 7.0,
+ "currency": "USDT",
+ "minNotional": 500000.0,
+ "maxNotional": 1000000.0,
+ "maintenanceMarginRate": 0.25,
+ "maxLeverage": 2.0,
+ "info": {
+ "bracket": "7",
+ "initialLeverage": "2",
+ "notionalCap": "1000000",
+ "notionalFloor": "500000",
+ "maintMarginRatio": "0.25",
+ "cum": "83250.0"
+ }
+ },
+ {
+ "tier": 8.0,
+ "currency": "USDT",
"minNotional": 1000000.0,
- "maxNotional": 3000000.0,
+ "maxNotional": 2000000.0,
"maintenanceMarginRate": 0.5,
"maxLeverage": 1.0,
"info": {
- "bracket": "6",
+ "bracket": "8",
"initialLeverage": "1",
- "notionalCap": "3000000",
+ "notionalCap": "2000000",
"notionalFloor": "1000000",
"maintMarginRatio": "0.5",
- "cum": "386900.0"
+ "cum": "333250.0"
}
}
],
@@ -22464,10 +23128,10 @@
"minNotional": 0.0,
"maxNotional": 10000.0,
"maintenanceMarginRate": 0.006,
- "maxLeverage": 75.0,
+ "maxLeverage": 51.0,
"info": {
"bracket": "1",
- "initialLeverage": "75",
+ "initialLeverage": "51",
"notionalCap": "10000",
"notionalFloor": "0",
"maintMarginRatio": "0.006",
@@ -22590,13 +23254,13 @@
"tier": 9.0,
"currency": "USDC",
"minNotional": 18000000.0,
- "maxNotional": 30000000.0,
+ "maxNotional": 19000000.0,
"maintenanceMarginRate": 0.5,
"maxLeverage": 1.0,
"info": {
"bracket": "9",
"initialLeverage": "1",
- "notionalCap": "30000000",
+ "notionalCap": "19000000",
"notionalFloor": "18000000",
"maintMarginRatio": "0.5",
"cum": "5491585.0"
@@ -22610,10 +23274,10 @@
"minNotional": 0.0,
"maxNotional": 10000.0,
"maintenanceMarginRate": 0.006,
- "maxLeverage": 75.0,
+ "maxLeverage": 51.0,
"info": {
"bracket": "1",
- "initialLeverage": "75",
+ "initialLeverage": "51",
"notionalCap": "10000",
"notionalFloor": "0",
"maintMarginRatio": "0.006",
@@ -22736,13 +23400,13 @@
"tier": 9.0,
"currency": "USDT",
"minNotional": 18000000.0,
- "maxNotional": 30000000.0,
+ "maxNotional": 19000000.0,
"maintenanceMarginRate": 0.5,
"maxLeverage": 1.0,
"info": {
"bracket": "9",
"initialLeverage": "1",
- "notionalCap": "30000000",
+ "notionalCap": "19000000",
"notionalFloor": "18000000",
"maintMarginRatio": "0.5",
"cum": "5491585.0"
@@ -22977,6 +23641,152 @@
}
}
],
+ "MBOX/USDT:USDT": [
+ {
+ "tier": 1.0,
+ "currency": "USDT",
+ "minNotional": 0.0,
+ "maxNotional": 5000.0,
+ "maintenanceMarginRate": 0.01,
+ "maxLeverage": 75.0,
+ "info": {
+ "bracket": "1",
+ "initialLeverage": "75",
+ "notionalCap": "5000",
+ "notionalFloor": "0",
+ "maintMarginRatio": "0.01",
+ "cum": "0.0"
+ }
+ },
+ {
+ "tier": 2.0,
+ "currency": "USDT",
+ "minNotional": 5000.0,
+ "maxNotional": 10000.0,
+ "maintenanceMarginRate": 0.015,
+ "maxLeverage": 50.0,
+ "info": {
+ "bracket": "2",
+ "initialLeverage": "50",
+ "notionalCap": "10000",
+ "notionalFloor": "5000",
+ "maintMarginRatio": "0.015",
+ "cum": "25.0"
+ }
+ },
+ {
+ "tier": 3.0,
+ "currency": "USDT",
+ "minNotional": 10000.0,
+ "maxNotional": 20000.0,
+ "maintenanceMarginRate": 0.02,
+ "maxLeverage": 25.0,
+ "info": {
+ "bracket": "3",
+ "initialLeverage": "25",
+ "notionalCap": "20000",
+ "notionalFloor": "10000",
+ "maintMarginRatio": "0.02",
+ "cum": "75.0"
+ }
+ },
+ {
+ "tier": 4.0,
+ "currency": "USDT",
+ "minNotional": 20000.0,
+ "maxNotional": 40000.0,
+ "maintenanceMarginRate": 0.025,
+ "maxLeverage": 20.0,
+ "info": {
+ "bracket": "4",
+ "initialLeverage": "20",
+ "notionalCap": "40000",
+ "notionalFloor": "20000",
+ "maintMarginRatio": "0.025",
+ "cum": "175.0"
+ }
+ },
+ {
+ "tier": 5.0,
+ "currency": "USDT",
+ "minNotional": 40000.0,
+ "maxNotional": 200000.0,
+ "maintenanceMarginRate": 0.05,
+ "maxLeverage": 10.0,
+ "info": {
+ "bracket": "5",
+ "initialLeverage": "10",
+ "notionalCap": "200000",
+ "notionalFloor": "40000",
+ "maintMarginRatio": "0.05",
+ "cum": "1175.0"
+ }
+ },
+ {
+ "tier": 6.0,
+ "currency": "USDT",
+ "minNotional": 200000.0,
+ "maxNotional": 400000.0,
+ "maintenanceMarginRate": 0.1,
+ "maxLeverage": 5.0,
+ "info": {
+ "bracket": "6",
+ "initialLeverage": "5",
+ "notionalCap": "400000",
+ "notionalFloor": "200000",
+ "maintMarginRatio": "0.1",
+ "cum": "11175.0"
+ }
+ },
+ {
+ "tier": 7.0,
+ "currency": "USDT",
+ "minNotional": 400000.0,
+ "maxNotional": 500000.0,
+ "maintenanceMarginRate": 0.125,
+ "maxLeverage": 4.0,
+ "info": {
+ "bracket": "7",
+ "initialLeverage": "4",
+ "notionalCap": "500000",
+ "notionalFloor": "400000",
+ "maintMarginRatio": "0.125",
+ "cum": "21175.0"
+ }
+ },
+ {
+ "tier": 8.0,
+ "currency": "USDT",
+ "minNotional": 500000.0,
+ "maxNotional": 1000000.0,
+ "maintenanceMarginRate": 0.25,
+ "maxLeverage": 2.0,
+ "info": {
+ "bracket": "8",
+ "initialLeverage": "2",
+ "notionalCap": "1000000",
+ "notionalFloor": "500000",
+ "maintMarginRatio": "0.25",
+ "cum": "83675.0"
+ }
+ },
+ {
+ "tier": 9.0,
+ "currency": "USDT",
+ "minNotional": 1000000.0,
+ "maxNotional": 2000000.0,
+ "maintenanceMarginRate": 0.5,
+ "maxLeverage": 1.0,
+ "info": {
+ "bracket": "9",
+ "initialLeverage": "1",
+ "notionalCap": "2000000",
+ "notionalFloor": "1000000",
+ "maintMarginRatio": "0.5",
+ "cum": "333675.0"
+ }
+ }
+ ],
"MDT/USDT:USDT": [
{
"tier": 1.0,
@@ -23356,13 +24166,13 @@
"tier": 2.0,
"currency": "USDT",
"minNotional": 5000.0,
- "maxNotional": 20000.0,
+ "maxNotional": 25000.0,
"maintenanceMarginRate": 0.02,
"maxLeverage": 25.0,
"info": {
"bracket": "2",
"initialLeverage": "25",
- "notionalCap": "20000",
+ "notionalCap": "25000",
"notionalFloor": "5000",
"maintMarginRatio": "0.02",
"cum": "25.0"
@@ -23371,97 +24181,97 @@
{
"tier": 3.0,
"currency": "USDT",
- "minNotional": 20000.0,
- "maxNotional": 25000.0,
+ "minNotional": 25000.0,
+ "maxNotional": 50000.0,
"maintenanceMarginRate": 0.025,
"maxLeverage": 20.0,
"info": {
"bracket": "3",
"initialLeverage": "20",
- "notionalCap": "25000",
- "notionalFloor": "20000",
+ "notionalCap": "50000",
+ "notionalFloor": "25000",
"maintMarginRatio": "0.025",
- "cum": "125.0"
+ "cum": "150.0"
}
},
{
"tier": 4.0,
"currency": "USDT",
- "minNotional": 25000.0,
- "maxNotional": 200000.0,
+ "minNotional": 50000.0,
+ "maxNotional": 500000.0,
"maintenanceMarginRate": 0.05,
"maxLeverage": 10.0,
"info": {
"bracket": "4",
"initialLeverage": "10",
- "notionalCap": "200000",
- "notionalFloor": "25000",
+ "notionalCap": "500000",
+ "notionalFloor": "50000",
"maintMarginRatio": "0.05",
- "cum": "750.0"
+ "cum": "1400.0"
}
},
{
"tier": 5.0,
"currency": "USDT",
- "minNotional": 200000.0,
- "maxNotional": 400000.0,
+ "minNotional": 500000.0,
+ "maxNotional": 1000000.0,
"maintenanceMarginRate": 0.1,
"maxLeverage": 5.0,
"info": {
"bracket": "5",
"initialLeverage": "5",
- "notionalCap": "400000",
- "notionalFloor": "200000",
+ "notionalCap": "1000000",
+ "notionalFloor": "500000",
"maintMarginRatio": "0.1",
- "cum": "10750.0"
+ "cum": "26400.0"
}
},
{
"tier": 6.0,
"currency": "USDT",
- "minNotional": 400000.0,
- "maxNotional": 500000.0,
+ "minNotional": 1000000.0,
+ "maxNotional": 1250000.0,
"maintenanceMarginRate": 0.125,
"maxLeverage": 4.0,
"info": {
"bracket": "6",
"initialLeverage": "4",
- "notionalCap": "500000",
- "notionalFloor": "400000",
+ "notionalCap": "1250000",
+ "notionalFloor": "1000000",
"maintMarginRatio": "0.125",
- "cum": "20750.0"
+ "cum": "51400.0"
}
},
{
"tier": 7.0,
"currency": "USDT",
- "minNotional": 500000.0,
- "maxNotional": 1000000.0,
+ "minNotional": 1250000.0,
+ "maxNotional": 2500000.0,
"maintenanceMarginRate": 0.25,
"maxLeverage": 2.0,
"info": {
"bracket": "7",
"initialLeverage": "2",
- "notionalCap": "1000000",
- "notionalFloor": "500000",
+ "notionalCap": "2500000",
+ "notionalFloor": "1250000",
"maintMarginRatio": "0.25",
- "cum": "83250.0"
+ "cum": "207650.0"
}
},
{
"tier": 8.0,
"currency": "USDT",
- "minNotional": 1000000.0,
- "maxNotional": 2000000.0,
+ "minNotional": 2500000.0,
+ "maxNotional": 5000000.0,
"maintenanceMarginRate": 0.5,
"maxLeverage": 1.0,
"info": {
"bracket": "8",
"initialLeverage": "1",
- "notionalCap": "2000000",
- "notionalFloor": "1000000",
+ "notionalCap": "5000000",
+ "notionalFloor": "2500000",
"maintMarginRatio": "0.5",
- "cum": "333250.0"
+ "cum": "832650.0"
}
}
],
@@ -24918,128 +25728,144 @@
"tier": 1.0,
"currency": "USDT",
"minNotional": 0.0,
- "maxNotional": 5000.0,
- "maintenanceMarginRate": 0.015,
- "maxLeverage": 50.0,
+ "maxNotional": 10000.0,
+ "maintenanceMarginRate": 0.01,
+ "maxLeverage": 75.0,
"info": {
"bracket": "1",
- "initialLeverage": "50",
- "notionalCap": "5000",
+ "initialLeverage": "75",
+ "notionalCap": "10000",
"notionalFloor": "0",
- "maintMarginRatio": "0.015",
+ "maintMarginRatio": "0.01",
"cum": "0.0"
}
},
{
"tier": 2.0,
"currency": "USDT",
- "minNotional": 5000.0,
- "maxNotional": 50000.0,
- "maintenanceMarginRate": 0.02,
- "maxLeverage": 25.0,
+ "minNotional": 10000.0,
+ "maxNotional": 60000.0,
+ "maintenanceMarginRate": 0.015,
+ "maxLeverage": 50.0,
"info": {
"bracket": "2",
- "initialLeverage": "25",
- "notionalCap": "50000",
- "notionalFloor": "5000",
- "maintMarginRatio": "0.02",
- "cum": "25.0"
+ "initialLeverage": "50",
+ "notionalCap": "60000",
+ "notionalFloor": "10000",
+ "maintMarginRatio": "0.015",
+ "cum": "50.0"
}
},
{
"tier": 3.0,
"currency": "USDT",
- "minNotional": 50000.0,
- "maxNotional": 100000.0,
- "maintenanceMarginRate": 0.025,
- "maxLeverage": 20.0,
+ "minNotional": 60000.0,
+ "maxNotional": 300000.0,
+ "maintenanceMarginRate": 0.02,
+ "maxLeverage": 25.0,
"info": {
"bracket": "3",
- "initialLeverage": "20",
- "notionalCap": "100000",
- "notionalFloor": "50000",
- "maintMarginRatio": "0.025",
- "cum": "275.0"
+ "initialLeverage": "25",
+ "notionalCap": "300000",
+ "notionalFloor": "60000",
+ "maintMarginRatio": "0.02",
+ "cum": "350.0"
}
},
{
"tier": 4.0,
"currency": "USDT",
- "minNotional": 100000.0,
- "maxNotional": 1000000.0,
- "maintenanceMarginRate": 0.05,
- "maxLeverage": 10.0,
+ "minNotional": 300000.0,
+ "maxNotional": 600000.0,
+ "maintenanceMarginRate": 0.025,
+ "maxLeverage": 20.0,
"info": {
"bracket": "4",
- "initialLeverage": "10",
- "notionalCap": "1000000",
- "notionalFloor": "100000",
- "maintMarginRatio": "0.05",
- "cum": "2775.0"
+ "initialLeverage": "20",
+ "notionalCap": "600000",
+ "notionalFloor": "300000",
+ "maintMarginRatio": "0.025",
+ "cum": "1850.0"
}
},
{
"tier": 5.0,
"currency": "USDT",
- "minNotional": 1000000.0,
- "maxNotional": 2000000.0,
- "maintenanceMarginRate": 0.1,
- "maxLeverage": 5.0,
+ "minNotional": 600000.0,
+ "maxNotional": 3000000.0,
+ "maintenanceMarginRate": 0.05,
+ "maxLeverage": 10.0,
"info": {
"bracket": "5",
- "initialLeverage": "5",
- "notionalCap": "2000000",
- "notionalFloor": "1000000",
- "maintMarginRatio": "0.1",
- "cum": "52775.0"
+ "initialLeverage": "10",
+ "notionalCap": "3000000",
+ "notionalFloor": "600000",
+ "maintMarginRatio": "0.05",
+ "cum": "16850.0"
}
},
{
"tier": 6.0,
"currency": "USDT",
- "minNotional": 2000000.0,
- "maxNotional": 2500000.0,
- "maintenanceMarginRate": 0.125,
- "maxLeverage": 4.0,
+ "minNotional": 3000000.0,
+ "maxNotional": 6000000.0,
+ "maintenanceMarginRate": 0.1,
+ "maxLeverage": 5.0,
"info": {
"bracket": "6",
- "initialLeverage": "4",
- "notionalCap": "2500000",
- "notionalFloor": "2000000",
- "maintMarginRatio": "0.125",
- "cum": "102775.0"
+ "initialLeverage": "5",
+ "notionalCap": "6000000",
+ "notionalFloor": "3000000",
+ "maintMarginRatio": "0.1",
+ "cum": "166850.0"
}
},
{
"tier": 7.0,
"currency": "USDT",
- "minNotional": 2500000.0,
- "maxNotional": 5000000.0,
- "maintenanceMarginRate": 0.25,
- "maxLeverage": 2.0,
+ "minNotional": 6000000.0,
+ "maxNotional": 7500000.0,
+ "maintenanceMarginRate": 0.125,
+ "maxLeverage": 4.0,
"info": {
"bracket": "7",
- "initialLeverage": "2",
- "notionalCap": "5000000",
- "notionalFloor": "2500000",
- "maintMarginRatio": "0.25",
- "cum": "415275.0"
+ "initialLeverage": "4",
+ "notionalCap": "7500000",
+ "notionalFloor": "6000000",
+ "maintMarginRatio": "0.125",
+ "cum": "316850.0"
}
},
{
"tier": 8.0,
"currency": "USDT",
- "minNotional": 5000000.0,
- "maxNotional": 10000000.0,
+ "minNotional": 7500000.0,
+ "maxNotional": 15000000.0,
+ "maintenanceMarginRate": 0.25,
+ "maxLeverage": 2.0,
+ "info": {
+ "bracket": "8",
+ "initialLeverage": "2",
+ "notionalCap": "15000000",
+ "notionalFloor": "7500000",
+ "maintMarginRatio": "0.25",
+ "cum": "1254350.0"
+ }
+ },
+ {
+ "tier": 9.0,
+ "currency": "USDT",
+ "minNotional": 15000000.0,
+ "maxNotional": 30000000.0,
"maintenanceMarginRate": 0.5,
"maxLeverage": 1.0,
"info": {
- "bracket": "8",
+ "bracket": "9",
"initialLeverage": "1",
- "notionalCap": "10000000",
- "notionalFloor": "5000000",
+ "notionalCap": "30000000",
+ "notionalFloor": "15000000",
"maintMarginRatio": "0.5",
- "cum": "1665275.0"
+ "cum": "5004350.0"
}
}
],
@@ -25157,6 +25983,152 @@
}
}
],
+ "NULS/USDT:USDT": [
+ {
+ "tier": 1.0,
+ "currency": "USDT",
+ "minNotional": 0.0,
+ "maxNotional": 5000.0,
+ "maintenanceMarginRate": 0.01,
+ "maxLeverage": 75.0,
+ "info": {
+ "bracket": "1",
+ "initialLeverage": "75",
+ "notionalCap": "5000",
+ "notionalFloor": "0",
+ "maintMarginRatio": "0.01",
+ "cum": "0.0"
+ }
+ },
+ {
+ "tier": 2.0,
+ "currency": "USDT",
+ "minNotional": 5000.0,
+ "maxNotional": 10000.0,
+ "maintenanceMarginRate": 0.015,
+ "maxLeverage": 50.0,
+ "info": {
+ "bracket": "2",
+ "initialLeverage": "50",
+ "notionalCap": "10000",
+ "notionalFloor": "5000",
+ "maintMarginRatio": "0.015",
+ "cum": "25.0"
+ }
+ },
+ {
+ "tier": 3.0,
+ "currency": "USDT",
+ "minNotional": 10000.0,
+ "maxNotional": 20000.0,
+ "maintenanceMarginRate": 0.02,
+ "maxLeverage": 25.0,
+ "info": {
+ "bracket": "3",
+ "initialLeverage": "25",
+ "notionalCap": "20000",
+ "notionalFloor": "10000",
+ "maintMarginRatio": "0.02",
+ "cum": "75.0"
+ }
+ },
+ {
+ "tier": 4.0,
+ "currency": "USDT",
+ "minNotional": 20000.0,
+ "maxNotional": 40000.0,
+ "maintenanceMarginRate": 0.025,
+ "maxLeverage": 20.0,
+ "info": {
+ "bracket": "4",
+ "initialLeverage": "20",
+ "notionalCap": "40000",
+ "notionalFloor": "20000",
+ "maintMarginRatio": "0.025",
+ "cum": "175.0"
+ }
+ },
+ {
+ "tier": 5.0,
+ "currency": "USDT",
+ "minNotional": 40000.0,
+ "maxNotional": 200000.0,
+ "maintenanceMarginRate": 0.05,
+ "maxLeverage": 10.0,
+ "info": {
+ "bracket": "5",
+ "initialLeverage": "10",
+ "notionalCap": "200000",
+ "notionalFloor": "40000",
+ "maintMarginRatio": "0.05",
+ "cum": "1175.0"
+ }
+ },
+ {
+ "tier": 6.0,
+ "currency": "USDT",
+ "minNotional": 200000.0,
+ "maxNotional": 400000.0,
+ "maintenanceMarginRate": 0.1,
+ "maxLeverage": 5.0,
+ "info": {
+ "bracket": "6",
+ "initialLeverage": "5",
+ "notionalCap": "400000",
+ "notionalFloor": "200000",
+ "maintMarginRatio": "0.1",
+ "cum": "11175.0"
+ }
+ },
+ {
+ "tier": 7.0,
+ "currency": "USDT",
+ "minNotional": 400000.0,
+ "maxNotional": 500000.0,
+ "maintenanceMarginRate": 0.125,
+ "maxLeverage": 4.0,
+ "info": {
+ "bracket": "7",
+ "initialLeverage": "4",
+ "notionalCap": "500000",
+ "notionalFloor": "400000",
+ "maintMarginRatio": "0.125",
+ "cum": "21175.0"
+ }
+ },
+ {
+ "tier": 8.0,
+ "currency": "USDT",
+ "minNotional": 500000.0,
+ "maxNotional": 1000000.0,
+ "maintenanceMarginRate": 0.25,
+ "maxLeverage": 2.0,
+ "info": {
+ "bracket": "8",
+ "initialLeverage": "2",
+ "notionalCap": "1000000",
+ "notionalFloor": "500000",
+ "maintMarginRatio": "0.25",
+ "cum": "83675.0"
+ }
+ },
+ {
+ "tier": 9.0,
+ "currency": "USDT",
+ "minNotional": 1000000.0,
+ "maxNotional": 2000000.0,
+ "maintenanceMarginRate": 0.5,
+ "maxLeverage": 1.0,
+ "info": {
+ "bracket": "9",
+ "initialLeverage": "1",
+ "notionalCap": "2000000",
+ "notionalFloor": "1000000",
+ "maintMarginRatio": "0.5",
+ "cum": "333675.0"
+ }
+ }
+ ],
"OCEAN/USDT:USDT": [
{
"tier": 1.0,
@@ -26642,13 +27614,13 @@
"tier": 1.0,
"currency": "USDT",
"minNotional": 0.0,
- "maxNotional": 5000.0,
+ "maxNotional": 10000.0,
"maintenanceMarginRate": 0.0065,
"maxLeverage": 75.0,
"info": {
"bracket": "1",
"initialLeverage": "75",
- "notionalCap": "5000",
+ "notionalCap": "10000",
"notionalFloor": "0",
"maintMarginRatio": "0.0065",
"cum": "0.0"
@@ -26657,23 +27629,23 @@
{
"tier": 2.0,
"currency": "USDT",
- "minNotional": 5000.0,
- "maxNotional": 75000.0,
+ "minNotional": 10000.0,
+ "maxNotional": 80000.0,
"maintenanceMarginRate": 0.01,
"maxLeverage": 50.0,
"info": {
"bracket": "2",
"initialLeverage": "50",
- "notionalCap": "75000",
- "notionalFloor": "5000",
+ "notionalCap": "80000",
+ "notionalFloor": "10000",
"maintMarginRatio": "0.01",
- "cum": "17.5"
+ "cum": "35.0"
}
},
{
"tier": 3.0,
"currency": "USDT",
- "minNotional": 75000.0,
+ "minNotional": 80000.0,
"maxNotional": 150000.0,
"maintenanceMarginRate": 0.015,
"maxLeverage": 40.0,
@@ -26681,31 +27653,31 @@
"bracket": "3",
"initialLeverage": "40",
"notionalCap": "150000",
- "notionalFloor": "75000",
+ "notionalFloor": "80000",
"maintMarginRatio": "0.015",
- "cum": "392.5"
+ "cum": "435.0"
}
},
{
"tier": 4.0,
"currency": "USDT",
"minNotional": 150000.0,
- "maxNotional": 300000.0,
+ "maxNotional": 400000.0,
"maintenanceMarginRate": 0.02,
"maxLeverage": 25.0,
"info": {
"bracket": "4",
"initialLeverage": "25",
- "notionalCap": "300000",
+ "notionalCap": "400000",
"notionalFloor": "150000",
"maintMarginRatio": "0.02",
- "cum": "1142.5"
+ "cum": "1185.0"
}
},
{
"tier": 5.0,
"currency": "USDT",
- "minNotional": 300000.0,
+ "minNotional": 400000.0,
"maxNotional": 1000000.0,
"maintenanceMarginRate": 0.025,
"maxLeverage": 20.0,
@@ -26713,47 +27685,47 @@
"bracket": "5",
"initialLeverage": "20",
"notionalCap": "1000000",
- "notionalFloor": "300000",
+ "notionalFloor": "400000",
"maintMarginRatio": "0.025",
- "cum": "2642.5"
+ "cum": "3185.0"
}
},
{
"tier": 6.0,
"currency": "USDT",
"minNotional": 1000000.0,
- "maxNotional": 3000000.0,
+ "maxNotional": 4000000.0,
"maintenanceMarginRate": 0.05,
"maxLeverage": 10.0,
"info": {
"bracket": "6",
"initialLeverage": "10",
- "notionalCap": "3000000",
+ "notionalCap": "4000000",
"notionalFloor": "1000000",
"maintMarginRatio": "0.05",
- "cum": "27642.5"
+ "cum": "28185.0"
}
},
{
"tier": 7.0,
"currency": "USDT",
- "minNotional": 3000000.0,
- "maxNotional": 6000000.0,
+ "minNotional": 4000000.0,
+ "maxNotional": 8000000.0,
"maintenanceMarginRate": 0.1,
"maxLeverage": 5.0,
"info": {
"bracket": "7",
"initialLeverage": "5",
- "notionalCap": "6000000",
- "notionalFloor": "3000000",
+ "notionalCap": "8000000",
+ "notionalFloor": "4000000",
"maintMarginRatio": "0.1",
- "cum": "177642.5"
+ "cum": "228185.0"
}
},
{
"tier": 8.0,
"currency": "USDT",
- "minNotional": 6000000.0,
+ "minNotional": 8000000.0,
"maxNotional": 10000000.0,
"maintenanceMarginRate": 0.125,
"maxLeverage": 4.0,
@@ -26761,9 +27733,9 @@
"bracket": "8",
"initialLeverage": "4",
"notionalCap": "10000000",
- "notionalFloor": "6000000",
+ "notionalFloor": "8000000",
"maintMarginRatio": "0.125",
- "cum": "327642.5"
+ "cum": "428185.0"
}
},
{
@@ -26779,23 +27751,23 @@
"notionalCap": "20000000",
"notionalFloor": "10000000",
"maintMarginRatio": "0.25",
- "cum": "1577642.5"
+ "cum": "1678185.0"
}
},
{
"tier": 10.0,
"currency": "USDT",
"minNotional": 20000000.0,
- "maxNotional": 30000000.0,
+ "maxNotional": 40000000.0,
"maintenanceMarginRate": 0.5,
"maxLeverage": 1.0,
"info": {
"bracket": "10",
"initialLeverage": "1",
- "notionalCap": "30000000",
+ "notionalCap": "40000000",
"notionalFloor": "20000000",
"maintMarginRatio": "0.5",
- "cum": "6577642.5"
+ "cum": "6678185.0"
}
}
],
@@ -27629,6 +28601,152 @@
}
}
],
+ "POPCAT/USDT:USDT": [
+ {
+ "tier": 1.0,
+ "currency": "USDT",
+ "minNotional": 0.0,
+ "maxNotional": 5000.0,
+ "maintenanceMarginRate": 0.01,
+ "maxLeverage": 75.0,
+ "info": {
+ "bracket": "1",
+ "initialLeverage": "75",
+ "notionalCap": "5000",
+ "notionalFloor": "0",
+ "maintMarginRatio": "0.01",
+ "cum": "0.0"
+ }
+ },
+ {
+ "tier": 2.0,
+ "currency": "USDT",
+ "minNotional": 5000.0,
+ "maxNotional": 10000.0,
+ "maintenanceMarginRate": 0.015,
+ "maxLeverage": 50.0,
+ "info": {
+ "bracket": "2",
+ "initialLeverage": "50",
+ "notionalCap": "10000",
+ "notionalFloor": "5000",
+ "maintMarginRatio": "0.015",
+ "cum": "25.0"
+ }
+ },
+ {
+ "tier": 3.0,
+ "currency": "USDT",
+ "minNotional": 10000.0,
+ "maxNotional": 20000.0,
+ "maintenanceMarginRate": 0.02,
+ "maxLeverage": 25.0,
+ "info": {
+ "bracket": "3",
+ "initialLeverage": "25",
+ "notionalCap": "20000",
+ "notionalFloor": "10000",
+ "maintMarginRatio": "0.02",
+ "cum": "75.0"
+ }
+ },
+ {
+ "tier": 4.0,
+ "currency": "USDT",
+ "minNotional": 20000.0,
+ "maxNotional": 40000.0,
+ "maintenanceMarginRate": 0.025,
+ "maxLeverage": 20.0,
+ "info": {
+ "bracket": "4",
+ "initialLeverage": "20",
+ "notionalCap": "40000",
+ "notionalFloor": "20000",
+ "maintMarginRatio": "0.025",
+ "cum": "175.0"
+ }
+ },
+ {
+ "tier": 5.0,
+ "currency": "USDT",
+ "minNotional": 40000.0,
+ "maxNotional": 200000.0,
+ "maintenanceMarginRate": 0.05,
+ "maxLeverage": 10.0,
+ "info": {
+ "bracket": "5",
+ "initialLeverage": "10",
+ "notionalCap": "200000",
+ "notionalFloor": "40000",
+ "maintMarginRatio": "0.05",
+ "cum": "1175.0"
+ }
+ },
+ {
+ "tier": 6.0,
+ "currency": "USDT",
+ "minNotional": 200000.0,
+ "maxNotional": 400000.0,
+ "maintenanceMarginRate": 0.1,
+ "maxLeverage": 5.0,
+ "info": {
+ "bracket": "6",
+ "initialLeverage": "5",
+ "notionalCap": "400000",
+ "notionalFloor": "200000",
+ "maintMarginRatio": "0.1",
+ "cum": "11175.0"
+ }
+ },
+ {
+ "tier": 7.0,
+ "currency": "USDT",
+ "minNotional": 400000.0,
+ "maxNotional": 500000.0,
+ "maintenanceMarginRate": 0.125,
+ "maxLeverage": 4.0,
+ "info": {
+ "bracket": "7",
+ "initialLeverage": "4",
+ "notionalCap": "500000",
+ "notionalFloor": "400000",
+ "maintMarginRatio": "0.125",
+ "cum": "21175.0"
+ }
+ },
+ {
+ "tier": 8.0,
+ "currency": "USDT",
+ "minNotional": 500000.0,
+ "maxNotional": 1000000.0,
+ "maintenanceMarginRate": 0.25,
+ "maxLeverage": 2.0,
+ "info": {
+ "bracket": "8",
+ "initialLeverage": "2",
+ "notionalCap": "1000000",
+ "notionalFloor": "500000",
+ "maintMarginRatio": "0.25",
+ "cum": "83675.0"
+ }
+ },
+ {
+ "tier": 9.0,
+ "currency": "USDT",
+ "minNotional": 1000000.0,
+ "maxNotional": 2000000.0,
+ "maintenanceMarginRate": 0.5,
+ "maxLeverage": 1.0,
+ "info": {
+ "bracket": "9",
+ "initialLeverage": "1",
+ "notionalCap": "2000000",
+ "notionalFloor": "1000000",
+ "maintMarginRatio": "0.5",
+ "cum": "333675.0"
+ }
+ }
+ ],
"PORTAL/USDT:USDT": [
{
"tier": 1.0,
@@ -28313,6 +29431,152 @@
}
}
],
+ "RARE/USDT:USDT": [
+ {
+ "tier": 1.0,
+ "currency": "USDT",
+ "minNotional": 0.0,
+ "maxNotional": 10000.0,
+ "maintenanceMarginRate": 0.01,
+ "maxLeverage": 75.0,
+ "info": {
+ "bracket": "1",
+ "initialLeverage": "75",
+ "notionalCap": "10000",
+ "notionalFloor": "0",
+ "maintMarginRatio": "0.01",
+ "cum": "0.0"
+ }
+ },
+ {
+ "tier": 2.0,
+ "currency": "USDT",
+ "minNotional": 10000.0,
+ "maxNotional": 30000.0,
+ "maintenanceMarginRate": 0.015,
+ "maxLeverage": 50.0,
+ "info": {
+ "bracket": "2",
+ "initialLeverage": "50",
+ "notionalCap": "30000",
+ "notionalFloor": "10000",
+ "maintMarginRatio": "0.015",
+ "cum": "50.0"
+ }
+ },
+ {
+ "tier": 3.0,
+ "currency": "USDT",
+ "minNotional": 30000.0,
+ "maxNotional": 150000.0,
+ "maintenanceMarginRate": 0.02,
+ "maxLeverage": 25.0,
+ "info": {
+ "bracket": "3",
+ "initialLeverage": "25",
+ "notionalCap": "150000",
+ "notionalFloor": "30000",
+ "maintMarginRatio": "0.02",
+ "cum": "200.0"
+ }
+ },
+ {
+ "tier": 4.0,
+ "currency": "USDT",
+ "minNotional": 150000.0,
+ "maxNotional": 300000.0,
+ "maintenanceMarginRate": 0.025,
+ "maxLeverage": 20.0,
+ "info": {
+ "bracket": "4",
+ "initialLeverage": "20",
+ "notionalCap": "300000",
+ "notionalFloor": "150000",
+ "maintMarginRatio": "0.025",
+ "cum": "950.0"
+ }
+ },
+ {
+ "tier": 5.0,
+ "currency": "USDT",
+ "minNotional": 300000.0,
+ "maxNotional": 1500000.0,
+ "maintenanceMarginRate": 0.05,
+ "maxLeverage": 10.0,
+ "info": {
+ "bracket": "5",
+ "initialLeverage": "10",
+ "notionalCap": "1500000",
+ "notionalFloor": "300000",
+ "maintMarginRatio": "0.05",
+ "cum": "8450.0"
+ }
+ },
+ {
+ "tier": 6.0,
+ "currency": "USDT",
+ "minNotional": 1500000.0,
+ "maxNotional": 3000000.0,
+ "maintenanceMarginRate": 0.1,
+ "maxLeverage": 5.0,
+ "info": {
+ "bracket": "6",
+ "initialLeverage": "5",
+ "notionalCap": "3000000",
+ "notionalFloor": "1500000",
+ "maintMarginRatio": "0.1",
+ "cum": "83450.0"
+ }
+ },
+ {
+ "tier": 7.0,
+ "currency": "USDT",
+ "minNotional": 3000000.0,
+ "maxNotional": 3750000.0,
+ "maintenanceMarginRate": 0.125,
+ "maxLeverage": 4.0,
+ "info": {
+ "bracket": "7",
+ "initialLeverage": "4",
+ "notionalCap": "3750000",
+ "notionalFloor": "3000000",
+ "maintMarginRatio": "0.125",
+ "cum": "158450.0"
+ }
+ },
+ {
+ "tier": 8.0,
+ "currency": "USDT",
+ "minNotional": 3750000.0,
+ "maxNotional": 7500000.0,
+ "maintenanceMarginRate": 0.25,
+ "maxLeverage": 2.0,
+ "info": {
+ "bracket": "8",
+ "initialLeverage": "2",
+ "notionalCap": "7500000",
+ "notionalFloor": "3750000",
+ "maintMarginRatio": "0.25",
+ "cum": "627200.0"
+ }
+ },
+ {
+ "tier": 9.0,
+ "currency": "USDT",
+ "minNotional": 7500000.0,
+ "maxNotional": 15000000.0,
+ "maintenanceMarginRate": 0.5,
+ "maxLeverage": 1.0,
+ "info": {
+ "bracket": "9",
+ "initialLeverage": "1",
+ "notionalCap": "15000000",
+ "notionalFloor": "7500000",
+ "maintMarginRatio": "0.5",
+ "cum": "2502200.0"
+ }
+ }
+ ],
"RAY/USDT:USDT": [
{
"tier": 1.0,
@@ -28526,6 +29790,250 @@
}
],
"REEF/USDT:USDT": [
+ {
+ "tier": 1.0,
+ "currency": "USDT",
+ "minNotional": 0.0,
+ "maxNotional": 5000.0,
+ "maintenanceMarginRate": 0.015,
+ "maxLeverage": 26.0,
+ "info": {
+ "bracket": "1",
+ "initialLeverage": "26",
+ "notionalCap": "5000",
+ "notionalFloor": "0",
+ "maintMarginRatio": "0.015",
+ "cum": "0.0"
+ }
+ },
+ {
+ "tier": 2.0,
+ "currency": "USDT",
+ "minNotional": 5000.0,
+ "maxNotional": 20000.0,
+ "maintenanceMarginRate": 0.02,
+ "maxLeverage": 25.0,
+ "info": {
+ "bracket": "2",
+ "initialLeverage": "25",
+ "notionalCap": "20000",
+ "notionalFloor": "5000",
+ "maintMarginRatio": "0.02",
+ "cum": "25.0"
+ }
+ },
+ {
+ "tier": 3.0,
+ "currency": "USDT",
+ "minNotional": 20000.0,
+ "maxNotional": 25000.0,
+ "maintenanceMarginRate": 0.025,
+ "maxLeverage": 20.0,
+ "info": {
+ "bracket": "3",
+ "initialLeverage": "20",
+ "notionalCap": "25000",
+ "notionalFloor": "20000",
+ "maintMarginRatio": "0.025",
+ "cum": "125.0"
+ }
+ },
+ {
+ "tier": 4.0,
+ "currency": "USDT",
+ "minNotional": 25000.0,
+ "maxNotional": 200000.0,
+ "maintenanceMarginRate": 0.05,
+ "maxLeverage": 10.0,
+ "info": {
+ "bracket": "4",
+ "initialLeverage": "10",
+ "notionalCap": "200000",
+ "notionalFloor": "25000",
+ "maintMarginRatio": "0.05",
+ "cum": "750.0"
+ }
+ },
+ {
+ "tier": 5.0,
+ "currency": "USDT",
+ "minNotional": 200000.0,
+ "maxNotional": 400000.0,
+ "maintenanceMarginRate": 0.1,
+ "maxLeverage": 5.0,
+ "info": {
+ "bracket": "5",
+ "initialLeverage": "5",
+ "notionalCap": "400000",
+ "notionalFloor": "200000",
+ "maintMarginRatio": "0.1",
+ "cum": "10750.0"
+ }
+ },
+ {
+ "tier": 6.0,
+ "currency": "USDT",
+ "minNotional": 400000.0,
+ "maxNotional": 500000.0,
+ "maintenanceMarginRate": 0.125,
+ "maxLeverage": 4.0,
+ "info": {
+ "bracket": "6",
+ "initialLeverage": "4",
+ "notionalCap": "500000",
+ "notionalFloor": "400000",
+ "maintMarginRatio": "0.125",
+ "cum": "20750.0"
+ }
+ },
+ {
+ "tier": 7.0,
+ "currency": "USDT",
+ "minNotional": 500000.0,
+ "maxNotional": 1000000.0,
+ "maintenanceMarginRate": 0.25,
+ "maxLeverage": 2.0,
+ "info": {
+ "bracket": "7",
+ "initialLeverage": "2",
+ "notionalCap": "1000000",
+ "notionalFloor": "500000",
+ "maintMarginRatio": "0.25",
+ "cum": "83250.0"
+ }
+ },
+ {
+ "tier": 8.0,
+ "currency": "USDT",
+ "minNotional": 1000000.0,
+ "maxNotional": 1500000.0,
+ "maintenanceMarginRate": 0.5,
+ "maxLeverage": 1.0,
+ "info": {
+ "bracket": "8",
+ "initialLeverage": "1",
+ "notionalCap": "1500000",
+ "notionalFloor": "1000000",
+ "maintMarginRatio": "0.5",
+ "cum": "333250.0"
+ }
+ }
+ ],
+ "REN/USDT:USDT": [
+ {
+ "tier": 1.0,
+ "currency": "USDT",
+ "minNotional": 0.0,
+ "maxNotional": 5000.0,
+ "maintenanceMarginRate": 0.015,
+ "maxLeverage": 50.0,
+ "info": {
+ "bracket": "1",
+ "initialLeverage": "50",
+ "notionalCap": "5000",
+ "notionalFloor": "0",
+ "maintMarginRatio": "0.015",
+ "cum": "0.0"
+ }
+ },
+ {
+ "tier": 2.0,
+ "currency": "USDT",
+ "minNotional": 5000.0,
+ "maxNotional": 50000.0,
+ "maintenanceMarginRate": 0.025,
+ "maxLeverage": 20.0,
+ "info": {
+ "bracket": "2",
+ "initialLeverage": "20",
+ "notionalCap": "50000",
+ "notionalFloor": "5000",
+ "maintMarginRatio": "0.025",
+ "cum": "50.0"
+ }
+ },
+ {
+ "tier": 3.0,
+ "currency": "USDT",
+ "minNotional": 50000.0,
+ "maxNotional": 600000.0,
+ "maintenanceMarginRate": 0.05,
+ "maxLeverage": 10.0,
+ "info": {
+ "bracket": "3",
+ "initialLeverage": "10",
+ "notionalCap": "600000",
+ "notionalFloor": "50000",
+ "maintMarginRatio": "0.05",
+ "cum": "1300.0"
+ }
+ },
+ {
+ "tier": 4.0,
+ "currency": "USDT",
+ "minNotional": 600000.0,
+ "maxNotional": 1600000.0,
+ "maintenanceMarginRate": 0.1,
+ "maxLeverage": 5.0,
+ "info": {
+ "bracket": "4",
+ "initialLeverage": "5",
+ "notionalCap": "1600000",
+ "notionalFloor": "600000",
+ "maintMarginRatio": "0.1",
+ "cum": "31300.0"
+ }
+ },
+ {
+ "tier": 5.0,
+ "currency": "USDT",
+ "minNotional": 1600000.0,
+ "maxNotional": 2000000.0,
+ "maintenanceMarginRate": 0.125,
+ "maxLeverage": 4.0,
+ "info": {
+ "bracket": "5",
+ "initialLeverage": "4",
+ "notionalCap": "2000000",
+ "notionalFloor": "1600000",
+ "maintMarginRatio": "0.125",
+ "cum": "71300.0"
+ }
+ },
+ {
+ "tier": 6.0,
+ "currency": "USDT",
+ "minNotional": 2000000.0,
+ "maxNotional": 6000000.0,
+ "maintenanceMarginRate": 0.25,
+ "maxLeverage": 2.0,
+ "info": {
+ "bracket": "6",
+ "initialLeverage": "2",
+ "notionalCap": "6000000",
+ "notionalFloor": "2000000",
+ "maintMarginRatio": "0.25",
+ "cum": "321300.0"
+ }
+ },
+ {
+ "tier": 7.0,
+ "currency": "USDT",
+ "minNotional": 6000000.0,
+ "maxNotional": 10000000.0,
+ "maintenanceMarginRate": 0.5,
+ "maxLeverage": 1.0,
+ "info": {
+ "bracket": "7",
+ "initialLeverage": "1",
+ "notionalCap": "10000000",
+ "notionalFloor": "6000000",
+ "maintMarginRatio": "0.5",
+ "cum": "1821300.0"
+ }
+ }
+ ],
+ "RENDER/USDT:USDT": [
{
"tier": 1.0,
"currency": "USDT",
@@ -28655,120 +30163,6 @@
}
}
],
- "REN/USDT:USDT": [
- {
- "tier": 1.0,
- "currency": "USDT",
- "minNotional": 0.0,
- "maxNotional": 5000.0,
- "maintenanceMarginRate": 0.015,
- "maxLeverage": 50.0,
- "info": {
- "bracket": "1",
- "initialLeverage": "50",
- "notionalCap": "5000",
- "notionalFloor": "0",
- "maintMarginRatio": "0.015",
- "cum": "0.0"
- }
- },
- {
- "tier": 2.0,
- "currency": "USDT",
- "minNotional": 5000.0,
- "maxNotional": 50000.0,
- "maintenanceMarginRate": 0.025,
- "maxLeverage": 20.0,
- "info": {
- "bracket": "2",
- "initialLeverage": "20",
- "notionalCap": "50000",
- "notionalFloor": "5000",
- "maintMarginRatio": "0.025",
- "cum": "50.0"
- }
- },
- {
- "tier": 3.0,
- "currency": "USDT",
- "minNotional": 50000.0,
- "maxNotional": 600000.0,
- "maintenanceMarginRate": 0.05,
- "maxLeverage": 10.0,
- "info": {
- "bracket": "3",
- "initialLeverage": "10",
- "notionalCap": "600000",
- "notionalFloor": "50000",
- "maintMarginRatio": "0.05",
- "cum": "1300.0"
- }
- },
- {
- "tier": 4.0,
- "currency": "USDT",
- "minNotional": 600000.0,
- "maxNotional": 1600000.0,
- "maintenanceMarginRate": 0.1,
- "maxLeverage": 5.0,
- "info": {
- "bracket": "4",
- "initialLeverage": "5",
- "notionalCap": "1600000",
- "notionalFloor": "600000",
- "maintMarginRatio": "0.1",
- "cum": "31300.0"
- }
- },
- {
- "tier": 5.0,
- "currency": "USDT",
- "minNotional": 1600000.0,
- "maxNotional": 2000000.0,
- "maintenanceMarginRate": 0.125,
- "maxLeverage": 4.0,
- "info": {
- "bracket": "5",
- "initialLeverage": "4",
- "notionalCap": "2000000",
- "notionalFloor": "1600000",
- "maintMarginRatio": "0.125",
- "cum": "71300.0"
- }
- },
- {
- "tier": 6.0,
- "currency": "USDT",
- "minNotional": 2000000.0,
- "maxNotional": 6000000.0,
- "maintenanceMarginRate": 0.25,
- "maxLeverage": 2.0,
- "info": {
- "bracket": "6",
- "initialLeverage": "2",
- "notionalCap": "6000000",
- "notionalFloor": "2000000",
- "maintMarginRatio": "0.25",
- "cum": "321300.0"
- }
- },
- {
- "tier": 7.0,
- "currency": "USDT",
- "minNotional": 6000000.0,
- "maxNotional": 10000000.0,
- "maintenanceMarginRate": 0.5,
- "maxLeverage": 1.0,
- "info": {
- "bracket": "7",
- "initialLeverage": "1",
- "notionalCap": "10000000",
- "notionalFloor": "6000000",
- "maintMarginRatio": "0.5",
- "cum": "1821300.0"
- }
- }
- ],
"REZ/USDT:USDT": [
{
"tier": 1.0,
@@ -29127,136 +30521,6 @@
}
}
],
- "RNDR/USDT:USDT": [
- {
- "tier": 1.0,
- "currency": "USDT",
- "minNotional": 0.0,
- "maxNotional": 5000.0,
- "maintenanceMarginRate": 0.015,
- "maxLeverage": 26.0,
- "info": {
- "bracket": "1",
- "initialLeverage": "26",
- "notionalCap": "5000",
- "notionalFloor": "0",
- "maintMarginRatio": "0.015",
- "cum": "0.0"
- }
- },
- {
- "tier": 2.0,
- "currency": "USDT",
- "minNotional": 5000.0,
- "maxNotional": 50000.0,
- "maintenanceMarginRate": 0.02,
- "maxLeverage": 25.0,
- "info": {
- "bracket": "2",
- "initialLeverage": "25",
- "notionalCap": "50000",
- "notionalFloor": "5000",
- "maintMarginRatio": "0.02",
- "cum": "25.0"
- }
- },
- {
- "tier": 3.0,
- "currency": "USDT",
- "minNotional": 50000.0,
- "maxNotional": 100000.0,
- "maintenanceMarginRate": 0.025,
- "maxLeverage": 20.0,
- "info": {
- "bracket": "3",
- "initialLeverage": "20",
- "notionalCap": "100000",
- "notionalFloor": "50000",
- "maintMarginRatio": "0.025",
- "cum": "275.0"
- }
- },
- {
- "tier": 4.0,
- "currency": "USDT",
- "minNotional": 100000.0,
- "maxNotional": 1000000.0,
- "maintenanceMarginRate": 0.05,
- "maxLeverage": 10.0,
- "info": {
- "bracket": "4",
- "initialLeverage": "10",
- "notionalCap": "1000000",
- "notionalFloor": "100000",
- "maintMarginRatio": "0.05",
- "cum": "2775.0"
- }
- },
- {
- "tier": 5.0,
- "currency": "USDT",
- "minNotional": 1000000.0,
- "maxNotional": 2000000.0,
- "maintenanceMarginRate": 0.1,
- "maxLeverage": 5.0,
- "info": {
- "bracket": "5",
- "initialLeverage": "5",
- "notionalCap": "2000000",
- "notionalFloor": "1000000",
- "maintMarginRatio": "0.1",
- "cum": "52775.0"
- }
- },
- {
- "tier": 6.0,
- "currency": "USDT",
- "minNotional": 2000000.0,
- "maxNotional": 2500000.0,
- "maintenanceMarginRate": 0.125,
- "maxLeverage": 4.0,
- "info": {
- "bracket": "6",
- "initialLeverage": "4",
- "notionalCap": "2500000",
- "notionalFloor": "2000000",
- "maintMarginRatio": "0.125",
- "cum": "102775.0"
- }
- },
- {
- "tier": 7.0,
- "currency": "USDT",
- "minNotional": 2500000.0,
- "maxNotional": 5000000.0,
- "maintenanceMarginRate": 0.25,
- "maxLeverage": 2.0,
- "info": {
- "bracket": "7",
- "initialLeverage": "2",
- "notionalCap": "5000000",
- "notionalFloor": "2500000",
- "maintMarginRatio": "0.25",
- "cum": "415275.0"
- }
- },
- {
- "tier": 8.0,
- "currency": "USDT",
- "minNotional": 5000000.0,
- "maxNotional": 6000000.0,
- "maintenanceMarginRate": 0.5,
- "maxLeverage": 1.0,
- "info": {
- "bracket": "8",
- "initialLeverage": "1",
- "notionalCap": "6000000",
- "notionalFloor": "5000000",
- "maintMarginRatio": "0.5",
- "cum": "1665275.0"
- }
- }
- ],
"RONIN/USDT:USDT": [
{
"tier": 1.0,
@@ -31052,13 +32316,13 @@
"tier": 1.0,
"currency": "USDT",
"minNotional": 0.0,
- "maxNotional": 10000.0,
+ "maxNotional": 20000.0,
"maintenanceMarginRate": 0.005,
"maxLeverage": 100.0,
"info": {
"bracket": "1",
"initialLeverage": "100",
- "notionalCap": "10000",
+ "notionalCap": "20000",
"notionalFloor": "0",
"maintMarginRatio": "0.005",
"cum": "0.0"
@@ -31067,161 +32331,145 @@
{
"tier": 2.0,
"currency": "USDT",
- "minNotional": 10000.0,
- "maxNotional": 50000.0,
+ "minNotional": 20000.0,
+ "maxNotional": 100000.0,
"maintenanceMarginRate": 0.0065,
"maxLeverage": 75.0,
"info": {
"bracket": "2",
"initialLeverage": "75",
- "notionalCap": "50000",
- "notionalFloor": "10000",
+ "notionalCap": "100000",
+ "notionalFloor": "20000",
"maintMarginRatio": "0.0065",
- "cum": "15.0"
+ "cum": "30.0"
}
},
{
"tier": 3.0,
"currency": "USDT",
- "minNotional": 50000.0,
- "maxNotional": 200000.0,
+ "minNotional": 100000.0,
+ "maxNotional": 800000.0,
"maintenanceMarginRate": 0.01,
"maxLeverage": 50.0,
"info": {
"bracket": "3",
"initialLeverage": "50",
- "notionalCap": "200000",
- "notionalFloor": "50000",
+ "notionalCap": "800000",
+ "notionalFloor": "100000",
"maintMarginRatio": "0.01",
- "cum": "190.0"
+ "cum": "380.0"
}
},
{
"tier": 4.0,
"currency": "USDT",
- "minNotional": 200000.0,
- "maxNotional": 500000.0,
- "maintenanceMarginRate": 0.012,
- "maxLeverage": 40.0,
+ "minNotional": 800000.0,
+ "maxNotional": 4000000.0,
+ "maintenanceMarginRate": 0.02,
+ "maxLeverage": 25.0,
"info": {
"bracket": "4",
- "initialLeverage": "40",
- "notionalCap": "500000",
- "notionalFloor": "200000",
- "maintMarginRatio": "0.012",
- "cum": "590.0"
+ "initialLeverage": "25",
+ "notionalCap": "4000000",
+ "notionalFloor": "800000",
+ "maintMarginRatio": "0.02",
+ "cum": "8380.0"
}
},
{
"tier": 5.0,
"currency": "USDT",
- "minNotional": 500000.0,
- "maxNotional": 1000000.0,
- "maintenanceMarginRate": 0.02,
- "maxLeverage": 25.0,
+ "minNotional": 4000000.0,
+ "maxNotional": 8000000.0,
+ "maintenanceMarginRate": 0.025,
+ "maxLeverage": 20.0,
"info": {
"bracket": "5",
- "initialLeverage": "25",
- "notionalCap": "1000000",
- "notionalFloor": "500000",
- "maintMarginRatio": "0.02",
- "cum": "4590.0"
+ "initialLeverage": "20",
+ "notionalCap": "8000000",
+ "notionalFloor": "4000000",
+ "maintMarginRatio": "0.025",
+ "cum": "28380.0"
}
},
{
"tier": 6.0,
"currency": "USDT",
- "minNotional": 1000000.0,
- "maxNotional": 2000000.0,
- "maintenanceMarginRate": 0.025,
- "maxLeverage": 20.0,
+ "minNotional": 8000000.0,
+ "maxNotional": 40000000.0,
+ "maintenanceMarginRate": 0.05,
+ "maxLeverage": 10.0,
"info": {
"bracket": "6",
- "initialLeverage": "20",
- "notionalCap": "2000000",
- "notionalFloor": "1000000",
- "maintMarginRatio": "0.025",
- "cum": "9590.0"
+ "initialLeverage": "10",
+ "notionalCap": "40000000",
+ "notionalFloor": "8000000",
+ "maintMarginRatio": "0.05",
+ "cum": "228380.0"
}
},
{
"tier": 7.0,
"currency": "USDT",
- "minNotional": 2000000.0,
- "maxNotional": 5000000.0,
- "maintenanceMarginRate": 0.05,
- "maxLeverage": 10.0,
+ "minNotional": 40000000.0,
+ "maxNotional": 80000000.0,
+ "maintenanceMarginRate": 0.1,
+ "maxLeverage": 5.0,
"info": {
"bracket": "7",
- "initialLeverage": "10",
- "notionalCap": "5000000",
- "notionalFloor": "2000000",
- "maintMarginRatio": "0.05",
- "cum": "59590.0"
+ "initialLeverage": "5",
+ "notionalCap": "80000000",
+ "notionalFloor": "40000000",
+ "maintMarginRatio": "0.1",
+ "cum": "2228380.0"
}
},
{
"tier": 8.0,
"currency": "USDT",
- "minNotional": 5000000.0,
- "maxNotional": 15000000.0,
- "maintenanceMarginRate": 0.1,
- "maxLeverage": 5.0,
+ "minNotional": 80000000.0,
+ "maxNotional": 100000000.0,
+ "maintenanceMarginRate": 0.125,
+ "maxLeverage": 4.0,
"info": {
"bracket": "8",
- "initialLeverage": "5",
- "notionalCap": "15000000",
- "notionalFloor": "5000000",
- "maintMarginRatio": "0.1",
- "cum": "309590.0"
+ "initialLeverage": "4",
+ "notionalCap": "100000000",
+ "notionalFloor": "80000000",
+ "maintMarginRatio": "0.125",
+ "cum": "4228380.0"
}
},
{
"tier": 9.0,
"currency": "USDT",
- "minNotional": 15000000.0,
- "maxNotional": 20000000.0,
- "maintenanceMarginRate": 0.125,
- "maxLeverage": 4.0,
+ "minNotional": 100000000.0,
+ "maxNotional": 200000000.0,
+ "maintenanceMarginRate": 0.25,
+ "maxLeverage": 2.0,
"info": {
"bracket": "9",
- "initialLeverage": "4",
- "notionalCap": "20000000",
- "notionalFloor": "15000000",
- "maintMarginRatio": "0.125",
- "cum": "684590.0"
+ "initialLeverage": "2",
+ "notionalCap": "200000000",
+ "notionalFloor": "100000000",
+ "maintMarginRatio": "0.25",
+ "cum": "16728380.0"
}
},
{
"tier": 10.0,
"currency": "USDT",
- "minNotional": 20000000.0,
- "maxNotional": 50000000.0,
- "maintenanceMarginRate": 0.25,
- "maxLeverage": 2.0,
- "info": {
- "bracket": "10",
- "initialLeverage": "2",
- "notionalCap": "50000000",
- "notionalFloor": "20000000",
- "maintMarginRatio": "0.25",
- "cum": "3184590.0"
- }
- },
- {
- "tier": 11.0,
- "currency": "USDT",
- "minNotional": 50000000.0,
- "maxNotional": 100000000.0,
+ "minNotional": 200000000.0,
+ "maxNotional": 400000000.0,
"maintenanceMarginRate": 0.5,
"maxLeverage": 1.0,
"info": {
- "bracket": "11",
+ "bracket": "10",
"initialLeverage": "1",
- "notionalCap": "100000000",
- "notionalFloor": "50000000",
+ "notionalCap": "400000000",
+ "notionalFloor": "200000000",
"maintMarginRatio": "0.5",
- "cum": "15684590.0"
+ "cum": "66728380.0"
}
}
],
@@ -32625,6 +33873,152 @@
}
}
],
+ "SUN/USDT:USDT": [
+ {
+ "tier": 1.0,
+ "currency": "USDT",
+ "minNotional": 0.0,
+ "maxNotional": 5000.0,
+ "maintenanceMarginRate": 0.01,
+ "maxLeverage": 75.0,
+ "info": {
+ "bracket": "1",
+ "initialLeverage": "75",
+ "notionalCap": "5000",
+ "notionalFloor": "0",
+ "maintMarginRatio": "0.01",
+ "cum": "0.0"
+ }
+ },
+ {
+ "tier": 2.0,
+ "currency": "USDT",
+ "minNotional": 5000.0,
+ "maxNotional": 10000.0,
+ "maintenanceMarginRate": 0.015,
+ "maxLeverage": 50.0,
+ "info": {
+ "bracket": "2",
+ "initialLeverage": "50",
+ "notionalCap": "10000",
+ "notionalFloor": "5000",
+ "maintMarginRatio": "0.015",
+ "cum": "25.0"
+ }
+ },
+ {
+ "tier": 3.0,
+ "currency": "USDT",
+ "minNotional": 10000.0,
+ "maxNotional": 20000.0,
+ "maintenanceMarginRate": 0.02,
+ "maxLeverage": 25.0,
+ "info": {
+ "bracket": "3",
+ "initialLeverage": "25",
+ "notionalCap": "20000",
+ "notionalFloor": "10000",
+ "maintMarginRatio": "0.02",
+ "cum": "75.0"
+ }
+ },
+ {
+ "tier": 4.0,
+ "currency": "USDT",
+ "minNotional": 20000.0,
+ "maxNotional": 40000.0,
+ "maintenanceMarginRate": 0.025,
+ "maxLeverage": 20.0,
+ "info": {
+ "bracket": "4",
+ "initialLeverage": "20",
+ "notionalCap": "40000",
+ "notionalFloor": "20000",
+ "maintMarginRatio": "0.025",
+ "cum": "175.0"
+ }
+ },
+ {
+ "tier": 5.0,
+ "currency": "USDT",
+ "minNotional": 40000.0,
+ "maxNotional": 200000.0,
+ "maintenanceMarginRate": 0.05,
+ "maxLeverage": 10.0,
+ "info": {
+ "bracket": "5",
+ "initialLeverage": "10",
+ "notionalCap": "200000",
+ "notionalFloor": "40000",
+ "maintMarginRatio": "0.05",
+ "cum": "1175.0"
+ }
+ },
+ {
+ "tier": 6.0,
+ "currency": "USDT",
+ "minNotional": 200000.0,
+ "maxNotional": 400000.0,
+ "maintenanceMarginRate": 0.1,
+ "maxLeverage": 5.0,
+ "info": {
+ "bracket": "6",
+ "initialLeverage": "5",
+ "notionalCap": "400000",
+ "notionalFloor": "200000",
+ "maintMarginRatio": "0.1",
+ "cum": "11175.0"
+ }
+ },
+ {
+ "tier": 7.0,
+ "currency": "USDT",
+ "minNotional": 400000.0,
+ "maxNotional": 500000.0,
+ "maintenanceMarginRate": 0.125,
+ "maxLeverage": 4.0,
+ "info": {
+ "bracket": "7",
+ "initialLeverage": "4",
+ "notionalCap": "500000",
+ "notionalFloor": "400000",
+ "maintMarginRatio": "0.125",
+ "cum": "21175.0"
+ }
+ },
+ {
+ "tier": 8.0,
+ "currency": "USDT",
+ "minNotional": 500000.0,
+ "maxNotional": 1000000.0,
+ "maintenanceMarginRate": 0.25,
+ "maxLeverage": 2.0,
+ "info": {
+ "bracket": "8",
+ "initialLeverage": "2",
+ "notionalCap": "1000000",
+ "notionalFloor": "500000",
+ "maintMarginRatio": "0.25",
+ "cum": "83675.0"
+ }
+ },
+ {
+ "tier": 9.0,
+ "currency": "USDT",
+ "minNotional": 1000000.0,
+ "maxNotional": 2000000.0,
+ "maintenanceMarginRate": 0.5,
+ "maxLeverage": 1.0,
+ "info": {
+ "bracket": "9",
+ "initialLeverage": "1",
+ "notionalCap": "2000000",
+ "notionalFloor": "1000000",
+ "maintMarginRatio": "0.5",
+ "cum": "333675.0"
+ }
+ }
+ ],
"SUPER/USDT:USDT": [
{
"tier": 1.0,
@@ -32967,6 +34361,266 @@
}
}
],
+ "SYN/USDT:USDT": [
+ {
+ "tier": 1.0,
+ "currency": "USDT",
+ "minNotional": 0.0,
+ "maxNotional": 5000.0,
+ "maintenanceMarginRate": 0.015,
+ "maxLeverage": 50.0,
+ "info": {
+ "bracket": "1",
+ "initialLeverage": "50",
+ "notionalCap": "5000",
+ "notionalFloor": "0",
+ "maintMarginRatio": "0.015",
+ "cum": "0.0"
+ }
+ },
+ {
+ "tier": 2.0,
+ "currency": "USDT",
+ "minNotional": 5000.0,
+ "maxNotional": 20000.0,
+ "maintenanceMarginRate": 0.02,
+ "maxLeverage": 25.0,
+ "info": {
+ "bracket": "2",
+ "initialLeverage": "25",
+ "notionalCap": "20000",
+ "notionalFloor": "5000",
+ "maintMarginRatio": "0.02",
+ "cum": "25.0"
+ }
+ },
+ {
+ "tier": 3.0,
+ "currency": "USDT",
+ "minNotional": 20000.0,
+ "maxNotional": 25000.0,
+ "maintenanceMarginRate": 0.025,
+ "maxLeverage": 20.0,
+ "info": {
+ "bracket": "3",
+ "initialLeverage": "20",
+ "notionalCap": "25000",
+ "notionalFloor": "20000",
+ "maintMarginRatio": "0.025",
+ "cum": "125.0"
+ }
+ },
+ {
+ "tier": 4.0,
+ "currency": "USDT",
+ "minNotional": 25000.0,
+ "maxNotional": 200000.0,
+ "maintenanceMarginRate": 0.05,
+ "maxLeverage": 10.0,
+ "info": {
+ "bracket": "4",
+ "initialLeverage": "10",
+ "notionalCap": "200000",
+ "notionalFloor": "25000",
+ "maintMarginRatio": "0.05",
+ "cum": "750.0"
+ }
+ },
+ {
+ "tier": 5.0,
+ "currency": "USDT",
+ "minNotional": 200000.0,
+ "maxNotional": 400000.0,
+ "maintenanceMarginRate": 0.1,
+ "maxLeverage": 5.0,
+ "info": {
+ "bracket": "5",
+ "initialLeverage": "5",
+ "notionalCap": "400000",
+ "notionalFloor": "200000",
+ "maintMarginRatio": "0.1",
+ "cum": "10750.0"
+ }
+ },
+ {
+ "tier": 6.0,
+ "currency": "USDT",
+ "minNotional": 400000.0,
+ "maxNotional": 500000.0,
+ "maintenanceMarginRate": 0.125,
+ "maxLeverage": 4.0,
+ "info": {
+ "bracket": "6",
+ "initialLeverage": "4",
+ "notionalCap": "500000",
+ "notionalFloor": "400000",
+ "maintMarginRatio": "0.125",
+ "cum": "20750.0"
+ }
+ },
+ {
+ "tier": 7.0,
+ "currency": "USDT",
+ "minNotional": 500000.0,
+ "maxNotional": 1000000.0,
+ "maintenanceMarginRate": 0.25,
+ "maxLeverage": 2.0,
+ "info": {
+ "bracket": "7",
+ "initialLeverage": "2",
+ "notionalCap": "1000000",
+ "notionalFloor": "500000",
+ "maintMarginRatio": "0.25",
+ "cum": "83250.0"
+ }
+ },
+ {
+ "tier": 8.0,
+ "currency": "USDT",
+ "minNotional": 1000000.0,
+ "maxNotional": 2000000.0,
+ "maintenanceMarginRate": 0.5,
+ "maxLeverage": 1.0,
+ "info": {
+ "bracket": "8",
+ "initialLeverage": "1",
+ "notionalCap": "2000000",
+ "notionalFloor": "1000000",
+ "maintMarginRatio": "0.5",
+ "cum": "333250.0"
+ }
+ }
+ ],
+ "SYS/USDT:USDT": [
+ {
+ "tier": 1.0,
+ "currency": "USDT",
+ "minNotional": 0.0,
+ "maxNotional": 5000.0,
+ "maintenanceMarginRate": 0.015,
+ "maxLeverage": 50.0,
+ "info": {
+ "bracket": "1",
+ "initialLeverage": "50",
+ "notionalCap": "5000",
+ "notionalFloor": "0",
+ "maintMarginRatio": "0.015",
+ "cum": "0.0"
+ }
+ },
+ {
+ "tier": 2.0,
+ "currency": "USDT",
+ "minNotional": 5000.0,
+ "maxNotional": 20000.0,
+ "maintenanceMarginRate": 0.02,
+ "maxLeverage": 25.0,
+ "info": {
+ "bracket": "2",
+ "initialLeverage": "25",
+ "notionalCap": "20000",
+ "notionalFloor": "5000",
+ "maintMarginRatio": "0.02",
+ "cum": "25.0"
+ }
+ },
+ {
+ "tier": 3.0,
+ "currency": "USDT",
+ "minNotional": 20000.0,
+ "maxNotional": 25000.0,
+ "maintenanceMarginRate": 0.025,
+ "maxLeverage": 20.0,
+ "info": {
+ "bracket": "3",
+ "initialLeverage": "20",
+ "notionalCap": "25000",
+ "notionalFloor": "20000",
+ "maintMarginRatio": "0.025",
+ "cum": "125.0"
+ }
+ },
+ {
+ "tier": 4.0,
+ "currency": "USDT",
+ "minNotional": 25000.0,
+ "maxNotional": 200000.0,
+ "maintenanceMarginRate": 0.05,
+ "maxLeverage": 10.0,
+ "info": {
+ "bracket": "4",
+ "initialLeverage": "10",
+ "notionalCap": "200000",
+ "notionalFloor": "25000",
+ "maintMarginRatio": "0.05",
+ "cum": "750.0"
+ }
+ },
+ {
+ "tier": 5.0,
+ "currency": "USDT",
+ "minNotional": 200000.0,
+ "maxNotional": 400000.0,
+ "maintenanceMarginRate": 0.1,
+ "maxLeverage": 5.0,
+ "info": {
+ "bracket": "5",
+ "initialLeverage": "5",
+ "notionalCap": "400000",
+ "notionalFloor": "200000",
+ "maintMarginRatio": "0.1",
+ "cum": "10750.0"
+ }
+ },
+ {
+ "tier": 6.0,
+ "currency": "USDT",
+ "minNotional": 400000.0,
+ "maxNotional": 500000.0,
+ "maintenanceMarginRate": 0.125,
+ "maxLeverage": 4.0,
+ "info": {
+ "bracket": "6",
+ "initialLeverage": "4",
+ "notionalCap": "500000",
+ "notionalFloor": "400000",
+ "maintMarginRatio": "0.125",
+ "cum": "20750.0"
+ }
+ },
+ {
+ "tier": 7.0,
+ "currency": "USDT",
+ "minNotional": 500000.0,
+ "maxNotional": 1000000.0,
+ "maintenanceMarginRate": 0.25,
+ "maxLeverage": 2.0,
+ "info": {
+ "bracket": "7",
+ "initialLeverage": "2",
+ "notionalCap": "1000000",
+ "notionalFloor": "500000",
+ "maintMarginRatio": "0.25",
+ "cum": "83250.0"
+ }
+ },
+ {
+ "tier": 8.0,
+ "currency": "USDT",
+ "minNotional": 1000000.0,
+ "maxNotional": 2000000.0,
+ "maintenanceMarginRate": 0.5,
+ "maxLeverage": 1.0,
+ "info": {
+ "bracket": "8",
+ "initialLeverage": "1",
+ "notionalCap": "2000000",
+ "notionalFloor": "1000000",
+ "maintMarginRatio": "0.5",
+ "cum": "333250.0"
+ }
+ }
+ ],
"T/USDT:USDT": [
{
"tier": 1.0,
@@ -33118,96 +34772,112 @@
"tier": 2.0,
"currency": "USDT",
"minNotional": 5000.0,
- "maxNotional": 25000.0,
- "maintenanceMarginRate": 0.025,
- "maxLeverage": 20.0,
+ "maxNotional": 20000.0,
+ "maintenanceMarginRate": 0.02,
+ "maxLeverage": 25.0,
"info": {
"bracket": "2",
- "initialLeverage": "20",
- "notionalCap": "25000",
+ "initialLeverage": "25",
+ "notionalCap": "20000",
"notionalFloor": "5000",
- "maintMarginRatio": "0.025",
- "cum": "50.0"
+ "maintMarginRatio": "0.02",
+ "cum": "25.0"
}
},
{
"tier": 3.0,
"currency": "USDT",
- "minNotional": 25000.0,
- "maxNotional": 100000.0,
- "maintenanceMarginRate": 0.05,
- "maxLeverage": 10.0,
+ "minNotional": 20000.0,
+ "maxNotional": 30000.0,
+ "maintenanceMarginRate": 0.025,
+ "maxLeverage": 20.0,
"info": {
"bracket": "3",
- "initialLeverage": "10",
- "notionalCap": "100000",
- "notionalFloor": "25000",
- "maintMarginRatio": "0.05",
- "cum": "675.0"
+ "initialLeverage": "20",
+ "notionalCap": "30000",
+ "notionalFloor": "20000",
+ "maintMarginRatio": "0.025",
+ "cum": "125.0"
}
},
{
"tier": 4.0,
"currency": "USDT",
- "minNotional": 100000.0,
- "maxNotional": 200000.0,
- "maintenanceMarginRate": 0.1,
- "maxLeverage": 5.0,
+ "minNotional": 30000.0,
+ "maxNotional": 300000.0,
+ "maintenanceMarginRate": 0.05,
+ "maxLeverage": 10.0,
"info": {
"bracket": "4",
- "initialLeverage": "5",
- "notionalCap": "200000",
- "notionalFloor": "100000",
- "maintMarginRatio": "0.1",
- "cum": "5675.0"
+ "initialLeverage": "10",
+ "notionalCap": "300000",
+ "notionalFloor": "30000",
+ "maintMarginRatio": "0.05",
+ "cum": "875.0"
}
},
{
"tier": 5.0,
"currency": "USDT",
- "minNotional": 200000.0,
- "maxNotional": 500000.0,
- "maintenanceMarginRate": 0.125,
- "maxLeverage": 4.0,
+ "minNotional": 300000.0,
+ "maxNotional": 600000.0,
+ "maintenanceMarginRate": 0.1,
+ "maxLeverage": 5.0,
"info": {
"bracket": "5",
- "initialLeverage": "4",
- "notionalCap": "500000",
- "notionalFloor": "200000",
- "maintMarginRatio": "0.125",
- "cum": "10675.0"
+ "initialLeverage": "5",
+ "notionalCap": "600000",
+ "notionalFloor": "300000",
+ "maintMarginRatio": "0.1",
+ "cum": "15875.0"
}
},
{
"tier": 6.0,
"currency": "USDT",
- "minNotional": 500000.0,
- "maxNotional": 1000000.0,
- "maintenanceMarginRate": 0.25,
- "maxLeverage": 2.0,
+ "minNotional": 600000.0,
+ "maxNotional": 750000.0,
+ "maintenanceMarginRate": 0.125,
+ "maxLeverage": 4.0,
"info": {
"bracket": "6",
- "initialLeverage": "2",
- "notionalCap": "1000000",
- "notionalFloor": "500000",
- "maintMarginRatio": "0.25",
- "cum": "73175.0"
+ "initialLeverage": "4",
+ "notionalCap": "750000",
+ "notionalFloor": "600000",
+ "maintMarginRatio": "0.125",
+ "cum": "30875.0"
}
},
{
"tier": 7.0,
"currency": "USDT",
- "minNotional": 1000000.0,
- "maxNotional": 2000000.0,
+ "minNotional": 750000.0,
+ "maxNotional": 1500000.0,
+ "maintenanceMarginRate": 0.25,
+ "maxLeverage": 2.0,
+ "info": {
+ "bracket": "7",
+ "initialLeverage": "2",
+ "notionalCap": "1500000",
+ "notionalFloor": "750000",
+ "maintMarginRatio": "0.25",
+ "cum": "124625.0"
+ }
+ },
+ {
+ "tier": 8.0,
+ "currency": "USDT",
+ "minNotional": 1500000.0,
+ "maxNotional": 3000000.0,
"maintenanceMarginRate": 0.5,
"maxLeverage": 1.0,
"info": {
- "bracket": "7",
+ "bracket": "8",
"initialLeverage": "1",
- "notionalCap": "2000000",
- "notionalFloor": "1000000",
+ "notionalCap": "3000000",
+ "notionalFloor": "1500000",
"maintMarginRatio": "0.5",
- "cum": "323175.0"
+ "cum": "499625.0"
}
}
],
@@ -35569,6 +37239,282 @@
}
}
],
+ "VIDT/USDT:USDT": [
+ {
+ "tier": 1.0,
+ "currency": "USDT",
+ "minNotional": 0.0,
+ "maxNotional": 5000.0,
+ "maintenanceMarginRate": 0.01,
+ "maxLeverage": 75.0,
+ "info": {
+ "bracket": "1",
+ "initialLeverage": "75",
+ "notionalCap": "5000",
+ "notionalFloor": "0",
+ "maintMarginRatio": "0.01",
+ "cum": "0.0"
+ }
+ },
+ {
+ "tier": 2.0,
+ "currency": "USDT",
+ "minNotional": 5000.0,
+ "maxNotional": 10000.0,
+ "maintenanceMarginRate": 0.015,
+ "maxLeverage": 50.0,
+ "info": {
+ "bracket": "2",
+ "initialLeverage": "50",
+ "notionalCap": "10000",
+ "notionalFloor": "5000",
+ "maintMarginRatio": "0.015",
+ "cum": "25.0"
+ }
+ },
+ {
+ "tier": 3.0,
+ "currency": "USDT",
+ "minNotional": 10000.0,
+ "maxNotional": 20000.0,
+ "maintenanceMarginRate": 0.02,
+ "maxLeverage": 25.0,
+ "info": {
+ "bracket": "3",
+ "initialLeverage": "25",
+ "notionalCap": "20000",
+ "notionalFloor": "10000",
+ "maintMarginRatio": "0.02",
+ "cum": "75.0"
+ }
+ },
+ {
+ "tier": 4.0,
+ "currency": "USDT",
+ "minNotional": 20000.0,
+ "maxNotional": 40000.0,
+ "maintenanceMarginRate": 0.025,
+ "maxLeverage": 20.0,
+ "info": {
+ "bracket": "4",
+ "initialLeverage": "20",
+ "notionalCap": "40000",
+ "notionalFloor": "20000",
+ "maintMarginRatio": "0.025",
+ "cum": "175.0"
+ }
+ },
+ {
+ "tier": 5.0,
+ "currency": "USDT",
+ "minNotional": 40000.0,
+ "maxNotional": 200000.0,
+ "maintenanceMarginRate": 0.05,
+ "maxLeverage": 10.0,
+ "info": {
+ "bracket": "5",
+ "initialLeverage": "10",
+ "notionalCap": "200000",
+ "notionalFloor": "40000",
+ "maintMarginRatio": "0.05",
+ "cum": "1175.0"
+ }
+ },
+ {
+ "tier": 6.0,
+ "currency": "USDT",
+ "minNotional": 200000.0,
+ "maxNotional": 400000.0,
+ "maintenanceMarginRate": 0.1,
+ "maxLeverage": 5.0,
+ "info": {
+ "bracket": "6",
+ "initialLeverage": "5",
+ "notionalCap": "400000",
+ "notionalFloor": "200000",
+ "maintMarginRatio": "0.1",
+ "cum": "11175.0"
+ }
+ },
+ {
+ "tier": 7.0,
+ "currency": "USDT",
+ "minNotional": 400000.0,
+ "maxNotional": 500000.0,
+ "maintenanceMarginRate": 0.125,
+ "maxLeverage": 4.0,
+ "info": {
+ "bracket": "7",
+ "initialLeverage": "4",
+ "notionalCap": "500000",
+ "notionalFloor": "400000",
+ "maintMarginRatio": "0.125",
+ "cum": "21175.0"
+ }
+ },
+ {
+ "tier": 8.0,
+ "currency": "USDT",
+ "minNotional": 500000.0,
+ "maxNotional": 1000000.0,
+ "maintenanceMarginRate": 0.25,
+ "maxLeverage": 2.0,
+ "info": {
+ "bracket": "8",
+ "initialLeverage": "2",
+ "notionalCap": "1000000",
+ "notionalFloor": "500000",
+ "maintMarginRatio": "0.25",
+ "cum": "83675.0"
+ }
+ },
+ {
+ "tier": 9.0,
+ "currency": "USDT",
+ "minNotional": 1000000.0,
+ "maxNotional": 2000000.0,
+ "maintenanceMarginRate": 0.5,
+ "maxLeverage": 1.0,
+ "info": {
+ "bracket": "9",
+ "initialLeverage": "1",
+ "notionalCap": "2000000",
+ "notionalFloor": "1000000",
+ "maintMarginRatio": "0.5",
+ "cum": "333675.0"
+ }
+ }
+ ],
+ "VOXEL/USDT:USDT": [
+ {
+ "tier": 1.0,
+ "currency": "USDT",
+ "minNotional": 0.0,
+ "maxNotional": 5000.0,
+ "maintenanceMarginRate": 0.015,
+ "maxLeverage": 50.0,
+ "info": {
+ "bracket": "1",
+ "initialLeverage": "50",
+ "notionalCap": "5000",
+ "notionalFloor": "0",
+ "maintMarginRatio": "0.015",
+ "cum": "0.0"
+ }
+ },
+ {
+ "tier": 2.0,
+ "currency": "USDT",
+ "minNotional": 5000.0,
+ "maxNotional": 20000.0,
+ "maintenanceMarginRate": 0.02,
+ "maxLeverage": 25.0,
+ "info": {
+ "bracket": "2",
+ "initialLeverage": "25",
+ "notionalCap": "20000",
+ "notionalFloor": "5000",
+ "maintMarginRatio": "0.02",
+ "cum": "25.0"
+ }
+ },
+ {
+ "tier": 3.0,
+ "currency": "USDT",
+ "minNotional": 20000.0,
+ "maxNotional": 25000.0,
+ "maintenanceMarginRate": 0.025,
+ "maxLeverage": 20.0,
+ "info": {
+ "bracket": "3",
+ "initialLeverage": "20",
+ "notionalCap": "25000",
+ "notionalFloor": "20000",
+ "maintMarginRatio": "0.025",
+ "cum": "125.0"
+ }
+ },
+ {
+ "tier": 4.0,
+ "currency": "USDT",
+ "minNotional": 25000.0,
+ "maxNotional": 200000.0,
+ "maintenanceMarginRate": 0.05,
+ "maxLeverage": 10.0,
+ "info": {
+ "bracket": "4",
+ "initialLeverage": "10",
+ "notionalCap": "200000",
+ "notionalFloor": "25000",
+ "maintMarginRatio": "0.05",
+ "cum": "750.0"
+ }
+ },
+ {
+ "tier": 5.0,
+ "currency": "USDT",
+ "minNotional": 200000.0,
+ "maxNotional": 400000.0,
+ "maintenanceMarginRate": 0.1,
+ "maxLeverage": 5.0,
+ "info": {
+ "bracket": "5",
+ "initialLeverage": "5",
+ "notionalCap": "400000",
+ "notionalFloor": "200000",
+ "maintMarginRatio": "0.1",
+ "cum": "10750.0"
+ }
+ },
+ {
+ "tier": 6.0,
+ "currency": "USDT",
+ "minNotional": 400000.0,
+ "maxNotional": 500000.0,
+ "maintenanceMarginRate": 0.125,
+ "maxLeverage": 4.0,
+ "info": {
+ "bracket": "6",
+ "initialLeverage": "4",
+ "notionalCap": "500000",
+ "notionalFloor": "400000",
+ "maintMarginRatio": "0.125",
+ "cum": "20750.0"
+ }
+ },
+ {
+ "tier": 7.0,
+ "currency": "USDT",
+ "minNotional": 500000.0,
+ "maxNotional": 1000000.0,
+ "maintenanceMarginRate": 0.25,
+ "maxLeverage": 2.0,
+ "info": {
+ "bracket": "7",
+ "initialLeverage": "2",
+ "notionalCap": "1000000",
+ "notionalFloor": "500000",
+ "maintMarginRatio": "0.25",
+ "cum": "83250.0"
+ }
+ },
+ {
+ "tier": 8.0,
+ "currency": "USDT",
+ "minNotional": 1000000.0,
+ "maxNotional": 2000000.0,
+ "maintenanceMarginRate": 0.5,
+ "maxLeverage": 1.0,
+ "info": {
+ "bracket": "8",
+ "initialLeverage": "1",
+ "notionalCap": "2000000",
+ "notionalFloor": "1000000",
+ "maintMarginRatio": "0.5",
+ "cum": "333250.0"
+ }
+ }
+ ],
"W/USDT:USDT": [
{
"tier": 1.0,
@@ -36062,128 +38008,144 @@
"tier": 1.0,
"currency": "USDT",
"minNotional": 0.0,
- "maxNotional": 5000.0,
- "maintenanceMarginRate": 0.015,
- "maxLeverage": 50.0,
+ "maxNotional": 10000.0,
+ "maintenanceMarginRate": 0.01,
+ "maxLeverage": 75.0,
"info": {
"bracket": "1",
- "initialLeverage": "50",
- "notionalCap": "5000",
+ "initialLeverage": "75",
+ "notionalCap": "10000",
"notionalFloor": "0",
- "maintMarginRatio": "0.015",
+ "maintMarginRatio": "0.01",
"cum": "0.0"
}
},
{
"tier": 2.0,
"currency": "USDT",
- "minNotional": 5000.0,
- "maxNotional": 50000.0,
- "maintenanceMarginRate": 0.02,
- "maxLeverage": 25.0,
+ "minNotional": 10000.0,
+ "maxNotional": 100000.0,
+ "maintenanceMarginRate": 0.015,
+ "maxLeverage": 50.0,
"info": {
"bracket": "2",
- "initialLeverage": "25",
- "notionalCap": "50000",
- "notionalFloor": "5000",
- "maintMarginRatio": "0.02",
- "cum": "25.0"
+ "initialLeverage": "50",
+ "notionalCap": "100000",
+ "notionalFloor": "10000",
+ "maintMarginRatio": "0.015",
+ "cum": "50.0"
}
},
{
"tier": 3.0,
"currency": "USDT",
- "minNotional": 50000.0,
- "maxNotional": 200000.0,
- "maintenanceMarginRate": 0.025,
- "maxLeverage": 20.0,
+ "minNotional": 100000.0,
+ "maxNotional": 500000.0,
+ "maintenanceMarginRate": 0.02,
+ "maxLeverage": 25.0,
"info": {
"bracket": "3",
- "initialLeverage": "20",
- "notionalCap": "200000",
- "notionalFloor": "50000",
- "maintMarginRatio": "0.025",
- "cum": "275.0"
+ "initialLeverage": "25",
+ "notionalCap": "500000",
+ "notionalFloor": "100000",
+ "maintMarginRatio": "0.02",
+ "cum": "550.0"
}
},
{
"tier": 4.0,
"currency": "USDT",
- "minNotional": 200000.0,
- "maxNotional": 2000000.0,
- "maintenanceMarginRate": 0.05,
- "maxLeverage": 10.0,
+ "minNotional": 500000.0,
+ "maxNotional": 1000000.0,
+ "maintenanceMarginRate": 0.025,
+ "maxLeverage": 20.0,
"info": {
"bracket": "4",
- "initialLeverage": "10",
- "notionalCap": "2000000",
- "notionalFloor": "200000",
- "maintMarginRatio": "0.05",
- "cum": "5275.0"
+ "initialLeverage": "20",
+ "notionalCap": "1000000",
+ "notionalFloor": "500000",
+ "maintMarginRatio": "0.025",
+ "cum": "3050.0"
}
},
{
"tier": 5.0,
"currency": "USDT",
- "minNotional": 2000000.0,
- "maxNotional": 4000000.0,
- "maintenanceMarginRate": 0.1,
- "maxLeverage": 5.0,
+ "minNotional": 1000000.0,
+ "maxNotional": 5000000.0,
+ "maintenanceMarginRate": 0.05,
+ "maxLeverage": 10.0,
"info": {
"bracket": "5",
- "initialLeverage": "5",
- "notionalCap": "4000000",
- "notionalFloor": "2000000",
- "maintMarginRatio": "0.1",
- "cum": "105275.0"
+ "initialLeverage": "10",
+ "notionalCap": "5000000",
+ "notionalFloor": "1000000",
+ "maintMarginRatio": "0.05",
+ "cum": "28050.0"
}
},
{
"tier": 6.0,
"currency": "USDT",
- "minNotional": 4000000.0,
- "maxNotional": 5000000.0,
- "maintenanceMarginRate": 0.125,
- "maxLeverage": 4.0,
+ "minNotional": 5000000.0,
+ "maxNotional": 10000000.0,
+ "maintenanceMarginRate": 0.1,
+ "maxLeverage": 5.0,
"info": {
"bracket": "6",
- "initialLeverage": "4",
- "notionalCap": "5000000",
- "notionalFloor": "4000000",
- "maintMarginRatio": "0.125",
- "cum": "205275.0"
+ "initialLeverage": "5",
+ "notionalCap": "10000000",
+ "notionalFloor": "5000000",
+ "maintMarginRatio": "0.1",
+ "cum": "278050.0"
}
},
{
"tier": 7.0,
"currency": "USDT",
- "minNotional": 5000000.0,
- "maxNotional": 10000000.0,
- "maintenanceMarginRate": 0.25,
- "maxLeverage": 2.0,
+ "minNotional": 10000000.0,
+ "maxNotional": 12500000.0,
+ "maintenanceMarginRate": 0.125,
+ "maxLeverage": 4.0,
"info": {
"bracket": "7",
- "initialLeverage": "2",
- "notionalCap": "10000000",
- "notionalFloor": "5000000",
- "maintMarginRatio": "0.25",
- "cum": "830275.0"
+ "initialLeverage": "4",
+ "notionalCap": "12500000",
+ "notionalFloor": "10000000",
+ "maintMarginRatio": "0.125",
+ "cum": "528050.0"
}
},
{
"tier": 8.0,
"currency": "USDT",
- "minNotional": 10000000.0,
- "maxNotional": 20000000.0,
+ "minNotional": 12500000.0,
+ "maxNotional": 25000000.0,
+ "maintenanceMarginRate": 0.25,
+ "maxLeverage": 2.0,
+ "info": {
+ "bracket": "8",
+ "initialLeverage": "2",
+ "notionalCap": "25000000",
+ "notionalFloor": "12500000",
+ "maintMarginRatio": "0.25",
+ "cum": "2090550.0"
+ }
+ },
+ {
+ "tier": 9.0,
+ "currency": "USDT",
+ "minNotional": 25000000.0,
+ "maxNotional": 50000000.0,
"maintenanceMarginRate": 0.5,
"maxLeverage": 1.0,
"info": {
- "bracket": "8",
+ "bracket": "9",
"initialLeverage": "1",
- "notionalCap": "20000000",
- "notionalFloor": "10000000",
+ "notionalCap": "50000000",
+ "notionalFloor": "25000000",
"maintMarginRatio": "0.5",
- "cum": "3330275.0"
+ "cum": "8340550.0"
}
}
],
@@ -37248,13 +39210,13 @@
"tier": 1.0,
"currency": "USDT",
"minNotional": 0.0,
- "maxNotional": 5000.0,
+ "maxNotional": 10000.0,
"maintenanceMarginRate": 0.005,
"maxLeverage": 75.0,
"info": {
"bracket": "1",
"initialLeverage": "75",
- "notionalCap": "5000",
+ "notionalCap": "10000",
"notionalFloor": "0",
"maintMarginRatio": "0.005",
"cum": "0.0"
@@ -37263,145 +39225,145 @@
{
"tier": 2.0,
"currency": "USDT",
- "minNotional": 5000.0,
- "maxNotional": 10000.0,
- "maintenanceMarginRate": 0.006,
+ "minNotional": 10000.0,
+ "maxNotional": 20000.0,
+ "maintenanceMarginRate": 0.0065,
"maxLeverage": 50.0,
"info": {
"bracket": "2",
"initialLeverage": "50",
- "notionalCap": "10000",
- "notionalFloor": "5000",
- "maintMarginRatio": "0.006",
- "cum": "5.0"
+ "notionalCap": "20000",
+ "notionalFloor": "10000",
+ "maintMarginRatio": "0.0065",
+ "cum": "15.0"
}
},
{
"tier": 3.0,
"currency": "USDT",
- "minNotional": 10000.0,
- "maxNotional": 50000.0,
+ "minNotional": 20000.0,
+ "maxNotional": 160000.0,
"maintenanceMarginRate": 0.01,
"maxLeverage": 40.0,
"info": {
"bracket": "3",
"initialLeverage": "40",
- "notionalCap": "50000",
- "notionalFloor": "10000",
+ "notionalCap": "160000",
+ "notionalFloor": "20000",
"maintMarginRatio": "0.01",
- "cum": "45.0"
+ "cum": "85.0"
}
},
{
"tier": 4.0,
"currency": "USDT",
- "minNotional": 50000.0,
- "maxNotional": 750000.0,
+ "minNotional": 160000.0,
+ "maxNotional": 800000.0,
"maintenanceMarginRate": 0.02,
"maxLeverage": 25.0,
"info": {
"bracket": "4",
"initialLeverage": "25",
- "notionalCap": "750000",
- "notionalFloor": "50000",
+ "notionalCap": "800000",
+ "notionalFloor": "160000",
"maintMarginRatio": "0.02",
- "cum": "545.0"
+ "cum": "1685.0"
}
},
{
"tier": 5.0,
"currency": "USDT",
- "minNotional": 750000.0,
- "maxNotional": 3000000.0,
- "maintenanceMarginRate": 0.05,
- "maxLeverage": 10.0,
+ "minNotional": 800000.0,
+ "maxNotional": 1600000.0,
+ "maintenanceMarginRate": 0.025,
+ "maxLeverage": 20.0,
"info": {
"bracket": "5",
- "initialLeverage": "10",
- "notionalCap": "3000000",
- "notionalFloor": "750000",
- "maintMarginRatio": "0.05",
- "cum": "23045.0"
+ "initialLeverage": "20",
+ "notionalCap": "1600000",
+ "notionalFloor": "800000",
+ "maintMarginRatio": "0.025",
+ "cum": "5685.0"
}
},
{
"tier": 6.0,
"currency": "USDT",
- "minNotional": 3000000.0,
- "maxNotional": 10000000.0,
- "maintenanceMarginRate": 0.1,
- "maxLeverage": 5.0,
+ "minNotional": 1600000.0,
+ "maxNotional": 8000000.0,
+ "maintenanceMarginRate": 0.05,
+ "maxLeverage": 10.0,
"info": {
"bracket": "6",
- "initialLeverage": "5",
- "notionalCap": "10000000",
- "notionalFloor": "3000000",
- "maintMarginRatio": "0.1",
- "cum": "173045.0"
+ "initialLeverage": "10",
+ "notionalCap": "8000000",
+ "notionalFloor": "1600000",
+ "maintMarginRatio": "0.05",
+ "cum": "45685.0"
}
},
{
"tier": 7.0,
"currency": "USDT",
- "minNotional": 10000000.0,
- "maxNotional": 12000000.0,
- "maintenanceMarginRate": 0.125,
- "maxLeverage": 4.0,
+ "minNotional": 8000000.0,
+ "maxNotional": 16000000.0,
+ "maintenanceMarginRate": 0.1,
+ "maxLeverage": 5.0,
"info": {
"bracket": "7",
- "initialLeverage": "4",
- "notionalCap": "12000000",
- "notionalFloor": "10000000",
- "maintMarginRatio": "0.125",
- "cum": "423045.0"
+ "initialLeverage": "5",
+ "notionalCap": "16000000",
+ "notionalFloor": "8000000",
+ "maintMarginRatio": "0.1",
+ "cum": "445685.0"
}
},
{
"tier": 8.0,
"currency": "USDT",
- "minNotional": 12000000.0,
+ "minNotional": 16000000.0,
"maxNotional": 20000000.0,
- "maintenanceMarginRate": 0.15,
- "maxLeverage": 3.0,
+ "maintenanceMarginRate": 0.125,
+ "maxLeverage": 4.0,
"info": {
"bracket": "8",
- "initialLeverage": "3",
+ "initialLeverage": "4",
"notionalCap": "20000000",
- "notionalFloor": "12000000",
- "maintMarginRatio": "0.15",
- "cum": "723045.0"
+ "notionalFloor": "16000000",
+ "maintMarginRatio": "0.125",
+ "cum": "845685.0"
}
},
{
"tier": 9.0,
"currency": "USDT",
"minNotional": 20000000.0,
- "maxNotional": 30000000.0,
+ "maxNotional": 40000000.0,
"maintenanceMarginRate": 0.25,
"maxLeverage": 2.0,
"info": {
"bracket": "9",
"initialLeverage": "2",
- "notionalCap": "30000000",
+ "notionalCap": "40000000",
"notionalFloor": "20000000",
"maintMarginRatio": "0.25",
- "cum": "2723045.0"
+ "cum": "3345685.0"
}
},
{
"tier": 10.0,
"currency": "USDT",
- "minNotional": 30000000.0,
- "maxNotional": 50000000.0,
+ "minNotional": 40000000.0,
+ "maxNotional": 80000000.0,
"maintenanceMarginRate": 0.5,
"maxLeverage": 1.0,
"info": {
"bracket": "10",
"initialLeverage": "1",
- "notionalCap": "50000000",
- "notionalFloor": "30000000",
+ "notionalCap": "80000000",
+ "notionalFloor": "40000000",
"maintMarginRatio": "0.5",
- "cum": "10223045.0"
+ "cum": "13345685.0"
}
}
],
diff --git a/freqtrade/exchange/bitvavo.py b/freqtrade/exchange/bitvavo.py
index d088e3435..ff0c0e37f 100644
--- a/freqtrade/exchange/bitvavo.py
+++ b/freqtrade/exchange/bitvavo.py
@@ -1,8 +1,10 @@
-"""Kucoin exchange subclass."""
+"""Bitvavo exchange subclass."""
import logging
from typing import Dict
+from ccxt import DECIMAL_PLACES
+
from freqtrade.exchange import Exchange
@@ -22,3 +24,11 @@ class Bitvavo(Exchange):
_ft_has: Dict = {
"ohlcv_candle_limit": 1440,
}
+
+ @property
+ def precisionMode(self) -> int:
+ """
+ Exchange ccxt precisionMode
+ Override due to https://github.com/ccxt/ccxt/issues/20408
+ """
+ return DECIMAL_PLACES
diff --git a/freqtrade/exchange/exchange.py b/freqtrade/exchange/exchange.py
index 2b6373ab8..48e800d35 100644
--- a/freqtrade/exchange/exchange.py
+++ b/freqtrade/exchange/exchange.py
@@ -88,7 +88,14 @@ from freqtrade.exchange.exchange_utils_timeframe import (
timeframe_to_seconds,
)
from freqtrade.exchange.exchange_ws import ExchangeWS
-from freqtrade.exchange.types import OHLCVResponse, OrderBook, Ticker, Tickers
+from freqtrade.exchange.types import (
+ CcxtBalances,
+ CcxtPosition,
+ OHLCVResponse,
+ OrderBook,
+ Ticker,
+ Tickers,
+)
from freqtrade.misc import (
chunks,
deep_merge_dicts,
@@ -128,6 +135,7 @@ class Exchange:
# Check https://github.com/ccxt/ccxt/issues/10767 for removal of ohlcv_volume_currency
"ohlcv_volume_currency": "base", # "base" or "quote"
"tickers_have_quoteVolume": True,
+ "tickers_have_percentage": True,
"tickers_have_bid_ask": True, # bid / ask empty for fetch_tickers
"tickers_have_price": True,
"trades_limit": 1000, # Limit for 1 call to fetch_trades
@@ -315,7 +323,7 @@ class Exchange:
asyncio.set_event_loop(loop)
return loop
- def validate_config(self, config):
+ def validate_config(self, config: Config) -> None:
# Check if timeframe is available
self.validate_timeframes(config.get("timeframe"))
@@ -329,6 +337,7 @@ class Exchange:
self.validate_pricing(config["exit_pricing"])
self.validate_pricing(config["entry_pricing"])
self.validate_orderflow(config["exchange"])
+ self.validate_freqai(config)
def _init_ccxt(
self, exchange_config: Dict[str, Any], sync: bool, ccxt_kwargs: Dict[str, Any]
@@ -352,14 +361,18 @@ class Exchange:
raise OperationalException(f"Exchange {name} is not supported by ccxt")
ex_config = {
- "apiKey": exchange_config.get("apiKey", exchange_config.get("key")),
+ "apiKey": exchange_config.get(
+ "api_key", exchange_config.get("apiKey", exchange_config.get("key"))
+ ),
"secret": exchange_config.get("secret"),
"password": exchange_config.get("password"),
"uid": exchange_config.get("uid", ""),
- "accountId": exchange_config.get("accountId", ""),
+ "accountId": exchange_config.get("account_id", exchange_config.get("accountId", "")),
# DEX attributes:
- "walletAddress": exchange_config.get("walletAddress"),
- "privateKey": exchange_config.get("privateKey"),
+ "walletAddress": exchange_config.get(
+ "wallet_address", exchange_config.get("walletAddress")
+ ),
+ "privateKey": exchange_config.get("private_key", exchange_config.get("privateKey")),
}
if ccxt_kwargs:
logger.info("Applying additional ccxt config: %s", ccxt_kwargs)
@@ -411,7 +424,17 @@ class Exchange:
@property
def precisionMode(self) -> int:
- """exchange ccxt precisionMode"""
+ """Exchange ccxt precisionMode"""
+ return self._api.precisionMode
+
+ @property
+ def precision_mode_price(self) -> int:
+ """
+ Exchange ccxt precisionMode used for price
+ Workaround for ccxt limitation to not have precisionMode for price
+ if it differs for an exchange
+ Might need to be updated if https://github.com/ccxt/ccxt/issues/20408 is fixed.
+ """
return self._api.precisionMode
def additional_exchange_init(self) -> None:
@@ -541,7 +564,7 @@ class Exchange:
else:
return self._trades[pair_interval]
else:
- return DataFrame()
+ return DataFrame(columns=DEFAULT_TRADES_COLUMNS)
def get_contract_size(self, pair: str) -> Optional[float]:
if self.trading_mode == TradingMode.FUTURES:
@@ -804,6 +827,13 @@ class Exchange:
f"Trade data not available for {self.name}. Can't use orderflow feature."
)
+ def validate_freqai(self, config: Config) -> None:
+ freqai_enabled = config.get("freqai", {}).get("enabled", False)
+ if freqai_enabled and not self._ft_has["ohlcv_has_history"]:
+ raise ConfigurationError(
+ f"Historic OHLCV data not available for {self.name}. Can't use freqAI."
+ )
+
def validate_required_startup_candles(self, startup_candles: int, timeframe: str) -> int:
"""
Checks if required startup_candles is more than ohlcv_candle_limit().
@@ -908,7 +938,10 @@ class Exchange:
For stoploss calculations, must use ROUND_UP for longs, and ROUND_DOWN for shorts.
"""
return price_to_precision(
- price, self.get_precision_price(pair), self.precisionMode, rounding_mode=rounding_mode
+ price,
+ self.get_precision_price(pair),
+ self.precision_mode_price,
+ rounding_mode=rounding_mode,
)
def price_get_one_pip(self, pair: str, price: float) -> float:
@@ -1645,7 +1678,7 @@ class Exchange:
return order
@retrier
- def get_balances(self) -> dict:
+ def get_balances(self) -> CcxtBalances:
try:
balances = self._api.fetch_balance()
# Remove additional info from ccxt results
@@ -1665,7 +1698,7 @@ class Exchange:
raise OperationalException(e) from e
@retrier
- def fetch_positions(self, pair: Optional[str] = None) -> List[Dict]:
+ def fetch_positions(self, pair: Optional[str] = None) -> List[CcxtPosition]:
"""
Fetch positions from the exchange.
If no pair is given, all positions are returned.
@@ -1677,7 +1710,7 @@ class Exchange:
symbols = []
if pair:
symbols.append(pair)
- positions: List[Dict] = self._api.fetch_positions(symbols)
+ positions: List[CcxtPosition] = self._api.fetch_positions(symbols)
self._log_exchange_response("fetch_positions", positions)
return positions
except ccxt.DDoSProtection as e:
@@ -2469,17 +2502,17 @@ class Exchange:
logger.debug("Refreshing candle (OHLCV) data for %d pairs", len(pair_list))
# Gather coroutines to run
- input_coroutines, cached_pairs = self._build_ohlcv_dl_jobs(pair_list, since_ms, cache)
+ ohlcv_dl_jobs, cached_pairs = self._build_ohlcv_dl_jobs(pair_list, since_ms, cache)
results_df = {}
# Chunk requests into batches of 100 to avoid overwhelming ccxt Throttling
- for input_coro in chunks(input_coroutines, 100):
+ for dl_jobs_batch in chunks(ohlcv_dl_jobs, 100):
- async def gather_stuff(coro):
+ async def gather_coroutines(coro):
return await asyncio.gather(*coro, return_exceptions=True)
with self._loop_lock:
- results = self.loop.run_until_complete(gather_stuff(input_coro))
+ results = self.loop.run_until_complete(gather_coroutines(dl_jobs_batch))
for res in results:
if isinstance(res, Exception):
@@ -2607,12 +2640,13 @@ class Exchange:
except (ccxt.OperationFailed, ccxt.ExchangeError) as e:
raise TemporaryError(
f"Could not fetch historical candle (OHLCV) data "
- f"for pair {pair} due to {e.__class__.__name__}. "
+ f"for {pair}, {timeframe}, {candle_type} due to {e.__class__.__name__}. "
f"Message: {e}"
) from e
except ccxt.BaseError as e:
raise OperationalException(
- f"Could not fetch historical candle (OHLCV) data for pair {pair}. Message: {e}"
+ f"Could not fetch historical candle (OHLCV) data for "
+ f"{pair}, {timeframe}, {candle_type}. Message: {e}"
) from e
async def _fetch_funding_rate_history(
@@ -2677,6 +2711,94 @@ class Exchange:
self._trades[(pair, timeframe, c_type)] = trades_df
return trades_df
+ async def _build_trades_dl_jobs(
+ self, pairwt: PairWithTimeframe, data_handler, cache: bool
+ ) -> Tuple[PairWithTimeframe, Optional[DataFrame]]:
+ """
+ Build coroutines to refresh trades for (they're then called through async.gather)
+ """
+ pair, timeframe, candle_type = pairwt
+ since_ms = None
+ new_ticks: List = []
+ all_stored_ticks_df = DataFrame(columns=DEFAULT_TRADES_COLUMNS + ["date"])
+ first_candle_ms = self.needed_candle_for_trades_ms(timeframe, candle_type)
+ # refresh, if
+ # a. not in _trades
+ # b. no cache used
+ # c. need new data
+ is_in_cache = (pair, timeframe, candle_type) in self._trades
+ if (
+ not is_in_cache
+ or not cache
+ or self._now_is_time_to_refresh_trades(pair, timeframe, candle_type)
+ ):
+ logger.debug(f"Refreshing TRADES data for {pair}")
+ # fetch trades since latest _trades and
+ # store together with existing trades
+ try:
+ until = None
+ from_id = None
+ if is_in_cache:
+ from_id = self._trades[(pair, timeframe, candle_type)].iloc[-1]["id"]
+ until = dt_ts() # now
+
+ else:
+ until = int(timeframe_to_prev_date(timeframe).timestamp()) * 1000
+ all_stored_ticks_df = data_handler.trades_load(
+ f"{pair}-cached", self.trading_mode
+ )
+
+ if not all_stored_ticks_df.empty:
+ if (
+ all_stored_ticks_df.iloc[-1]["timestamp"] > first_candle_ms
+ and all_stored_ticks_df.iloc[0]["timestamp"] <= first_candle_ms
+ ):
+ # Use cache and populate further
+ last_cached_ms = all_stored_ticks_df.iloc[-1]["timestamp"]
+ from_id = all_stored_ticks_df.iloc[-1]["id"]
+ # only use cached if it's closer than first_candle_ms
+ since_ms = (
+ last_cached_ms
+ if last_cached_ms > first_candle_ms
+ else first_candle_ms
+ )
+ else:
+ # Skip cache, it's too old
+ all_stored_ticks_df = DataFrame(
+ columns=DEFAULT_TRADES_COLUMNS + ["date"]
+ )
+
+ # from_id overrules with exchange set to id paginate
+ [_, new_ticks] = await self._async_get_trade_history(
+ pair,
+ since=since_ms if since_ms else first_candle_ms,
+ until=until,
+ from_id=from_id,
+ )
+
+ except Exception:
+ logger.exception(f"Refreshing TRADES data for {pair} failed")
+ return pairwt, None
+
+ if new_ticks:
+ all_stored_ticks_list = all_stored_ticks_df[DEFAULT_TRADES_COLUMNS].values.tolist()
+ all_stored_ticks_list.extend(new_ticks)
+ trades_df = self._process_trades_df(
+ pair,
+ timeframe,
+ candle_type,
+ all_stored_ticks_list,
+ cache,
+ first_required_candle_date=first_candle_ms,
+ )
+ data_handler.trades_store(
+ f"{pair}-cached", trades_df[DEFAULT_TRADES_COLUMNS], self.trading_mode
+ )
+ return pairwt, trades_df
+ else:
+ logger.error(f"No new ticks for {pair}")
+ return pairwt, None
+
def refresh_latest_trades(
self,
pair_list: ListPairsWithTimeframes,
@@ -2697,90 +2819,25 @@ class Exchange:
self._config["datadir"], data_format=self._config["dataformat_trades"]
)
logger.debug("Refreshing TRADES data for %d pairs", len(pair_list))
- since_ms = None
results_df = {}
- for pair, timeframe, candle_type in set(pair_list):
- new_ticks: List = []
- all_stored_ticks_df = DataFrame(columns=DEFAULT_TRADES_COLUMNS + ["date"])
- first_candle_ms = self.needed_candle_for_trades_ms(timeframe, candle_type)
- # refresh, if
- # a. not in _trades
- # b. no cache used
- # c. need new data
- is_in_cache = (pair, timeframe, candle_type) in self._trades
- if (
- not is_in_cache
- or not cache
- or self._now_is_time_to_refresh_trades(pair, timeframe, candle_type)
- ):
- logger.debug(f"Refreshing TRADES data for {pair}")
- # fetch trades since latest _trades and
- # store together with existing trades
- try:
- until = None
- from_id = None
- if is_in_cache:
- from_id = self._trades[(pair, timeframe, candle_type)].iloc[-1]["id"]
- until = dt_ts() # now
+ trades_dl_jobs = []
+ for pair_wt in set(pair_list):
+ trades_dl_jobs.append(self._build_trades_dl_jobs(pair_wt, data_handler, cache))
- else:
- until = int(timeframe_to_prev_date(timeframe).timestamp()) * 1000
- all_stored_ticks_df = data_handler.trades_load(
- f"{pair}-cached", self.trading_mode
- )
+ async def gather_coroutines(coro):
+ return await asyncio.gather(*coro, return_exceptions=True)
- if not all_stored_ticks_df.empty:
- if (
- all_stored_ticks_df.iloc[-1]["timestamp"] > first_candle_ms
- and all_stored_ticks_df.iloc[0]["timestamp"] <= first_candle_ms
- ):
- # Use cache and populate further
- last_cached_ms = all_stored_ticks_df.iloc[-1]["timestamp"]
- from_id = all_stored_ticks_df.iloc[-1]["id"]
- # only use cached if it's closer than first_candle_ms
- since_ms = (
- last_cached_ms
- if last_cached_ms > first_candle_ms
- else first_candle_ms
- )
- else:
- # Skip cache, it's too old
- all_stored_ticks_df = DataFrame(
- columns=DEFAULT_TRADES_COLUMNS + ["date"]
- )
+ for dl_job_chunk in chunks(trades_dl_jobs, 100):
+ with self._loop_lock:
+ results = self.loop.run_until_complete(gather_coroutines(dl_job_chunk))
- # from_id overrules with exchange set to id paginate
- [_, new_ticks] = self.get_historic_trades(
- pair,
- since=since_ms if since_ms else first_candle_ms,
- until=until,
- from_id=from_id,
- )
-
- except Exception:
- logger.exception(f"Refreshing TRADES data for {pair} failed")
+ for res in results:
+ if isinstance(res, Exception):
+ logger.warning(f"Async code raised an exception: {repr(res)}")
continue
-
- if new_ticks:
- all_stored_ticks_list = all_stored_ticks_df[
- DEFAULT_TRADES_COLUMNS
- ].values.tolist()
- all_stored_ticks_list.extend(new_ticks)
- trades_df = self._process_trades_df(
- pair,
- timeframe,
- candle_type,
- all_stored_ticks_list,
- cache,
- first_required_candle_date=first_candle_ms,
- )
- results_df[(pair, timeframe, candle_type)] = trades_df
- data_handler.trades_store(
- f"{pair}-cached", trades_df[DEFAULT_TRADES_COLUMNS], self.trading_mode
- )
-
- else:
- logger.error(f"No new ticks for {pair}")
+ pairwt, trades_df = res
+ if trades_df is not None:
+ results_df[pairwt] = trades_df
return results_df
diff --git a/freqtrade/exchange/exchange_utils.py b/freqtrade/exchange/exchange_utils.py
index 9c2514f92..fc3824dcf 100644
--- a/freqtrade/exchange/exchange_utils.py
+++ b/freqtrade/exchange/exchange_utils.py
@@ -2,6 +2,7 @@
Exchange support utils
"""
+import inspect
from datetime import datetime, timedelta, timezone
from math import ceil, floor
from typing import Any, Dict, List, Optional, Tuple
@@ -53,9 +54,9 @@ def available_exchanges(ccxt_module: Optional[CcxtModuleType] = None) -> List[st
return [x for x in exchanges if validate_exchange(x)[0]]
-def validate_exchange(exchange: str) -> Tuple[bool, str, bool]:
+def validate_exchange(exchange: str) -> Tuple[bool, str, Optional[ccxt.Exchange]]:
"""
- returns: can_use, reason
+ returns: can_use, reason, exchange_object
with Reason including both missing and missing_opt
"""
try:
@@ -64,11 +65,10 @@ def validate_exchange(exchange: str) -> Tuple[bool, str, bool]:
ex_mod = getattr(ccxt.async_support, exchange.lower())()
if not ex_mod or not ex_mod.has:
- return False, "", False
+ return False, "", None
result = True
reason = ""
- is_dex = getattr(ex_mod, "dex", False)
missing = [
k
for k, v in EXCHANGE_HAS_REQUIRED.items()
@@ -87,19 +87,24 @@ def validate_exchange(exchange: str) -> Tuple[bool, str, bool]:
if missing_opt:
reason += f"{'. ' if reason else ''}missing opt: {', '.join(missing_opt)}. "
- return result, reason, is_dex
+ return result, reason, ex_mod
def _build_exchange_list_entry(
exchange_name: str, exchangeClasses: Dict[str, Any]
) -> ValidExchangesType:
- valid, comment, is_dex = validate_exchange(exchange_name)
+ valid, comment, ex_mod = validate_exchange(exchange_name)
result: ValidExchangesType = {
- "name": exchange_name,
+ "name": getattr(ex_mod, "name", exchange_name),
+ "classname": exchange_name,
"valid": valid,
"supported": exchange_name.lower() in SUPPORTED_EXCHANGES,
"comment": comment,
- "dex": is_dex,
+ "dex": getattr(ex_mod, "dex", False),
+ "is_alias": getattr(ex_mod, "alias", False),
+ "alias_for": inspect.getmro(ex_mod.__class__)[1]().id
+ if getattr(ex_mod, "alias", False)
+ else None,
"trade_modes": [{"trading_mode": "spot", "margin_mode": ""}],
}
if resolved := exchangeClasses.get(exchange_name.lower()):
diff --git a/freqtrade/exchange/hyperliquid.py b/freqtrade/exchange/hyperliquid.py
index d2b1e1482..1255b977a 100644
--- a/freqtrade/exchange/hyperliquid.py
+++ b/freqtrade/exchange/hyperliquid.py
@@ -3,6 +3,8 @@
import logging
from typing import Dict
+from ccxt import SIGNIFICANT_DIGITS
+
from freqtrade.exchange import Exchange
@@ -17,8 +19,15 @@ class Hyperliquid(Exchange):
_ft_has: Dict = {
# Only the most recent 5000 candles are available according to the
# exchange's API documentation.
- "ohlcv_has_history": True,
+ "ohlcv_has_history": False,
"ohlcv_candle_limit": 5000,
"trades_has_history": False, # Trades endpoint doesn't seem available.
"exchange_has_overrides": {"fetchTrades": False},
}
+
+ @property
+ def precision_mode_price(self) -> int:
+ """
+ Override the default precision mode for price.
+ """
+ return SIGNIFICANT_DIGITS
diff --git a/freqtrade/exchange/kraken.py b/freqtrade/exchange/kraken.py
index f0562ecaf..4b178420a 100644
--- a/freqtrade/exchange/kraken.py
+++ b/freqtrade/exchange/kraken.py
@@ -12,7 +12,7 @@ from freqtrade.enums import MarginMode, TradingMode
from freqtrade.exceptions import DDosProtection, OperationalException, TemporaryError
from freqtrade.exchange import Exchange
from freqtrade.exchange.common import retrier
-from freqtrade.exchange.types import Tickers
+from freqtrade.exchange.types import CcxtBalances, Tickers
logger = logging.getLogger(__name__)
@@ -57,7 +57,7 @@ class Kraken(Exchange):
return super().get_tickers(symbols=symbols, cached=cached)
@retrier
- def get_balances(self) -> dict:
+ def get_balances(self) -> CcxtBalances:
if self._config["dry_run"]:
return {}
diff --git a/freqtrade/exchange/okx.py b/freqtrade/exchange/okx.py
index 94a81b452..db94f576d 100644
--- a/freqtrade/exchange/okx.py
+++ b/freqtrade/exchange/okx.py
@@ -34,6 +34,7 @@ class Okx(Exchange):
"stoploss_order_types": {"limit": "limit"},
"stoploss_on_exchange": True,
"trades_has_history": False, # Endpoint doesn't have a "since" parameter
+ "ws.enabled": True,
}
_ft_has_futures: Dict = {
"tickers_have_quoteVolume": False,
@@ -43,6 +44,7 @@ class Okx(Exchange):
PriceType.MARK: "index",
PriceType.INDEX: "mark",
},
+ "ws.enabled": True,
}
_supported_trading_mode_margin_pairs: List[Tuple[TradingMode, MarginMode]] = [
diff --git a/freqtrade/exchange/types.py b/freqtrade/exchange/types.py
index 5568e4336..2a9ae1078 100644
--- a/freqtrade/exchange/types.py
+++ b/freqtrade/exchange/types.py
@@ -12,9 +12,13 @@ class Ticker(TypedDict):
last: Optional[float]
quoteVolume: Optional[float]
baseVolume: Optional[float]
+ percentage: Optional[float]
# Several more - only listing required.
+Tickers = Dict[str, Ticker]
+
+
class OrderBook(TypedDict):
symbol: str
bids: List[Tuple[float, float]]
@@ -24,7 +28,24 @@ class OrderBook(TypedDict):
nonce: Optional[int]
-Tickers = Dict[str, Ticker]
+class CcxtBalance(TypedDict):
+ free: float
+ used: float
+ total: float
+
+
+CcxtBalances = Dict[str, CcxtBalance]
+
+
+class CcxtPosition(TypedDict):
+ symbol: str
+ side: str
+ contracts: float
+ leverage: float
+ collateral: Optional[float]
+ initialMargin: Optional[float]
+ liquidationPrice: Optional[float]
+
# pair, timeframe, candleType, OHLCV, drop last?,
OHLCVResponse = Tuple[str, str, CandleType, List, bool]
diff --git a/freqtrade/freqtradebot.py b/freqtrade/freqtradebot.py
index 5a33e9fa6..dff99e93e 100644
--- a/freqtrade/freqtradebot.py
+++ b/freqtrade/freqtradebot.py
@@ -374,6 +374,7 @@ class FreqtradeBot(LoggingMixin):
if trade.exchange != self.exchange.id:
continue
trade.precision_mode = self.exchange.precisionMode
+ trade.precision_mode_price = self.exchange.precision_mode_price
trade.amount_precision = self.exchange.get_precision_amount(trade.pair)
trade.price_precision = self.exchange.get_precision_price(trade.pair)
trade.contract_size = self.exchange.get_contract_size(trade.pair)
@@ -541,7 +542,11 @@ class FreqtradeBot(LoggingMixin):
)
else:
trade.exit_reason = prev_exit_reason
- total = self.wallets.get_total(trade.base_currency) if trade.base_currency else 0
+ total = (
+ self.wallets.get_owned(trade.pair, trade.base_currency)
+ if trade.base_currency
+ else 0
+ )
if total < trade.amount:
if trade.fully_canceled_entry_order_count == len(trade.orders):
logger.warning(
@@ -992,6 +997,7 @@ class FreqtradeBot(LoggingMixin):
amount_precision=self.exchange.get_precision_amount(pair),
price_precision=self.exchange.get_precision_price(pair),
precision_mode=self.exchange.precisionMode,
+ precision_mode_price=self.exchange.precision_mode_price,
contract_size=self.exchange.get_contract_size(pair),
)
stoploss = self.strategy.stoploss if not self.edge else self.edge.get_stoploss(pair)
diff --git a/freqtrade/main.py b/freqtrade/main.py
index 8161e20a6..67584c5b7 100755
--- a/freqtrade/main.py
+++ b/freqtrade/main.py
@@ -15,6 +15,7 @@ if sys.version_info < (3, 9): # pragma: no cover
from freqtrade import __version__
from freqtrade.commands import Arguments
+from freqtrade.configuration import asyncio_setup
from freqtrade.constants import DOCS_LINK
from freqtrade.exceptions import ConfigurationError, FreqtradeException, OperationalException
from freqtrade.loggers import setup_logging_pre
@@ -33,6 +34,7 @@ def main(sysargv: Optional[List[str]] = None) -> None:
return_code: Any = 1
try:
setup_logging_pre()
+ asyncio_setup()
arguments = Arguments(sysargv)
args = arguments.get_parsed_arg()
diff --git a/freqtrade/misc.py b/freqtrade/misc.py
index 23e2779a0..7c56231c3 100644
--- a/freqtrade/misc.py
+++ b/freqtrade/misc.py
@@ -128,7 +128,10 @@ def round_dict(d, n):
return {k: (round(v, n) if isinstance(v, float) else v) for k, v in d.items()}
-def safe_value_fallback(obj: dict, key1: str, key2: Optional[str] = None, default_value=None):
+DictMap = Union[Dict[str, Any], Mapping[str, Any]]
+
+
+def safe_value_fallback(obj: DictMap, key1: str, key2: Optional[str] = None, default_value=None):
"""
Search a value in obj, return this if it's not None.
Then search key2 in obj - return that if it's not none - then use default_value.
@@ -142,10 +145,7 @@ def safe_value_fallback(obj: dict, key1: str, key2: Optional[str] = None, defaul
return default_value
-dictMap = Union[Dict[str, Any], Mapping[str, Any]]
-
-
-def safe_value_fallback2(dict1: dictMap, dict2: dictMap, key1: str, key2: str, default_value=None):
+def safe_value_fallback2(dict1: DictMap, dict2: DictMap, key1: str, key2: str, default_value=None):
"""
Search a value in dict1, return this if it's not None.
Fall back to dict2 - return key2 from dict2 if it's not None.
diff --git a/freqtrade/optimize/analysis/recursive.py b/freqtrade/optimize/analysis/recursive.py
index f6e4fa3a9..e6f7e4152 100644
--- a/freqtrade/optimize/analysis/recursive.py
+++ b/freqtrade/optimize/analysis/recursive.py
@@ -14,6 +14,7 @@ from freqtrade.loggers.set_log_levels import (
)
from freqtrade.optimize.backtesting import Backtesting
from freqtrade.optimize.base_analysis import BaseAnalysis, VarHolder
+from freqtrade.resolvers import StrategyResolver
logger = logging.getLogger(__name__)
@@ -21,10 +22,19 @@ logger = logging.getLogger(__name__)
class RecursiveAnalysis(BaseAnalysis):
def __init__(self, config: Dict[str, Any], strategy_obj: Dict):
- self._startup_candle = config.get("startup_candle", [199, 399, 499, 999, 1999])
+ self._startup_candle = list(
+ map(int, config.get("startup_candle", [199, 399, 499, 999, 1999]))
+ )
super().__init__(config, strategy_obj)
+ strat = StrategyResolver.load_strategy(config)
+ self._strat_scc = strat.startup_candle_count
+
+ if self._strat_scc not in self._startup_candle:
+ self._startup_candle.append(self._strat_scc)
+ self._startup_candle.sort()
+
self.partial_varHolder_array: List[VarHolder] = []
self.partial_varHolder_lookahead_array: List[VarHolder] = []
@@ -58,9 +68,13 @@ class RecursiveAnalysis(BaseAnalysis):
values_diff = compare_df.loc[indicator]
values_diff_self = values_diff.loc["self"]
values_diff_other = values_diff.loc["other"]
- diff = (values_diff_other - values_diff_self) / values_diff_self * 100
- self.dict_recursive[indicator][part.startup_candle] = f"{diff:.3f}%"
+ if values_diff_self and values_diff_other:
+ diff = (values_diff_other - values_diff_self) / values_diff_self * 100
+ str_diff = f"{diff:.3f}%"
+ else:
+ str_diff = "NaN"
+ self.dict_recursive[indicator][part.startup_candle] = str_diff
else:
logger.info("No variance on indicator(s) found due to recursive formula.")
@@ -174,7 +188,7 @@ class RecursiveAnalysis(BaseAnalysis):
start_date_partial = end_date_full - timedelta(minutes=int(timeframe_minutes))
for startup_candle in self._startup_candle:
- self.fill_partial_varholder(start_date_partial, int(startup_candle))
+ self.fill_partial_varholder(start_date_partial, startup_candle)
# Restore verbosity, so it's not too quiet for the next strategy
restore_verbosity_for_bias_tester()
diff --git a/freqtrade/optimize/analysis/recursive_helpers.py b/freqtrade/optimize/analysis/recursive_helpers.py
index be596fa68..474604923 100644
--- a/freqtrade/optimize/analysis/recursive_helpers.py
+++ b/freqtrade/optimize/analysis/recursive_helpers.py
@@ -17,9 +17,13 @@ class RecursiveAnalysisSubFunctions:
@staticmethod
def text_table_recursive_analysis_instances(recursive_instances: List[RecursiveAnalysis]):
startups = recursive_instances[0]._startup_candle
+ strat_scc = recursive_instances[0]._strat_scc
headers = ["Indicators"]
for candle in startups:
- headers.append(str(candle))
+ if candle == strat_scc:
+ headers.append(f"{candle} (from strategy)")
+ else:
+ headers.append(str(candle))
data = []
for inst in recursive_instances:
diff --git a/freqtrade/optimize/backtesting.py b/freqtrade/optimize/backtesting.py
index c28c080f5..1487d9f10 100644
--- a/freqtrade/optimize/backtesting.py
+++ b/freqtrade/optimize/backtesting.py
@@ -181,6 +181,7 @@ class Backtesting:
self.fee = max(fee for fee in fees if fee is not None)
logger.info(f"Using fee {self.fee:.4%} - worst case fee from exchange (lowest tier).")
self.precision_mode = self.exchange.precisionMode
+ self.precision_mode_price = self.exchange.precision_mode_price
if self.config.get("freqai_backtest_live_models", False):
from freqtrade.freqai.utils import get_timerange_backtest_live_models
@@ -329,15 +330,15 @@ class Backtesting:
else:
self.detail_data = {}
if self.trading_mode == TradingMode.FUTURES:
- self.funding_fee_timeframe: str = self.exchange.get_option("funding_fee_timeframe")
- self.funding_fee_timeframe_secs: int = timeframe_to_seconds(self.funding_fee_timeframe)
+ funding_fee_timeframe: str = self.exchange.get_option("funding_fee_timeframe")
+ self.funding_fee_timeframe_secs: int = timeframe_to_seconds(funding_fee_timeframe)
mark_timeframe: str = self.exchange.get_option("mark_ohlcv_timeframe")
# Load additional futures data.
funding_rates_dict = history.load_data(
datadir=self.config["datadir"],
pairs=self.pairlists.whitelist,
- timeframe=self.funding_fee_timeframe,
+ timeframe=funding_fee_timeframe,
timerange=self.timerange,
startup_candles=0,
fail_without_data=True,
@@ -785,7 +786,7 @@ class Backtesting:
)
if rate is not None and rate != close_rate:
close_rate = price_to_precision(
- rate, trade.price_precision, self.precision_mode
+ rate, trade.price_precision, self.precision_mode_price
)
# We can't place orders lower than current low.
# freqtrade does not support this in live, and the order would fill immediately
@@ -929,7 +930,9 @@ class Backtesting:
# We can't place orders higher than current high (otherwise it'd be a stop limit entry)
# which freqtrade does not support in live.
if new_rate is not None and new_rate != propose_rate:
- propose_rate = price_to_precision(new_rate, price_precision, self.precision_mode)
+ propose_rate = price_to_precision(
+ new_rate, price_precision, self.precision_mode_price
+ )
if direction == "short":
propose_rate = max(propose_rate, row[LOW_IDX])
else:
@@ -1109,6 +1112,7 @@ class Backtesting:
amount_precision=precision_amount,
price_precision=precision_price,
precision_mode=self.precision_mode,
+ precision_mode_price=self.precision_mode_price,
contract_size=contract_size,
orders=[],
)
@@ -1332,10 +1336,9 @@ class Backtesting:
pair: str,
current_time: datetime,
end_date: datetime,
- open_trade_count_start: int,
trade_dir: Optional[LongShort],
is_first: bool = True,
- ) -> int:
+ ) -> None:
"""
NOTE: This method is used by Hyperopt at each iteration. Please keep it optimized.
@@ -1345,7 +1348,6 @@ class Backtesting:
# 1. Manage currently open orders of active trades
if self.manage_open_orders(t, current_time, row):
# Close trade
- open_trade_count_start -= 1
LocalTrade.remove_bt_trade(t)
self.wallets.update()
@@ -1361,13 +1363,9 @@ class Backtesting:
and trade_dir is not None
and not PairLocks.is_pair_locked(pair, row[DATE_IDX], trade_dir)
):
- if self.trade_slot_available(open_trade_count_start):
+ if self.trade_slot_available(LocalTrade.bt_open_open_trade_count):
trade = self._enter_trade(pair, row, trade_dir)
if trade:
- # TODO: hacky workaround to avoid opening > max_open_trades
- # This emulates previous behavior - not sure if this is correct
- # Prevents entering if the trade-slot was freed in this candle
- open_trade_count_start += 1
self.wallets.update()
else:
self._collate_rejected(pair, row)
@@ -1386,7 +1384,28 @@ class Backtesting:
order = trade.select_order(trade.exit_side, is_open=True)
if order:
self._process_exit_order(order, trade, current_time, row, pair)
- return open_trade_count_start
+
+ def time_pair_generator(
+ self, start_date: datetime, end_date: datetime, increment: timedelta, pairs: List[str]
+ ):
+ """
+ Backtest time and pair generator
+ """
+ current_time = start_date + increment
+ self.progress.init_step(
+ BacktestState.BACKTEST, int((end_date - start_date) / self.timeframe_td)
+ )
+ while current_time <= end_date:
+ is_first = True
+ # Pairs that have open trades should be processed first
+ new_pairlist = list(dict.fromkeys([t.pair for t in LocalTrade.bt_trades_open] + pairs))
+
+ for pair in new_pairlist:
+ yield current_time, pair, is_first
+ is_first = False
+
+ self.progress.increment()
+ current_time += increment
def backtest(self, processed: Dict, start_date: datetime, end_date: datetime) -> Dict[str, Any]:
"""
@@ -1411,87 +1430,75 @@ class Backtesting:
# Indexes per pair, so some pairs are allowed to have a missing start.
indexes: Dict = defaultdict(int)
- current_time = start_date + self.timeframe_td
- self.progress.init_step(
- BacktestState.BACKTEST, int((end_date - start_date) / self.timeframe_td)
- )
# Loop timerange and get candle for each pair at that point in time
- while current_time <= end_date:
- open_trade_count_start = LocalTrade.bt_open_open_trade_count
- self.check_abort()
- strategy_safe_wrapper(self.strategy.bot_loop_start, supress_error=True)(
- current_time=current_time
- )
- for i, pair in enumerate(data):
- row_index = indexes[pair]
- row = self.validate_row(data, pair, row_index, current_time)
- if not row:
+ for current_time, pair, is_first in self.time_pair_generator(
+ start_date, end_date, self.timeframe_td, list(data.keys())
+ ):
+ if is_first:
+ self.check_abort()
+ strategy_safe_wrapper(self.strategy.bot_loop_start, supress_error=True)(
+ current_time=current_time
+ )
+ row_index = indexes[pair]
+ row = self.validate_row(data, pair, row_index, current_time)
+ if not row:
+ continue
+
+ row_index += 1
+ indexes[pair] = row_index
+ self.dataprovider._set_dataframe_max_index(self.required_startup + row_index)
+ self.dataprovider._set_dataframe_max_date(current_time)
+ current_detail_time: datetime = row[DATE_IDX].to_pydatetime()
+ trade_dir: Optional[LongShort] = self.check_for_trade_entry(row)
+
+ if (
+ (trade_dir is not None or len(LocalTrade.bt_trades_open_pp[pair]) > 0)
+ and self.timeframe_detail
+ and pair in self.detail_data
+ ):
+ # Spread out into detail timeframe.
+ # Should only happen when we are either in a trade for this pair
+ # or when we got the signal for a new trade.
+ exit_candle_end = current_detail_time + self.timeframe_td
+
+ detail_data = self.detail_data[pair]
+ detail_data = detail_data.loc[
+ (detail_data["date"] >= current_detail_time)
+ & (detail_data["date"] < exit_candle_end)
+ ].copy()
+ if len(detail_data) == 0:
+ # Fall back to "regular" data if no detail data was found for this candle
+ self.backtest_loop(row, pair, current_time, end_date, trade_dir)
continue
-
- row_index += 1
- indexes[pair] = row_index
- self.dataprovider._set_dataframe_max_index(self.required_startup + row_index)
- self.dataprovider._set_dataframe_max_date(current_time)
- current_detail_time: datetime = row[DATE_IDX].to_pydatetime()
- trade_dir: Optional[LongShort] = self.check_for_trade_entry(row)
-
- if (
- (trade_dir is not None or len(LocalTrade.bt_trades_open_pp[pair]) > 0)
- and self.timeframe_detail
- and pair in self.detail_data
- ):
- # Spread out into detail timeframe.
- # Should only happen when we are either in a trade for this pair
- # or when we got the signal for a new trade.
- exit_candle_end = current_detail_time + self.timeframe_td
-
- detail_data = self.detail_data[pair]
- detail_data = detail_data.loc[
- (detail_data["date"] >= current_detail_time)
- & (detail_data["date"] < exit_candle_end)
- ].copy()
- if len(detail_data) == 0:
- # Fall back to "regular" data if no detail data was found for this candle
- open_trade_count_start = self.backtest_loop(
- row, pair, current_time, end_date, open_trade_count_start, trade_dir
- )
- continue
- detail_data.loc[:, "enter_long"] = row[LONG_IDX]
- detail_data.loc[:, "exit_long"] = row[ELONG_IDX]
- detail_data.loc[:, "enter_short"] = row[SHORT_IDX]
- detail_data.loc[:, "exit_short"] = row[ESHORT_IDX]
- detail_data.loc[:, "enter_tag"] = row[ENTER_TAG_IDX]
- detail_data.loc[:, "exit_tag"] = row[EXIT_TAG_IDX]
- is_first = True
- current_time_det = current_time
- for det_row in detail_data[HEADERS].values.tolist():
- self.dataprovider._set_dataframe_max_date(current_time_det)
- open_trade_count_start = self.backtest_loop(
- det_row,
- pair,
- current_time_det,
- end_date,
- open_trade_count_start,
- trade_dir,
- is_first,
- )
- current_time_det += self.timeframe_detail_td
- is_first = False
- else:
- self.dataprovider._set_dataframe_max_date(current_time)
- open_trade_count_start = self.backtest_loop(
- row, pair, current_time, end_date, open_trade_count_start, trade_dir
+ detail_data.loc[:, "enter_long"] = row[LONG_IDX]
+ detail_data.loc[:, "exit_long"] = row[ELONG_IDX]
+ detail_data.loc[:, "enter_short"] = row[SHORT_IDX]
+ detail_data.loc[:, "exit_short"] = row[ESHORT_IDX]
+ detail_data.loc[:, "enter_tag"] = row[ENTER_TAG_IDX]
+ detail_data.loc[:, "exit_tag"] = row[EXIT_TAG_IDX]
+ is_first = True
+ current_time_det = current_time
+ for det_row in detail_data[HEADERS].values.tolist():
+ self.dataprovider._set_dataframe_max_date(current_time_det)
+ self.backtest_loop(
+ det_row,
+ pair,
+ current_time_det,
+ end_date,
+ trade_dir,
+ is_first,
)
-
- # Move time one configured time_interval ahead.
- self.progress.increment()
- current_time += self.timeframe_td
+ current_time_det += self.timeframe_detail_td
+ is_first = False
+ else:
+ self.dataprovider._set_dataframe_max_date(current_time)
+ self.backtest_loop(row, pair, current_time, end_date, trade_dir)
self.handle_left_open(LocalTrade.bt_trades_open_pp, data=data)
self.wallets.update()
- results = trade_list_to_dataframe(LocalTrade.trades)
+ results = trade_list_to_dataframe(LocalTrade.bt_trades)
return {
"results": results,
"config": self.strategy.config,
diff --git a/freqtrade/optimize/hyperopt.py b/freqtrade/optimize/hyperopt.py
index 2006cee58..428a5cddd 100644
--- a/freqtrade/optimize/hyperopt.py
+++ b/freqtrade/optimize/hyperopt.py
@@ -17,7 +17,6 @@ import rapidjson
from joblib import Parallel, cpu_count, delayed, dump, load, wrap_non_picklable_objects
from joblib.externals import cloudpickle
from pandas import DataFrame
-from rich.align import Align
from rich.console import Console
from freqtrade.constants import DATETIME_PRINT_FORMAT, FTHYPT_FILEVERSION, LAST_BT_RESULT_FN, Config
@@ -80,7 +79,7 @@ class Hyperopt:
self.max_open_trades_space: List[Dimension] = []
self.dimensions: List[Dimension] = []
- self._hyper_out: HyperoptOutput = HyperoptOutput()
+ self._hyper_out: HyperoptOutput = HyperoptOutput(streaming=True)
self.config = config
self.min_date: datetime
@@ -168,7 +167,9 @@ class Hyperopt:
cloudpickle.register_pickle_by_value(sys.modules[modules.__module__])
self.hyperopt_pickle_magic(modules.__bases__)
- def _get_params_dict(self, dimensions: List[Dimension], raw_params: List[Any]) -> Dict:
+ def _get_params_dict(
+ self, dimensions: List[Dimension], raw_params: List[Any]
+ ) -> Dict[str, Any]:
# Ensure the number of dimensions match
# the number of parameters in the list.
if len(raw_params) != len(dimensions):
@@ -317,7 +318,7 @@ class Hyperopt:
+ self.max_open_trades_space
)
- def assign_params(self, params_dict: Dict, category: str) -> None:
+ def assign_params(self, params_dict: Dict[str, Any], category: str) -> None:
"""
Assign hyperoptable parameters
"""
@@ -404,7 +405,12 @@ class Hyperopt:
)
def _get_results_dict(
- self, backtesting_results, min_date, max_date, params_dict, processed: Dict[str, DataFrame]
+ self,
+ backtesting_results: Dict[str, Any],
+ min_date: datetime,
+ max_date: datetime,
+ params_dict: Dict[str, Any],
+ processed: Dict[str, DataFrame],
) -> Dict[str, Any]:
params_details = self._get_params_details(params_dict)
@@ -628,7 +634,7 @@ class Hyperopt:
# Define progressbar
with get_progress_tracker(
console=console,
- cust_objs=[Align.center(self._hyper_out.table)],
+ cust_callables=[self._hyper_out],
) as pbar:
task = pbar.add_task("Epochs", total=self.total_epochs)
diff --git a/freqtrade/optimize/hyperopt_output.py b/freqtrade/optimize/hyperopt_output.py
index 72e049745..c83583d72 100644
--- a/freqtrade/optimize/hyperopt_output.py
+++ b/freqtrade/optimize/hyperopt_output.py
@@ -1,6 +1,8 @@
import sys
-from typing import List, Optional, Union
+from os import get_terminal_size
+from typing import Any, List, Optional
+from rich.align import Align
from rich.console import Console
from rich.table import Table
from rich.text import Text
@@ -11,7 +13,16 @@ from freqtrade.util import fmt_coin
class HyperoptOutput:
- def __init__(self):
+ def __init__(self, streaming=False) -> None:
+ self._results: List[Any] = []
+ self._streaming = streaming
+ self.__init_table()
+
+ def __call__(self, *args: Any, **kwds: Any) -> Any:
+ return Align.center(self.table)
+
+ def __init_table(self) -> None:
+ """Initialize table"""
self.table = Table(
title="Hyperopt results",
)
@@ -26,17 +37,6 @@ class HyperoptOutput:
self.table.add_column("Objective", justify="right")
self.table.add_column("Max Drawdown (Acct)", justify="right")
- def _add_row(self, data: List[Union[str, Text]]):
- """Add single row"""
- row_to_add: List[Union[str, Text]] = [r if isinstance(r, Text) else str(r) for r in data]
-
- self.table.add_row(*row_to_add)
-
- def _add_rows(self, data: List[List[Union[str, Text]]]):
- """add multiple rows"""
- for row in data:
- self._add_row(row)
-
def print(self, console: Optional[Console] = None, *, print_colorized=True):
if not console:
console = Console(
@@ -55,8 +55,28 @@ class HyperoptOutput:
) -> None:
"""Format one or multiple rows and add them"""
stake_currency = config["stake_currency"]
+ self._results.extend(results)
- for r in results:
+ max_rows: Optional[int] = None
+
+ if self._streaming:
+ try:
+ ts = get_terminal_size()
+ # Get terminal size.
+ # Account for header, borders, and for the progress bar.
+ # This assumes that lines don't wrap.
+ if ts.columns < 148:
+ # If the terminal is too small, we can't display the table properly.
+ # We will halve the number of rows to display.
+ max_rows = -(int(ts.lines / 2) - 6)
+ else:
+ max_rows = -(ts.lines - 6)
+ except OSError:
+ # If we can't get the terminal size, we will just display the last 10 rows.
+ pass
+
+ self.__init_table()
+ for r in self._results[max_rows:]:
self.table.add_row(
*[
# "Best":
diff --git a/freqtrade/persistence/migrations.py b/freqtrade/persistence/migrations.py
index e2e3b2175..2150d76bc 100644
--- a/freqtrade/persistence/migrations.py
+++ b/freqtrade/persistence/migrations.py
@@ -147,6 +147,9 @@ def migrate_trades_and_orders_table(
price_precision = get_column_def(cols, "price_precision", "null")
precision_mode = get_column_def(cols, "precision_mode", "null")
contract_size = get_column_def(cols, "contract_size", "null")
+ precision_mode_price = get_column_def(
+ cols, "precision_mode_price", get_column_def(cols, "precision_mode", "null")
+ )
# Schema migration necessary
with engine.begin() as connection:
@@ -177,7 +180,7 @@ def migrate_trades_and_orders_table(
timeframe, open_trade_value, close_profit_abs,
trading_mode, leverage, liquidation_price, is_short,
interest_rate, funding_fees, funding_fee_running, realized_profit,
- amount_precision, price_precision, precision_mode, contract_size,
+ amount_precision, price_precision, precision_mode, precision_mode_price, contract_size,
max_stake_amount
)
select id, lower(exchange), pair, {base_currency} base_currency,
@@ -207,8 +210,8 @@ def migrate_trades_and_orders_table(
{funding_fees} funding_fees, {funding_fee_running} funding_fee_running,
{realized_profit} realized_profit,
{amount_precision} amount_precision, {price_precision} price_precision,
- {precision_mode} precision_mode, {contract_size} contract_size,
- {max_stake_amount} max_stake_amount
+ {precision_mode} precision_mode, {precision_mode_price} precision_mode_price,
+ {contract_size} contract_size, {max_stake_amount} max_stake_amount
from {trade_back_name}
"""
)
@@ -348,8 +351,8 @@ def check_migrate(engine, decl_base, previous_tables) -> None:
# if ('orders' not in previous_tables
# or not has_column(cols_orders, 'funding_fee')):
migrating = False
- # if not has_column(cols_trades, 'funding_fee_running'):
- if not has_column(cols_orders, "ft_order_tag"):
+ if not has_column(cols_trades, "precision_mode_price"):
+ # if not has_column(cols_orders, "ft_order_tag"):
migrating = True
logger.info(
f"Running database migration for trades - "
diff --git a/freqtrade/persistence/trade_model.py b/freqtrade/persistence/trade_model.py
index e731f7552..4e7f01906 100644
--- a/freqtrade/persistence/trade_model.py
+++ b/freqtrade/persistence/trade_model.py
@@ -373,12 +373,12 @@ class LocalTrade:
use_db: bool = False
# Trades container for backtesting
- trades: List["LocalTrade"] = []
- trades_open: List["LocalTrade"] = []
+ bt_trades: List["LocalTrade"] = []
+ bt_trades_open: List["LocalTrade"] = []
# Copy of trades_open - but indexed by pair
bt_trades_open_pp: Dict[str, List["LocalTrade"]] = defaultdict(list)
bt_open_open_trade_count: int = 0
- total_profit: float = 0
+ bt_total_profit: float = 0
realized_profit: float = 0
id: int = 0
@@ -433,6 +433,7 @@ class LocalTrade:
amount_precision: Optional[float] = None
price_precision: Optional[float] = None
precision_mode: Optional[int] = None
+ precision_mode_price: Optional[int] = None
contract_size: Optional[float] = None
# Leverage trading properties
@@ -730,6 +731,7 @@ class LocalTrade:
"amount_precision": self.amount_precision,
"price_precision": self.price_precision,
"precision_mode": self.precision_mode,
+ "precision_mode_price": self.precision_mode_price,
"contract_size": self.contract_size,
"has_open_orders": self.has_open_orders,
"orders": orders_json,
@@ -740,11 +742,11 @@ class LocalTrade:
"""
Resets all trades. Only active for backtesting mode.
"""
- LocalTrade.trades = []
- LocalTrade.trades_open = []
+ LocalTrade.bt_trades = []
+ LocalTrade.bt_trades_open = []
LocalTrade.bt_trades_open_pp = defaultdict(list)
LocalTrade.bt_open_open_trade_count = 0
- LocalTrade.total_profit = 0
+ LocalTrade.bt_total_profit = 0
def adjust_min_max_rates(self, current_price: float, current_price_low: float) -> None:
"""
@@ -810,7 +812,7 @@ class LocalTrade:
stop_loss_norm = price_to_precision(
new_loss,
self.price_precision,
- self.precision_mode,
+ self.precision_mode_price,
rounding_mode=ROUND_DOWN if self.is_short else ROUND_UP,
)
# no stop loss assigned yet
@@ -819,7 +821,7 @@ class LocalTrade:
self.initial_stop_loss = price_to_precision(
stop_loss_norm,
self.price_precision,
- self.precision_mode,
+ self.precision_mode_price,
rounding_mode=ROUND_DOWN if self.is_short else ROUND_UP,
)
self.initial_stop_loss_pct = -1 * abs(stoploss)
@@ -1217,7 +1219,7 @@ class LocalTrade:
# with realized_profit.
close_profit = (close_profit_abs / total_stake) * self.leverage
else:
- total_stake = total_stake + self._calc_open_trade_value(tmp_amount, price)
+ total_stake += self._calc_open_trade_value(tmp_amount, price)
max_stake_amount += tmp_amount * price
self.funding_fees = funding_fees
self.max_stake_amount = float(max_stake_amount)
@@ -1236,7 +1238,7 @@ class LocalTrade:
self.open_rate = float(current_stake / current_amount)
self.amount = current_amount_tr
self.stake_amount = float(current_stake) / (self.leverage or 1.0)
- self.fee_open_cost = self.fee_open * float(current_stake)
+ self.fee_open_cost = self.fee_open * float(self.max_stake_amount)
self.recalc_open_trade_value()
if self.stop_loss_pct is not None and self.open_rate is not None:
self.adjust_stop_loss(self.open_rate, self.stop_loss_pct)
@@ -1405,7 +1407,7 @@ class LocalTrade:
Helper function to query Trades.
Returns a List of trades, filtered on the parameters given.
In live mode, converts the filter to a database query and returns all rows
- In Backtest mode, uses filters on Trade.trades to get the result.
+ In Backtest mode, uses filters on Trade.bt_trades to get the result.
:param pair: Filter by pair
:param is_open: Filter by open/closed status
@@ -1418,13 +1420,13 @@ class LocalTrade:
# Offline mode - without database
if is_open is not None:
if is_open:
- sel_trades = LocalTrade.trades_open
+ sel_trades = LocalTrade.bt_trades_open
else:
- sel_trades = LocalTrade.trades
+ sel_trades = LocalTrade.bt_trades
else:
# Not used during backtesting, but might be used by a strategy
- sel_trades = list(LocalTrade.trades + LocalTrade.trades_open)
+ sel_trades = list(LocalTrade.bt_trades + LocalTrade.bt_trades_open)
if pair:
sel_trades = [trade for trade in sel_trades if trade.pair == pair]
@@ -1439,24 +1441,24 @@ class LocalTrade:
@staticmethod
def close_bt_trade(trade):
- LocalTrade.trades_open.remove(trade)
+ LocalTrade.bt_trades_open.remove(trade)
LocalTrade.bt_trades_open_pp[trade.pair].remove(trade)
LocalTrade.bt_open_open_trade_count -= 1
- LocalTrade.trades.append(trade)
- LocalTrade.total_profit += trade.close_profit_abs
+ LocalTrade.bt_trades.append(trade)
+ LocalTrade.bt_total_profit += trade.close_profit_abs
@staticmethod
def add_bt_trade(trade):
if trade.is_open:
- LocalTrade.trades_open.append(trade)
+ LocalTrade.bt_trades_open.append(trade)
LocalTrade.bt_trades_open_pp[trade.pair].append(trade)
LocalTrade.bt_open_open_trade_count += 1
else:
- LocalTrade.trades.append(trade)
+ LocalTrade.bt_trades.append(trade)
@staticmethod
def remove_bt_trade(trade):
- LocalTrade.trades_open.remove(trade)
+ LocalTrade.bt_trades_open.remove(trade)
LocalTrade.bt_trades_open_pp[trade.pair].remove(trade)
LocalTrade.bt_open_open_trade_count -= 1
@@ -1562,6 +1564,7 @@ class LocalTrade:
amount_precision=data.get("amount_precision", None),
price_precision=data.get("price_precision", None),
precision_mode=data.get("precision_mode", None),
+ precision_mode_price=data.get("precision_mode_price", data.get("precision_mode", None)),
contract_size=data.get("contract_size", None),
)
for order in data["orders"]:
@@ -1695,6 +1698,9 @@ class Trade(ModelBase, LocalTrade):
)
price_precision: Mapped[Optional[float]] = mapped_column(Float(), nullable=True) # type: ignore
precision_mode: Mapped[Optional[int]] = mapped_column(Integer, nullable=True) # type: ignore
+ precision_mode_price: Mapped[Optional[int]] = mapped_column( # type: ignore
+ Integer, nullable=True
+ )
contract_size: Mapped[Optional[float]] = mapped_column(Float(), nullable=True) # type: ignore
# Leverage trading properties
@@ -1761,7 +1767,7 @@ class Trade(ModelBase, LocalTrade):
Helper function to query Trades.j
Returns a List of trades, filtered on the parameters given.
In live mode, converts the filter to a database query and returns all rows
- In Backtest mode, uses filters on Trade.trades to get the result.
+ In Backtest mode, uses filters on Trade.bt_trades to get the result.
:return: unsorted List[Trade]
"""
diff --git a/freqtrade/plugins/pairlist/PercentChangePairList.py b/freqtrade/plugins/pairlist/PercentChangePairList.py
new file mode 100644
index 000000000..b22891b98
--- /dev/null
+++ b/freqtrade/plugins/pairlist/PercentChangePairList.py
@@ -0,0 +1,329 @@
+"""
+Percent Change PairList provider
+
+Provides dynamic pair list based on trade change
+sorted based on percentage change in price over a
+defined period or as coming from ticker
+"""
+
+import logging
+from datetime import timedelta
+from typing import Any, Dict, List, Optional
+
+from cachetools import TTLCache
+from pandas import DataFrame
+
+from freqtrade.constants import ListPairsWithTimeframes, PairWithTimeframe
+from freqtrade.exceptions import OperationalException
+from freqtrade.exchange import timeframe_to_minutes, timeframe_to_prev_date
+from freqtrade.exchange.types import Ticker, Tickers
+from freqtrade.plugins.pairlist.IPairList import IPairList, PairlistParameter, SupportsBacktesting
+from freqtrade.util import dt_now, format_ms_time
+
+
+logger = logging.getLogger(__name__)
+
+
+class PercentChangePairList(IPairList):
+ is_pairlist_generator = True
+ supports_backtesting = SupportsBacktesting.NO
+
+ def __init__(self, *args, **kwargs) -> None:
+ super().__init__(*args, **kwargs)
+
+ if "number_assets" not in self._pairlistconfig:
+ raise OperationalException(
+ "`number_assets` not specified. Please check your configuration "
+ 'for "pairlist.config.number_assets"'
+ )
+
+ self._stake_currency = self._config["stake_currency"]
+ self._number_pairs = self._pairlistconfig["number_assets"]
+ self._min_value = self._pairlistconfig.get("min_value", None)
+ self._max_value = self._pairlistconfig.get("max_value", None)
+ self._refresh_period = self._pairlistconfig.get("refresh_period", 1800)
+ self._pair_cache: TTLCache = TTLCache(maxsize=1, ttl=self._refresh_period)
+ self._lookback_days = self._pairlistconfig.get("lookback_days", 0)
+ self._lookback_timeframe = self._pairlistconfig.get("lookback_timeframe", "1d")
+ self._lookback_period = self._pairlistconfig.get("lookback_period", 0)
+ self._sort_direction: Optional[str] = self._pairlistconfig.get("sort_direction", "desc")
+ self._def_candletype = self._config["candle_type_def"]
+
+ if (self._lookback_days > 0) & (self._lookback_period > 0):
+ raise OperationalException(
+ "Ambiguous configuration: lookback_days and lookback_period both set in pairlist "
+ "config. Please set lookback_days only or lookback_period and lookback_timeframe "
+ "and restart the bot."
+ )
+
+ # overwrite lookback timeframe and days when lookback_days is set
+ if self._lookback_days > 0:
+ self._lookback_timeframe = "1d"
+ self._lookback_period = self._lookback_days
+
+ # get timeframe in minutes and seconds
+ self._tf_in_min = timeframe_to_minutes(self._lookback_timeframe)
+ _tf_in_sec = self._tf_in_min * 60
+
+ # whether to use range lookback or not
+ self._use_range = (self._tf_in_min > 0) & (self._lookback_period > 0)
+
+ if self._use_range & (self._refresh_period < _tf_in_sec):
+ raise OperationalException(
+ f"Refresh period of {self._refresh_period} seconds is smaller than one "
+ f"timeframe of {self._lookback_timeframe}. Please adjust refresh_period "
+ f"to at least {_tf_in_sec} and restart the bot."
+ )
+
+ if not self._use_range and not (
+ self._exchange.exchange_has("fetchTickers")
+ and self._exchange.get_option("tickers_have_percentage")
+ ):
+ raise OperationalException(
+ "Exchange does not support dynamic whitelist in this configuration. "
+ "Please edit your config and either remove PercentChangePairList, "
+ "or switch to using candles. and restart the bot."
+ )
+
+ candle_limit = self._exchange.ohlcv_candle_limit(
+ self._lookback_timeframe, self._config["candle_type_def"]
+ )
+
+ if self._lookback_period > candle_limit:
+ raise OperationalException(
+ "ChangeFilter requires lookback_period to not "
+ f"exceed exchange max request size ({candle_limit})"
+ )
+
+ @property
+ def needstickers(self) -> bool:
+ """
+ Boolean property defining if tickers are necessary.
+ If no Pairlist requires tickers, an empty Dict is passed
+ as tickers argument to filter_pairlist
+ """
+ return not self._use_range
+
+ def short_desc(self) -> str:
+ """
+ Short whitelist method description - used for startup-messages
+ """
+ return f"{self.name} - top {self._pairlistconfig['number_assets']} percent change pairs."
+
+ @staticmethod
+ def description() -> str:
+ return "Provides dynamic pair list based on percentage change."
+
+ @staticmethod
+ def available_parameters() -> Dict[str, PairlistParameter]:
+ return {
+ "number_assets": {
+ "type": "number",
+ "default": 30,
+ "description": "Number of assets",
+ "help": "Number of assets to use from the pairlist",
+ },
+ "min_value": {
+ "type": "number",
+ "default": None,
+ "description": "Minimum value",
+ "help": "Minimum value to use for filtering the pairlist.",
+ },
+ "max_value": {
+ "type": "number",
+ "default": None,
+ "description": "Maximum value",
+ "help": "Maximum value to use for filtering the pairlist.",
+ },
+ "sort_direction": {
+ "type": "option",
+ "default": "desc",
+ "options": ["", "asc", "desc"],
+ "description": "Sort pairlist",
+ "help": "Sort Pairlist ascending or descending by rate of change.",
+ },
+ **IPairList.refresh_period_parameter(),
+ "lookback_days": {
+ "type": "number",
+ "default": 0,
+ "description": "Lookback Days",
+ "help": "Number of days to look back at.",
+ },
+ "lookback_timeframe": {
+ "type": "string",
+ "default": "1d",
+ "description": "Lookback Timeframe",
+ "help": "Timeframe to use for lookback.",
+ },
+ "lookback_period": {
+ "type": "number",
+ "default": 0,
+ "description": "Lookback Period",
+ "help": "Number of periods to look back at.",
+ },
+ }
+
+ def gen_pairlist(self, tickers: Tickers) -> List[str]:
+ """
+ Generate the pairlist
+ :param tickers: Tickers (from exchange.get_tickers). May be cached.
+ :return: List of pairs
+ """
+ pairlist = self._pair_cache.get("pairlist")
+ if pairlist:
+ # Item found - no refresh necessary
+ return pairlist.copy()
+ else:
+ # Use fresh pairlist
+ # Check if pair quote currency equals to the stake currency.
+ _pairlist = [
+ k
+ for k in self._exchange.get_markets(
+ quote_currencies=[self._stake_currency], tradable_only=True, active_only=True
+ ).keys()
+ ]
+
+ # No point in testing for blacklisted pairs...
+ _pairlist = self.verify_blacklist(_pairlist, logger.info)
+ if not self._use_range:
+ filtered_tickers = [
+ v
+ for k, v in tickers.items()
+ if (
+ self._exchange.get_pair_quote_currency(k) == self._stake_currency
+ and (self._use_range or v.get("percentage") is not None)
+ and v["symbol"] in _pairlist
+ )
+ ]
+ pairlist = [s["symbol"] for s in filtered_tickers]
+ else:
+ pairlist = _pairlist
+
+ pairlist = self.filter_pairlist(pairlist, tickers)
+ self._pair_cache["pairlist"] = pairlist.copy()
+
+ return pairlist
+
+ def filter_pairlist(self, pairlist: List[str], tickers: Dict) -> List[str]:
+ """
+ Filters and sorts pairlist and returns the whitelist again.
+ Called on each bot iteration - please use internal caching if necessary
+ :param pairlist: pairlist to filter or sort
+ :param tickers: Tickers (from exchange.get_tickers). May be cached.
+ :return: new whitelist
+ """
+ filtered_tickers: List[Dict[str, Any]] = [{"symbol": k} for k in pairlist]
+ if self._use_range:
+ # calculating using lookback_period
+ self.fetch_percent_change_from_lookback_period(filtered_tickers)
+ else:
+ # Fetching 24h change by default from supported exchange tickers
+ self.fetch_percent_change_from_tickers(filtered_tickers, tickers)
+
+ if self._min_value is not None:
+ filtered_tickers = [v for v in filtered_tickers if v["percentage"] > self._min_value]
+ if self._max_value is not None:
+ filtered_tickers = [v for v in filtered_tickers if v["percentage"] < self._max_value]
+
+ sorted_tickers = sorted(
+ filtered_tickers,
+ reverse=self._sort_direction == "desc",
+ key=lambda t: t["percentage"],
+ )
+
+ # Validate whitelist to only have active market pairs
+ pairs = self._whitelist_for_active_markets([s["symbol"] for s in sorted_tickers])
+ pairs = self.verify_blacklist(pairs, logmethod=logger.info)
+ # Limit pairlist to the requested number of pairs
+ pairs = pairs[: self._number_pairs]
+
+ return pairs
+
+ def fetch_candles_for_lookback_period(
+ self, filtered_tickers: List[Dict[str, str]]
+ ) -> Dict[PairWithTimeframe, DataFrame]:
+ since_ms = (
+ int(
+ timeframe_to_prev_date(
+ self._lookback_timeframe,
+ dt_now()
+ + timedelta(
+ minutes=-(self._lookback_period * self._tf_in_min) - self._tf_in_min
+ ),
+ ).timestamp()
+ )
+ * 1000
+ )
+ to_ms = (
+ int(
+ timeframe_to_prev_date(
+ self._lookback_timeframe, dt_now() - timedelta(minutes=self._tf_in_min)
+ ).timestamp()
+ )
+ * 1000
+ )
+ # todo: utc date output for starting date
+ self.log_once(
+ f"Using change range of {self._lookback_period} candles, timeframe: "
+ f"{self._lookback_timeframe}, starting from {format_ms_time(since_ms)} "
+ f"till {format_ms_time(to_ms)}",
+ logger.info,
+ )
+ needed_pairs: ListPairsWithTimeframes = [
+ (p, self._lookback_timeframe, self._def_candletype)
+ for p in [s["symbol"] for s in filtered_tickers]
+ if p not in self._pair_cache
+ ]
+ candles = self._exchange.refresh_ohlcv_with_cache(needed_pairs, since_ms)
+ return candles
+
+ def fetch_percent_change_from_lookback_period(self, filtered_tickers: List[Dict[str, Any]]):
+ # get lookback period in ms, for exchange ohlcv fetch
+ candles = self.fetch_candles_for_lookback_period(filtered_tickers)
+
+ for i, p in enumerate(filtered_tickers):
+ pair_candles = (
+ candles[(p["symbol"], self._lookback_timeframe, self._def_candletype)]
+ if (p["symbol"], self._lookback_timeframe, self._def_candletype) in candles
+ else None
+ )
+
+ # in case of candle data calculate typical price and change for candle
+ if pair_candles is not None and not pair_candles.empty:
+ current_close = pair_candles["close"].iloc[-1]
+ previous_close = pair_candles["close"].shift(self._lookback_period).iloc[-1]
+ pct_change = (
+ ((current_close - previous_close) / previous_close) if previous_close > 0 else 0
+ )
+
+ # replace change with a range change sum calculated above
+ filtered_tickers[i]["percentage"] = pct_change
+ else:
+ filtered_tickers[i]["percentage"] = 0
+
+ def fetch_percent_change_from_tickers(self, filtered_tickers: List[Dict[str, Any]], tickers):
+ for i, p in enumerate(filtered_tickers):
+ # Filter out assets
+ if not self._validate_pair(
+ p["symbol"], tickers[p["symbol"]] if p["symbol"] in tickers else None
+ ):
+ filtered_tickers.remove(p)
+ else:
+ filtered_tickers[i]["percentage"] = tickers[p["symbol"]]["percentage"]
+
+ def _validate_pair(self, pair: str, ticker: Optional[Ticker]) -> bool:
+ """
+ Check if one price-step (pip) is > than a certain barrier.
+ :param pair: Pair that's currently validated
+ :param ticker: ticker dict as returned from ccxt.fetch_ticker
+ :return: True if the pair can stay, false if it should be removed
+ """
+ if not ticker or "percentage" not in ticker or ticker["percentage"] is None:
+ self.log_once(
+ f"Removed {pair} from whitelist, because "
+ "ticker['percentage'] is empty (Usually no trade in the last 24h).",
+ logger.info,
+ )
+ return False
+
+ return True
diff --git a/freqtrade/plugins/protections/cooldown_period.py b/freqtrade/plugins/protections/cooldown_period.py
index 2948d17d0..d30bd87e5 100644
--- a/freqtrade/plugins/protections/cooldown_period.py
+++ b/freqtrade/plugins/protections/cooldown_period.py
@@ -18,19 +18,19 @@ class CooldownPeriod(IProtection):
"""
LockReason to use
"""
- return f"Cooldown period for {self.stop_duration_str}."
+ return f"Cooldown period for {self.unlock_reason_time_element}."
def short_desc(self) -> str:
"""
- Short method description - used for startup-messages
+ Short method description - used for startup messages
"""
- return f"{self.name} - Cooldown period of {self.stop_duration_str}."
+ return f"{self.name} - Cooldown period {self.unlock_reason_time_element}."
def _cooldown_period(self, pair: str, date_now: datetime) -> Optional[ProtectionReturn]:
"""
Get last trade for this pair
"""
- look_back_until = date_now - timedelta(minutes=self._stop_duration)
+ look_back_until = date_now - timedelta(minutes=self._lookback_period)
# filters = [
# Trade.is_open.is_(False),
# Trade.close_date > look_back_until,
@@ -42,8 +42,8 @@ class CooldownPeriod(IProtection):
# Get latest trade
# Ignore type error as we know we only get closed trades.
trade = sorted(trades, key=lambda t: t.close_date)[-1] # type: ignore
- self.log_once(f"Cooldown for {pair} for {self.stop_duration_str}.", logger.info)
- until = self.calculate_lock_end([trade], self._stop_duration)
+ self.log_once(f"Cooldown for {pair} {self.unlock_reason_time_element}.", logger.info)
+ until = self.calculate_lock_end([trade])
return ProtectionReturn(
lock=True,
diff --git a/freqtrade/plugins/protections/iprotection.py b/freqtrade/plugins/protections/iprotection.py
index 204a8b827..c4b039161 100644
--- a/freqtrade/plugins/protections/iprotection.py
+++ b/freqtrade/plugins/protections/iprotection.py
@@ -32,15 +32,19 @@ class IProtection(LoggingMixin, ABC):
self._config = config
self._protection_config = protection_config
self._stop_duration_candles: Optional[int] = None
+ self._stop_duration: int = 0
self._lookback_period_candles: Optional[int] = None
+ self._unlock_at: Optional[str] = None
tf_in_min = timeframe_to_minutes(config["timeframe"])
if "stop_duration_candles" in protection_config:
self._stop_duration_candles = int(protection_config.get("stop_duration_candles", 1))
self._stop_duration = tf_in_min * self._stop_duration_candles
+ elif "unlock_at" in protection_config:
+ self._unlock_at = protection_config.get("unlock_at")
else:
- self._stop_duration_candles = None
self._stop_duration = int(protection_config.get("stop_duration", 60))
+
if "lookback_period_candles" in protection_config:
self._lookback_period_candles = int(protection_config.get("lookback_period_candles", 1))
self._lookback_period = tf_in_min * self._lookback_period_candles
@@ -80,6 +84,16 @@ class IProtection(LoggingMixin, ABC):
else:
return f"{self._lookback_period} {plural(self._lookback_period, 'minute', 'minutes')}"
+ @property
+ def unlock_reason_time_element(self) -> str:
+ """
+ Output configured unlock time or stop duration
+ """
+ if self._unlock_at is not None:
+ return f"until {self._unlock_at}"
+ else:
+ return f"for {self.stop_duration_str}"
+
@abstractmethod
def short_desc(self) -> str:
"""
@@ -105,16 +119,23 @@ class IProtection(LoggingMixin, ABC):
If true, this pair will be locked with until
"""
- @staticmethod
- def calculate_lock_end(trades: List[LocalTrade], stop_minutes: int) -> datetime:
+ def calculate_lock_end(self, trades: List[LocalTrade]) -> datetime:
"""
Get lock end time
+ Implicitly uses `self._stop_duration` or `self._unlock_at` depending on the configuration.
"""
max_date: datetime = max([trade.close_date for trade in trades if trade.close_date])
# coming from Database, tzinfo is not set.
if max_date.tzinfo is None:
max_date = max_date.replace(tzinfo=timezone.utc)
- until = max_date + timedelta(minutes=stop_minutes)
+ if self._unlock_at is not None:
+ # unlock_at case with fixed hour of the day
+ hour, minutes = self._unlock_at.split(":")
+ unlock_at = max_date.replace(hour=int(hour), minute=int(minutes))
+ if unlock_at < max_date:
+ unlock_at += timedelta(days=1)
+ return unlock_at
+ until = max_date + timedelta(minutes=self._stop_duration)
return until
diff --git a/freqtrade/plugins/protections/low_profit_pairs.py b/freqtrade/plugins/protections/low_profit_pairs.py
index 360f6721c..f0023646a 100644
--- a/freqtrade/plugins/protections/low_profit_pairs.py
+++ b/freqtrade/plugins/protections/low_profit_pairs.py
@@ -36,7 +36,7 @@ class LowProfitPairs(IProtection):
"""
return (
f"{profit} < {self._required_profit} in {self.lookback_period_str}, "
- f"locking for {self.stop_duration_str}."
+ f"locking {self.unlock_reason_time_element}."
)
def _low_profit(
@@ -70,7 +70,7 @@ class LowProfitPairs(IProtection):
f"within {self._lookback_period} minutes.",
logger.info,
)
- until = self.calculate_lock_end(trades, self._stop_duration)
+ until = self.calculate_lock_end(trades)
return ProtectionReturn(
lock=True,
diff --git a/freqtrade/plugins/protections/max_drawdown_protection.py b/freqtrade/plugins/protections/max_drawdown_protection.py
index a1ba166fa..5939ee9f0 100644
--- a/freqtrade/plugins/protections/max_drawdown_protection.py
+++ b/freqtrade/plugins/protections/max_drawdown_protection.py
@@ -39,7 +39,7 @@ class MaxDrawdown(IProtection):
"""
return (
f"{drawdown} passed {self._max_allowed_drawdown} in {self.lookback_period_str}, "
- f"locking for {self.stop_duration_str}."
+ f"locking {self.unlock_reason_time_element}."
)
def _max_drawdown(self, date_now: datetime) -> Optional[ProtectionReturn]:
@@ -70,7 +70,8 @@ class MaxDrawdown(IProtection):
f" within {self.lookback_period_str}.",
logger.info,
)
- until = self.calculate_lock_end(trades, self._stop_duration)
+
+ until = self.calculate_lock_end(trades)
return ProtectionReturn(
lock=True,
diff --git a/freqtrade/plugins/protections/stoploss_guard.py b/freqtrade/plugins/protections/stoploss_guard.py
index a9aca20b4..da7437178 100644
--- a/freqtrade/plugins/protections/stoploss_guard.py
+++ b/freqtrade/plugins/protections/stoploss_guard.py
@@ -38,7 +38,7 @@ class StoplossGuard(IProtection):
"""
return (
f"{self._trade_limit} stoplosses in {self._lookback_period} min, "
- f"locking for {self._stop_duration} min."
+ f"locking {self.unlock_reason_time_element}."
)
def _stoploss_guard(
@@ -78,7 +78,7 @@ class StoplossGuard(IProtection):
f"stoplosses within {self._lookback_period} minutes.",
logger.info,
)
- until = self.calculate_lock_end(trades, self._stop_duration)
+ until = self.calculate_lock_end(trades)
return ProtectionReturn(
lock=True,
until=until,
diff --git a/freqtrade/rpc/api_server/api_backtest.py b/freqtrade/rpc/api_server/api_backtest.py
index 42b09de0a..4295d9d19 100644
--- a/freqtrade/rpc/api_server/api_backtest.py
+++ b/freqtrade/rpc/api_server/api_backtest.py
@@ -182,7 +182,7 @@ def api_get_backtest():
ApiBG.bt["bt"].progress.action if ApiBG.bt["bt"] else str(BacktestState.STARTUP)
),
"progress": ApiBG.bt["bt"].progress.progress if ApiBG.bt["bt"] else 0,
- "trade_count": len(LocalTrade.trades),
+ "trade_count": len(LocalTrade.bt_trades),
"status_msg": "Backtest running",
}
diff --git a/freqtrade/rpc/api_server/api_schemas.py b/freqtrade/rpc/api_server/api_schemas.py
index 0e36c0992..6ba65d0ec 100644
--- a/freqtrade/rpc/api_server/api_schemas.py
+++ b/freqtrade/rpc/api_server/api_schemas.py
@@ -67,7 +67,6 @@ class Balance(BaseModel):
stake: str
# Starting with 2.x
side: str
- leverage: float
is_position: bool
position: float
is_bot_managed: bool
diff --git a/freqtrade/rpc/api_server/api_ws.py b/freqtrade/rpc/api_server/api_ws.py
index 5e2eddc68..ed458165e 100644
--- a/freqtrade/rpc/api_server/api_ws.py
+++ b/freqtrade/rpc/api_server/api_ws.py
@@ -58,7 +58,7 @@ async def channel_broadcaster(channel: WebSocketChannel, message_stream: Message
" consumers."
)
- await channel.send(message, timeout=True)
+ await channel.send(message, use_timeout=True)
async def _process_consumer_request(request: Dict[str, Any], channel: WebSocketChannel, rpc: RPC):
diff --git a/freqtrade/rpc/api_server/uvicorn_threaded.py b/freqtrade/rpc/api_server/uvicorn_threaded.py
index d717c5567..07faaff53 100644
--- a/freqtrade/rpc/api_server/uvicorn_threaded.py
+++ b/freqtrade/rpc/api_server/uvicorn_threaded.py
@@ -8,6 +8,7 @@ def asyncio_setup() -> None: # pragma: no cover
# Set eventloop for win32 setups
# Reverts a change done in uvicorn 0.15.0 - which now sets the eventloop
# via policy.
+ # TODO: is this workaround actually needed?
import sys
if sys.version_info >= (3, 8) and sys.platform == "win32":
diff --git a/freqtrade/rpc/api_server/ws/channel.py b/freqtrade/rpc/api_server/ws/channel.py
index 0041bb6b2..3c1e0ce2d 100644
--- a/freqtrade/rpc/api_server/ws/channel.py
+++ b/freqtrade/rpc/api_server/ws/channel.py
@@ -80,7 +80,7 @@ class WebSocketChannel:
self._send_high_limit = min(max(self.avg_send_time * 2, 1), 3)
async def send(
- self, message: Union[WSMessageSchemaType, Dict[str, Any]], timeout: bool = False
+ self, message: Union[WSMessageSchemaType, Dict[str, Any]], use_timeout: bool = False
):
"""
Send a message on the wrapped websocket. If the sending
@@ -88,7 +88,7 @@ class WebSocketChannel:
disconnect the connection.
:param message: The message to send
- :param timeout: Enforce send high limit, defaults to False
+ :param use_timeout: Enforce send high limit, defaults to False
"""
try:
_ = time.time()
@@ -96,7 +96,8 @@ class WebSocketChannel:
# a TimeoutError and bubble up to the
# message_endpoint to close the connection
await asyncio.wait_for(
- self._wrapped_ws.send(message), timeout=self._send_high_limit if timeout else None
+ self._wrapped_ws.send(message),
+ timeout=self._send_high_limit if use_timeout else None,
)
total_time = time.time() - _
self._send_times.append(total_time)
diff --git a/freqtrade/rpc/rpc.py b/freqtrade/rpc/rpc.py
index 3feb4860c..0c555e860 100644
--- a/freqtrade/rpc/rpc.py
+++ b/freqtrade/rpc/rpc.py
@@ -296,7 +296,10 @@ class RPC:
else:
trade_profit = 0.0
profit_str = f"{0.0:.2f}"
- direction_str = ("S" if trade.is_short else "L") if nonspot else ""
+ leverage = f"{trade.leverage:.3g}"
+ direction_str = (
+ (f"S {leverage}x" if trade.is_short else f"L {leverage}x") if nonspot else ""
+ )
if self._fiat_converter:
fiat_profit = self._fiat_converter.convert_amount(
trade_profit, stake_currency, fiat_display_currency
@@ -742,7 +745,6 @@ class RPC:
"est_stake_bot": est_stake_bot if is_bot_managed else 0,
"stake": stake_currency,
"side": "long",
- "leverage": 1,
"position": 0,
"is_bot_managed": is_bot_managed,
"is_position": False,
@@ -764,7 +766,6 @@ class RPC:
"est_stake": position.collateral,
"est_stake_bot": position.collateral,
"stake": stake_currency,
- "leverage": position.leverage,
"side": position.side,
"is_bot_managed": True,
"is_position": True,
diff --git a/freqtrade/rpc/telegram.py b/freqtrade/rpc/telegram.py
index 8eaa970c8..22b574621 100644
--- a/freqtrade/rpc/telegram.py
+++ b/freqtrade/rpc/telegram.py
@@ -1133,7 +1133,6 @@ class Telegram(RPCHandler):
curr_output = (
f"*{curr['currency']}:*\n"
f"\t`{curr['side']}: {curr['position']:.8f}`\n"
- f"\t`Leverage: {curr['leverage']:.1f}`\n"
f"\t`Est. {curr['stake']}: "
f"{fmt_coin(curr['est_stake'], curr['stake'], False)}`\n"
)
diff --git a/freqtrade/strategy/__init__.py b/freqtrade/strategy/__init__.py
index bb21100c4..e99473b4e 100644
--- a/freqtrade/strategy/__init__.py
+++ b/freqtrade/strategy/__init__.py
@@ -6,6 +6,7 @@ from freqtrade.exchange import (
timeframe_to_prev_date,
timeframe_to_seconds,
)
+from freqtrade.persistence import Order, PairLocks, Trade
from freqtrade.strategy.informative_decorator import informative
from freqtrade.strategy.interface import IStrategy
from freqtrade.strategy.parameters import (
@@ -20,3 +21,27 @@ from freqtrade.strategy.strategy_helper import (
stoploss_from_absolute,
stoploss_from_open,
)
+
+
+# Imports to be used for `from freqtrade.strategy import *`
+__all__ = [
+ "IStrategy",
+ "Trade",
+ "Order",
+ "PairLocks",
+ "informative",
+ # Parameters
+ "BooleanParameter",
+ "CategoricalParameter",
+ "DecimalParameter",
+ "IntParameter",
+ "RealParameter",
+ # timeframe helpers
+ "timeframe_to_minutes",
+ "timeframe_to_next_date",
+ "timeframe_to_prev_date",
+ # Strategy helper functions
+ "merge_informative_pair",
+ "stoploss_from_absolute",
+ "stoploss_from_open",
+]
diff --git a/freqtrade/templates/base_config.json.j2 b/freqtrade/templates/base_config.json.j2
index 4956cf056..86a717a40 100644
--- a/freqtrade/templates/base_config.json.j2
+++ b/freqtrade/templates/base_config.json.j2
@@ -6,6 +6,7 @@
"refresh_period": 1800
}' %}
{
+ "$schema": "https://schema.freqtrade.io/schema.json",
"max_open_trades": {{ max_open_trades }},
"stake_currency": "{{ stake_currency }}",
"stake_amount": {{ stake_amount }},
diff --git a/freqtrade/templates/base_strategy.py.j2 b/freqtrade/templates/base_strategy.py.j2
index a4e0a2b24..fe577826a 100644
--- a/freqtrade/templates/base_strategy.py.j2
+++ b/freqtrade/templates/base_strategy.py.j2
@@ -1,15 +1,34 @@
# pragma pylint: disable=missing-docstring, invalid-name, pointless-string-statement
# flake8: noqa: F401
# isort: skip_file
-# --- Do not remove these libs ---
+# --- Do not remove these imports ---
import numpy as np
import pandas as pd
+from datetime import datetime, timedelta, timezone
from pandas import DataFrame
-from datetime import datetime
-from typing import Optional, Union
+from typing import Dict, Optional, Union, Tuple
-from freqtrade.strategy import (BooleanParameter, CategoricalParameter, DecimalParameter,
- IntParameter, IStrategy, merge_informative_pair)
+from freqtrade.strategy import (
+ IStrategy,
+ Trade,
+ Order,
+ PairLocks,
+ informative, # @informative decorator
+ # Hyperopt Parameters
+ BooleanParameter,
+ CategoricalParameter,
+ DecimalParameter,
+ IntParameter,
+ RealParameter,
+ # timeframe helpers
+ timeframe_to_minutes,
+ timeframe_to_next_date,
+ timeframe_to_prev_date,
+ # Strategy helper functions
+ merge_informative_pair,
+ stoploss_from_absolute,
+ stoploss_from_open,
+)
# --------------------------------
# Add your lib to import here
@@ -40,7 +59,7 @@ class {{ strategy }}(IStrategy):
INTERFACE_VERSION = 3
# Optimal timeframe for the strategy.
- timeframe = '5m'
+ timeframe = "5m"
# Can this strategy go short?
can_short: bool = False
@@ -78,8 +97,8 @@ class {{ strategy }}(IStrategy):
buy_rsi = IntParameter(10, 40, default=30, space="buy")
sell_rsi = IntParameter(60, 90, default=70, space="sell")
- {{ attributes | indent(4) }}
- {{ plot_config | indent(4) }}
+ {{- attributes | indent(4) }}
+ {{- plot_config | indent(4) }}
def informative_pairs(self):
"""
@@ -105,7 +124,7 @@ class {{ strategy }}(IStrategy):
:param metadata: Additional information, like the currently traded pair
:return: a Dataframe with all mandatory indicators for the strategies
"""
- {{ indicators | indent(8) }}
+ {{- indicators | indent(8) }}
return dataframe
@@ -119,9 +138,9 @@ class {{ strategy }}(IStrategy):
dataframe.loc[
(
{{ buy_trend | indent(16) }}
- (dataframe['volume'] > 0) # Make sure Volume is not 0
+ (dataframe["volume"] > 0) # Make sure Volume is not 0
),
- 'enter_long'] = 1
+ "enter_long"] = 1
# Uncomment to use shorts (Only used in futures/margin mode. Check the documentation for more info)
"""
dataframe.loc[
@@ -144,9 +163,9 @@ class {{ strategy }}(IStrategy):
dataframe.loc[
(
{{ sell_trend | indent(16) }}
- (dataframe['volume'] > 0) # Make sure Volume is not 0
+ (dataframe["volume"] > 0) # Make sure Volume is not 0
),
- 'exit_long'] = 1
+ "exit_long"] = 1
# Uncomment to use shorts (Only used in futures/margin mode. Check the documentation for more info)
"""
dataframe.loc[
@@ -157,4 +176,4 @@ class {{ strategy }}(IStrategy):
'exit_short'] = 1
"""
return dataframe
- {{ additional_methods | indent(4) }}
+ {{- additional_methods | indent(4) }}
diff --git a/freqtrade/templates/sample_strategy.py b/freqtrade/templates/sample_strategy.py
index 033c0d24e..835e6fa91 100644
--- a/freqtrade/templates/sample_strategy.py
+++ b/freqtrade/templates/sample_strategy.py
@@ -1,24 +1,39 @@
# pragma pylint: disable=missing-docstring, invalid-name, pointless-string-statement
# flake8: noqa: F401
# isort: skip_file
-# --- Do not remove these libs ---
-import numpy as np # noqa
-import pandas as pd # noqa
+# --- Do not remove these imports ---
+import numpy as np
+import pandas as pd
+from datetime import datetime, timedelta, timezone
from pandas import DataFrame
-from typing import Optional, Union
+from typing import Dict, Optional, Union, Tuple
from freqtrade.strategy import (
+ IStrategy,
+ Trade,
+ Order,
+ PairLocks,
+ informative, # @informative decorator
+ # Hyperopt Parameters
BooleanParameter,
CategoricalParameter,
DecimalParameter,
- IStrategy,
IntParameter,
+ RealParameter,
+ # timeframe helpers
+ timeframe_to_minutes,
+ timeframe_to_next_date,
+ timeframe_to_prev_date,
+ # Strategy helper functions
+ merge_informative_pair,
+ stoploss_from_absolute,
+ stoploss_from_open,
)
# --------------------------------
# Add your lib to import here
import talib.abstract as ta
-import freqtrade.vendor.qtpylib.indicators as qtpylib
+from technical import qtpylib
# This class is a sample. Feel free to customize it.
diff --git a/freqtrade/templates/strategy_analysis_example.ipynb b/freqtrade/templates/strategy_analysis_example.ipynb
index 8d4459a3c..e81ff72ca 100644
--- a/freqtrade/templates/strategy_analysis_example.ipynb
+++ b/freqtrade/templates/strategy_analysis_example.ipynb
@@ -29,19 +29,22 @@
"import os\n",
"from pathlib import Path\n",
"\n",
+ "\n",
"# Change directory\n",
"# Modify this cell to insure that the output shows the correct path.\n",
"# Define all paths relative to the project root shown in the cell output\n",
"project_root = \"somedir/freqtrade\"\n",
- "i=0\n",
+ "i = 0\n",
"try:\n",
" os.chdir(project_root)\n",
- " assert Path('LICENSE').is_file()\n",
- "except:\n",
- " while i<4 and (not Path('LICENSE').is_file()):\n",
- " os.chdir(Path(Path.cwd(), '../'))\n",
- " i+=1\n",
- " project_root = Path.cwd()\n",
+ " if not Path(\"LICENSE\").is_file():\n",
+ " i = 0\n",
+ " while i < 4 and (not Path(\"LICENSE\").is_file()):\n",
+ " os.chdir(Path(Path.cwd(), \"../\"))\n",
+ " i += 1\n",
+ " project_root = Path.cwd()\n",
+ "except FileNotFoundError:\n",
+ " print(\"Please define the project root relative to the current directory\")\n",
"print(Path.cwd())"
]
},
@@ -60,6 +63,7 @@
"source": [
"from freqtrade.configuration import Configuration\n",
"\n",
+ "\n",
"# Customize these according to your needs.\n",
"\n",
"# Initialize empty configuration object\n",
@@ -87,12 +91,14 @@
"from freqtrade.data.history import load_pair_history\n",
"from freqtrade.enums import CandleType\n",
"\n",
- "candles = load_pair_history(datadir=data_location,\n",
- " timeframe=config[\"timeframe\"],\n",
- " pair=pair,\n",
- " data_format = \"json\", # Make sure to update this to your data\n",
- " candle_type=CandleType.SPOT,\n",
- " )\n",
+ "\n",
+ "candles = load_pair_history(\n",
+ " datadir=data_location,\n",
+ " timeframe=config[\"timeframe\"],\n",
+ " pair=pair,\n",
+ " data_format=\"json\", # Make sure to update this to your data\n",
+ " candle_type=CandleType.SPOT,\n",
+ ")\n",
"\n",
"# Confirm success\n",
"print(f\"Loaded {len(candles)} rows of data for {pair} from {data_location}\")\n",
@@ -114,14 +120,16 @@
"outputs": [],
"source": [
"# Load strategy using values set above\n",
- "from freqtrade.resolvers import StrategyResolver\n",
"from freqtrade.data.dataprovider import DataProvider\n",
+ "from freqtrade.resolvers import StrategyResolver\n",
+ "\n",
+ "\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",
+ "df = strategy.analyze_ticker(candles, {\"pair\": pair})\n",
"df.tail()"
]
},
@@ -148,7 +156,7 @@
"source": [
"# Report results\n",
"print(f\"Generated {df['enter_long'].sum()} entry signals\")\n",
- "data = df.set_index('date', drop=False)\n",
+ "data = df.set_index(\"date\", drop=False)\n",
"data.tail()"
]
},
@@ -179,10 +187,13 @@
"source": [
"from freqtrade.data.btanalysis import load_backtest_data, load_backtest_stats\n",
"\n",
+ "\n",
"# if backtest_dir points to a directory, it'll automatically load the last backtest file.\n",
"backtest_dir = config[\"user_data_dir\"] / \"backtest_results\"\n",
"# backtest_dir can also point to a specific file\n",
- "# backtest_dir = config[\"user_data_dir\"] / \"backtest_results/backtest-result-2020-07-01_20-04-22.json\""
+ "# backtest_dir = (\n",
+ "# config[\"user_data_dir\"] / \"backtest_results/backtest-result-2020-07-01_20-04-22.json\"\n",
+ "# )"
]
},
{
@@ -195,23 +206,24 @@
"# This contains all information used to generate the backtest result.\n",
"stats = load_backtest_stats(backtest_dir)\n",
"\n",
- "strategy = 'SampleStrategy'\n",
- "# All statistics are available per strategy, so if `--strategy-list` was used during backtest, this will be reflected here as well.\n",
+ "strategy = \"SampleStrategy\"\n",
+ "# All statistics are available per strategy, so if `--strategy-list` was used during backtest,\n",
+ "# this will be reflected here as well.\n",
"# Example usages:\n",
- "print(stats['strategy'][strategy]['results_per_pair'])\n",
+ "print(stats[\"strategy\"][strategy][\"results_per_pair\"])\n",
"# Get pairlist used for this backtest\n",
- "print(stats['strategy'][strategy]['pairlist'])\n",
+ "print(stats[\"strategy\"][strategy][\"pairlist\"])\n",
"# Get market change (average change of all pairs from start to end of the backtest period)\n",
- "print(stats['strategy'][strategy]['market_change'])\n",
+ "print(stats[\"strategy\"][strategy][\"market_change\"])\n",
"# Maximum drawdown ()\n",
- "print(stats['strategy'][strategy]['max_drawdown'])\n",
+ "print(stats[\"strategy\"][strategy][\"max_drawdown\"])\n",
"# Maximum drawdown start and end\n",
- "print(stats['strategy'][strategy]['drawdown_start'])\n",
- "print(stats['strategy'][strategy]['drawdown_end'])\n",
+ "print(stats[\"strategy\"][strategy][\"drawdown_start\"])\n",
+ "print(stats[\"strategy\"][strategy][\"drawdown_end\"])\n",
"\n",
"\n",
"# Get strategy comparison (only relevant if multiple strategies were compared)\n",
- "print(stats['strategy_comparison'])\n"
+ "print(stats[\"strategy_comparison\"])"
]
},
{
@@ -242,23 +254,25 @@
"source": [
"# Plotting equity line (starting with 0 on day 1 and adding daily profit for each backtested day)\n",
"\n",
+ "import pandas as pd\n",
+ "import plotly.express as px\n",
+ "\n",
"from freqtrade.configuration import Configuration\n",
"from freqtrade.data.btanalysis import load_backtest_stats\n",
- "import plotly.express as px\n",
- "import pandas as pd\n",
+ "\n",
"\n",
"# strategy = 'SampleStrategy'\n",
"# config = Configuration.from_files([\"user_data/config.json\"])\n",
"# backtest_dir = config[\"user_data_dir\"] / \"backtest_results\"\n",
"\n",
"stats = load_backtest_stats(backtest_dir)\n",
- "strategy_stats = stats['strategy'][strategy]\n",
+ "strategy_stats = stats[\"strategy\"][strategy]\n",
"\n",
- "df = pd.DataFrame(columns=['dates','equity'], data=strategy_stats['daily_profit'])\n",
- "df['equity_daily'] = df['equity'].cumsum()\n",
+ "df = pd.DataFrame(columns=[\"dates\", \"equity\"], data=strategy_stats[\"daily_profit\"])\n",
+ "df[\"equity_daily\"] = df[\"equity\"].cumsum()\n",
"\n",
"fig = px.line(df, x=\"dates\", y=\"equity_daily\")\n",
- "fig.show()\n"
+ "fig.show()"
]
},
{
@@ -278,6 +292,7 @@
"source": [
"from freqtrade.data.btanalysis import load_trades_from_db\n",
"\n",
+ "\n",
"# Fetch trades from database\n",
"trades = load_trades_from_db(\"sqlite:///tradesv3.sqlite\")\n",
"\n",
@@ -303,8 +318,9 @@
"source": [
"from freqtrade.data.btanalysis import analyze_trade_parallelism\n",
"\n",
+ "\n",
"# Analyze the above\n",
- "parallel_trades = analyze_trade_parallelism(trades, '5m')\n",
+ "parallel_trades = analyze_trade_parallelism(trades, \"5m\")\n",
"\n",
"parallel_trades.plot()"
]
@@ -324,22 +340,23 @@
"metadata": {},
"outputs": [],
"source": [
- "from freqtrade.plot.plotting import generate_candlestick_graph\n",
+ "from freqtrade.plot.plotting import generate_candlestick_graph\n",
+ "\n",
+ "\n",
"# Limit graph period to keep plotly quick and reactive\n",
"\n",
"# Filter trades to one pair\n",
- "trades_red = trades.loc[trades['pair'] == pair]\n",
+ "trades_red = trades.loc[trades[\"pair\"] == pair]\n",
"\n",
- "data_red = data['2019-06-01':'2019-06-10']\n",
+ "data_red = data[\"2019-06-01\":\"2019-06-10\"]\n",
"# Generate candlestick graph\n",
- "graph = generate_candlestick_graph(pair=pair,\n",
- " data=data_red,\n",
- " trades=trades_red,\n",
- " indicators1=['sma20', 'ema50', 'ema55'],\n",
- " indicators2=['rsi', 'macd', 'macdsignal', 'macdhist']\n",
- " )\n",
- "\n",
- "\n"
+ "graph = generate_candlestick_graph(\n",
+ " pair=pair,\n",
+ " data=data_red,\n",
+ " trades=trades_red,\n",
+ " indicators1=[\"sma20\", \"ema50\", \"ema55\"],\n",
+ " indicators2=[\"rsi\", \"macd\", \"macdsignal\", \"macdhist\"],\n",
+ ")"
]
},
{
@@ -352,7 +369,7 @@
"# graph.show()\n",
"\n",
"# Render graph in a separate window\n",
- "graph.show(renderer=\"browser\")\n"
+ "graph.show(renderer=\"browser\")"
]
},
{
@@ -370,11 +387,12 @@
"source": [
"import plotly.figure_factory as ff\n",
"\n",
+ "\n",
"hist_data = [trades.profit_ratio]\n",
- "group_labels = ['profit_ratio'] # name of the dataset\n",
+ "group_labels = [\"profit_ratio\"] # name of the dataset\n",
"\n",
"fig = ff.create_distplot(hist_data, group_labels, bin_size=0.01)\n",
- "fig.show()\n"
+ "fig.show()"
]
},
{
diff --git a/freqtrade/templates/strategy_subtemplates/buy_trend_full.j2 b/freqtrade/templates/strategy_subtemplates/buy_trend_full.j2
index aac8325a7..7a50fd4dc 100644
--- a/freqtrade/templates/strategy_subtemplates/buy_trend_full.j2
+++ b/freqtrade/templates/strategy_subtemplates/buy_trend_full.j2
@@ -1,3 +1,3 @@
-(qtpylib.crossed_above(dataframe['rsi'], self.buy_rsi.value)) & # Signal: RSI crosses above buy_rsi
-(dataframe['tema'] <= dataframe['bb_middleband']) & # Guard: tema below BB middle
-(dataframe['tema'] > dataframe['tema'].shift(1)) & # Guard: tema is raising
+(qtpylib.crossed_above(dataframe["rsi"], self.buy_rsi.value)) & # Signal: RSI crosses above buy_rsi
+(dataframe["tema"] <= dataframe["bb_middleband"]) & # Guard: tema below BB middle
+(dataframe["tema"] > dataframe["tema"].shift(1)) & # Guard: tema is raising
diff --git a/freqtrade/templates/strategy_subtemplates/buy_trend_minimal.j2 b/freqtrade/templates/strategy_subtemplates/buy_trend_minimal.j2
index e89d3779e..bcecacc3c 100644
--- a/freqtrade/templates/strategy_subtemplates/buy_trend_minimal.j2
+++ b/freqtrade/templates/strategy_subtemplates/buy_trend_minimal.j2
@@ -1 +1 @@
-(qtpylib.crossed_above(dataframe['rsi'], self.buy_rsi.value)) & # Signal: RSI crosses above buy_rsi
+(qtpylib.crossed_above(dataframe["rsi"], self.buy_rsi.value)) & # Signal: RSI crosses above buy_rsi
diff --git a/freqtrade/templates/strategy_subtemplates/indicators_full.j2 b/freqtrade/templates/strategy_subtemplates/indicators_full.j2
index a497b47cb..e4c4daac4 100644
--- a/freqtrade/templates/strategy_subtemplates/indicators_full.j2
+++ b/freqtrade/templates/strategy_subtemplates/indicators_full.j2
@@ -3,24 +3,24 @@
# ------------------------------------
# ADX
-dataframe['adx'] = ta.ADX(dataframe)
+dataframe["adx"] = ta.ADX(dataframe)
# # Plus Directional Indicator / Movement
-# dataframe['plus_dm'] = ta.PLUS_DM(dataframe)
-# dataframe['plus_di'] = ta.PLUS_DI(dataframe)
+# dataframe["plus_dm"] = ta.PLUS_DM(dataframe)
+# dataframe["plus_di"] = ta.PLUS_DI(dataframe)
# # Minus Directional Indicator / Movement
-# dataframe['minus_dm'] = ta.MINUS_DM(dataframe)
-# dataframe['minus_di'] = ta.MINUS_DI(dataframe)
+# dataframe["minus_dm"] = ta.MINUS_DM(dataframe)
+# dataframe["minus_di"] = ta.MINUS_DI(dataframe)
# # Aroon, Aroon Oscillator
# aroon = ta.AROON(dataframe)
-# dataframe['aroonup'] = aroon['aroonup']
-# dataframe['aroondown'] = aroon['aroondown']
-# dataframe['aroonosc'] = ta.AROONOSC(dataframe)
+# dataframe["aroonup"] = aroon["aroonup"]
+# dataframe["aroondown"] = aroon["aroondown"]
+# dataframe["aroonosc"] = ta.AROONOSC(dataframe)
# # Awesome Oscillator
-# dataframe['ao'] = qtpylib.awesome_oscillator(dataframe)
+# dataframe["ao"] = qtpylib.awesome_oscillator(dataframe)
# # Keltner Channel
# keltner = qtpylib.keltner_channel(dataframe)
@@ -36,58 +36,58 @@ dataframe['adx'] = ta.ADX(dataframe)
# )
# # Ultimate Oscillator
-# dataframe['uo'] = ta.ULTOSC(dataframe)
+# dataframe["uo"] = ta.ULTOSC(dataframe)
# # Commodity Channel Index: values [Oversold:-100, Overbought:100]
-# dataframe['cci'] = ta.CCI(dataframe)
+# dataframe["cci"] = ta.CCI(dataframe)
# RSI
-dataframe['rsi'] = ta.RSI(dataframe)
+dataframe["rsi"] = ta.RSI(dataframe)
# # Inverse Fisher transform on RSI: values [-1.0, 1.0] (https://goo.gl/2JGGoy)
-# rsi = 0.1 * (dataframe['rsi'] - 50)
-# dataframe['fisher_rsi'] = (np.exp(2 * rsi) - 1) / (np.exp(2 * rsi) + 1)
+# rsi = 0.1 * (dataframe["rsi"] - 50)
+# dataframe["fisher_rsi"] = (np.exp(2 * rsi) - 1) / (np.exp(2 * rsi) + 1)
# # Inverse Fisher transform on RSI normalized: values [0.0, 100.0] (https://goo.gl/2JGGoy)
-# dataframe['fisher_rsi_norma'] = 50 * (dataframe['fisher_rsi'] + 1)
+# dataframe["fisher_rsi_norma"] = 50 * (dataframe["fisher_rsi"] + 1)
# # Stochastic Slow
# stoch = ta.STOCH(dataframe)
-# dataframe['slowd'] = stoch['slowd']
-# dataframe['slowk'] = stoch['slowk']
+# dataframe["slowd"] = stoch["slowd"]
+# dataframe["slowk"] = stoch["slowk"]
# Stochastic Fast
stoch_fast = ta.STOCHF(dataframe)
-dataframe['fastd'] = stoch_fast['fastd']
-dataframe['fastk'] = stoch_fast['fastk']
+dataframe["fastd"] = stoch_fast["fastd"]
+dataframe["fastk"] = stoch_fast["fastk"]
# # Stochastic RSI
# Please read https://github.com/freqtrade/freqtrade/issues/2961 before using this.
# STOCHRSI is NOT aligned with tradingview, which may result in non-expected results.
# stoch_rsi = ta.STOCHRSI(dataframe)
-# dataframe['fastd_rsi'] = stoch_rsi['fastd']
-# dataframe['fastk_rsi'] = stoch_rsi['fastk']
+# dataframe["fastd_rsi"] = stoch_rsi["fastd"]
+# dataframe["fastk_rsi"] = stoch_rsi["fastk"]
# MACD
macd = ta.MACD(dataframe)
-dataframe['macd'] = macd['macd']
-dataframe['macdsignal'] = macd['macdsignal']
-dataframe['macdhist'] = macd['macdhist']
+dataframe["macd"] = macd["macd"]
+dataframe["macdsignal"] = macd["macdsignal"]
+dataframe["macdhist"] = macd["macdhist"]
# MFI
-dataframe['mfi'] = ta.MFI(dataframe)
+dataframe["mfi"] = ta.MFI(dataframe)
# # ROC
-# dataframe['roc'] = ta.ROC(dataframe)
+# dataframe["roc"] = ta.ROC(dataframe)
# Overlap Studies
# ------------------------------------
# Bollinger Bands
bollinger = qtpylib.bollinger_bands(qtpylib.typical_price(dataframe), window=20, stds=2)
-dataframe['bb_lowerband'] = bollinger['lower']
-dataframe['bb_middleband'] = bollinger['mid']
-dataframe['bb_upperband'] = bollinger['upper']
+dataframe["bb_lowerband"] = bollinger["lower"]
+dataframe["bb_middleband"] = bollinger["mid"]
+dataframe["bb_upperband"] = bollinger["upper"]
dataframe["bb_percent"] = (
(dataframe["close"] - dataframe["bb_lowerband"]) /
(dataframe["bb_upperband"] - dataframe["bb_lowerband"])
@@ -112,95 +112,95 @@ dataframe["bb_width"] = (
# )
# # EMA - Exponential Moving Average
-# dataframe['ema3'] = ta.EMA(dataframe, timeperiod=3)
-# dataframe['ema5'] = ta.EMA(dataframe, timeperiod=5)
-# dataframe['ema10'] = ta.EMA(dataframe, timeperiod=10)
-# dataframe['ema21'] = ta.EMA(dataframe, timeperiod=21)
-# dataframe['ema50'] = ta.EMA(dataframe, timeperiod=50)
-# dataframe['ema100'] = ta.EMA(dataframe, timeperiod=100)
+# dataframe["ema3"] = ta.EMA(dataframe, timeperiod=3)
+# dataframe["ema5"] = ta.EMA(dataframe, timeperiod=5)
+# dataframe["ema10"] = ta.EMA(dataframe, timeperiod=10)
+# dataframe["ema21"] = ta.EMA(dataframe, timeperiod=21)
+# dataframe["ema50"] = ta.EMA(dataframe, timeperiod=50)
+# dataframe["ema100"] = ta.EMA(dataframe, timeperiod=100)
# # SMA - Simple Moving Average
-# dataframe['sma3'] = ta.SMA(dataframe, timeperiod=3)
-# dataframe['sma5'] = ta.SMA(dataframe, timeperiod=5)
-# dataframe['sma10'] = ta.SMA(dataframe, timeperiod=10)
-# dataframe['sma21'] = ta.SMA(dataframe, timeperiod=21)
-# dataframe['sma50'] = ta.SMA(dataframe, timeperiod=50)
-# dataframe['sma100'] = ta.SMA(dataframe, timeperiod=100)
+# dataframe["sma3"] = ta.SMA(dataframe, timeperiod=3)
+# dataframe["sma5"] = ta.SMA(dataframe, timeperiod=5)
+# dataframe["sma10"] = ta.SMA(dataframe, timeperiod=10)
+# dataframe["sma21"] = ta.SMA(dataframe, timeperiod=21)
+# dataframe["sma50"] = ta.SMA(dataframe, timeperiod=50)
+# dataframe["sma100"] = ta.SMA(dataframe, timeperiod=100)
# Parabolic SAR
-dataframe['sar'] = ta.SAR(dataframe)
+dataframe["sar"] = ta.SAR(dataframe)
# TEMA - Triple Exponential Moving Average
-dataframe['tema'] = ta.TEMA(dataframe, timeperiod=9)
+dataframe["tema"] = ta.TEMA(dataframe, timeperiod=9)
# Cycle Indicator
# ------------------------------------
# Hilbert Transform Indicator - SineWave
hilbert = ta.HT_SINE(dataframe)
-dataframe['htsine'] = hilbert['sine']
-dataframe['htleadsine'] = hilbert['leadsine']
+dataframe["htsine"] = hilbert["sine"]
+dataframe["htleadsine"] = hilbert["leadsine"]
# Pattern Recognition - Bullish candlestick patterns
# ------------------------------------
# # Hammer: values [0, 100]
-# dataframe['CDLHAMMER'] = ta.CDLHAMMER(dataframe)
+# dataframe["CDLHAMMER"] = ta.CDLHAMMER(dataframe)
# # Inverted Hammer: values [0, 100]
-# dataframe['CDLINVERTEDHAMMER'] = ta.CDLINVERTEDHAMMER(dataframe)
+# dataframe["CDLINVERTEDHAMMER"] = ta.CDLINVERTEDHAMMER(dataframe)
# # Dragonfly Doji: values [0, 100]
-# dataframe['CDLDRAGONFLYDOJI'] = ta.CDLDRAGONFLYDOJI(dataframe)
+# dataframe["CDLDRAGONFLYDOJI"] = ta.CDLDRAGONFLYDOJI(dataframe)
# # Piercing Line: values [0, 100]
-# dataframe['CDLPIERCING'] = ta.CDLPIERCING(dataframe) # values [0, 100]
+# dataframe["CDLPIERCING"] = ta.CDLPIERCING(dataframe) # values [0, 100]
# # Morningstar: values [0, 100]
-# dataframe['CDLMORNINGSTAR'] = ta.CDLMORNINGSTAR(dataframe) # values [0, 100]
+# dataframe["CDLMORNINGSTAR"] = ta.CDLMORNINGSTAR(dataframe) # values [0, 100]
# # Three White Soldiers: values [0, 100]
-# dataframe['CDL3WHITESOLDIERS'] = ta.CDL3WHITESOLDIERS(dataframe) # values [0, 100]
+# dataframe["CDL3WHITESOLDIERS"] = ta.CDL3WHITESOLDIERS(dataframe) # values [0, 100]
# Pattern Recognition - Bearish candlestick patterns
# ------------------------------------
# # Hanging Man: values [0, 100]
-# dataframe['CDLHANGINGMAN'] = ta.CDLHANGINGMAN(dataframe)
+# dataframe["CDLHANGINGMAN"] = ta.CDLHANGINGMAN(dataframe)
# # Shooting Star: values [0, 100]
-# dataframe['CDLSHOOTINGSTAR'] = ta.CDLSHOOTINGSTAR(dataframe)
+# dataframe["CDLSHOOTINGSTAR"] = ta.CDLSHOOTINGSTAR(dataframe)
# # Gravestone Doji: values [0, 100]
-# dataframe['CDLGRAVESTONEDOJI'] = ta.CDLGRAVESTONEDOJI(dataframe)
+# dataframe["CDLGRAVESTONEDOJI"] = ta.CDLGRAVESTONEDOJI(dataframe)
# # Dark Cloud Cover: values [0, 100]
-# dataframe['CDLDARKCLOUDCOVER'] = ta.CDLDARKCLOUDCOVER(dataframe)
+# dataframe["CDLDARKCLOUDCOVER"] = ta.CDLDARKCLOUDCOVER(dataframe)
# # Evening Doji Star: values [0, 100]
-# dataframe['CDLEVENINGDOJISTAR'] = ta.CDLEVENINGDOJISTAR(dataframe)
+# dataframe["CDLEVENINGDOJISTAR"] = ta.CDLEVENINGDOJISTAR(dataframe)
# # Evening Star: values [0, 100]
-# dataframe['CDLEVENINGSTAR'] = ta.CDLEVENINGSTAR(dataframe)
+# dataframe["CDLEVENINGSTAR"] = ta.CDLEVENINGSTAR(dataframe)
# Pattern Recognition - Bullish/Bearish candlestick patterns
# ------------------------------------
# # Three Line Strike: values [0, -100, 100]
-# dataframe['CDL3LINESTRIKE'] = ta.CDL3LINESTRIKE(dataframe)
+# dataframe["CDL3LINESTRIKE"] = ta.CDL3LINESTRIKE(dataframe)
# # Spinning Top: values [0, -100, 100]
-# dataframe['CDLSPINNINGTOP'] = ta.CDLSPINNINGTOP(dataframe) # values [0, -100, 100]
+# dataframe["CDLSPINNINGTOP"] = ta.CDLSPINNINGTOP(dataframe) # values [0, -100, 100]
# # Engulfing: values [0, -100, 100]
-# dataframe['CDLENGULFING'] = ta.CDLENGULFING(dataframe) # values [0, -100, 100]
+# dataframe["CDLENGULFING"] = ta.CDLENGULFING(dataframe) # values [0, -100, 100]
# # Harami: values [0, -100, 100]
-# dataframe['CDLHARAMI'] = ta.CDLHARAMI(dataframe) # values [0, -100, 100]
+# dataframe["CDLHARAMI"] = ta.CDLHARAMI(dataframe) # values [0, -100, 100]
# # Three Outside Up/Down: values [0, -100, 100]
-# dataframe['CDL3OUTSIDE'] = ta.CDL3OUTSIDE(dataframe) # values [0, -100, 100]
+# dataframe["CDL3OUTSIDE"] = ta.CDL3OUTSIDE(dataframe) # values [0, -100, 100]
# # Three Inside Up/Down: values [0, -100, 100]
-# dataframe['CDL3INSIDE'] = ta.CDL3INSIDE(dataframe) # values [0, -100, 100]
+# dataframe["CDL3INSIDE"] = ta.CDL3INSIDE(dataframe) # values [0, -100, 100]
# # Chart type
# # ------------------------------------
# # Heikin Ashi Strategy
# heikinashi = qtpylib.heikinashi(dataframe)
-# dataframe['ha_open'] = heikinashi['open']
-# dataframe['ha_close'] = heikinashi['close']
-# dataframe['ha_high'] = heikinashi['high']
-# dataframe['ha_low'] = heikinashi['low']
+# dataframe["ha_open"] = heikinashi["open"]
+# dataframe["ha_close"] = heikinashi["close"]
+# dataframe["ha_high"] = heikinashi["high"]
+# dataframe["ha_low"] = heikinashi["low"]
# Retrieve best bid and best ask from the orderbook
# ------------------------------------
"""
# first check if dataprovider is available
if self.dp:
- if self.dp.runmode.value in ('live', 'dry_run'):
- ob = self.dp.orderbook(metadata['pair'], 1)
- dataframe['best_bid'] = ob['bids'][0][0]
- dataframe['best_ask'] = ob['asks'][0][0]
+ if self.dp.runmode.value in ("live", "dry_run"):
+ ob = self.dp.orderbook(metadata["pair"], 1)
+ dataframe["best_bid"] = ob["bids"][0][0]
+ dataframe["best_ask"] = ob["asks"][0][0]
"""
diff --git a/freqtrade/templates/strategy_subtemplates/indicators_minimal.j2 b/freqtrade/templates/strategy_subtemplates/indicators_minimal.j2
index 90f4f4d4a..1594a8988 100644
--- a/freqtrade/templates/strategy_subtemplates/indicators_minimal.j2
+++ b/freqtrade/templates/strategy_subtemplates/indicators_minimal.j2
@@ -3,15 +3,15 @@
# ------------------------------------
# RSI
-dataframe['rsi'] = ta.RSI(dataframe)
+dataframe["rsi"] = ta.RSI(dataframe)
# Retrieve best bid and best ask from the orderbook
# ------------------------------------
"""
# first check if dataprovider is available
if self.dp:
- if self.dp.runmode.value in ('live', 'dry_run'):
- ob = self.dp.orderbook(metadata['pair'], 1)
- dataframe['best_bid'] = ob['bids'][0][0]
- dataframe['best_ask'] = ob['asks'][0][0]
+ if self.dp.runmode.value in ("live", "dry_run"):
+ ob = self.dp.orderbook(metadata["pair"], 1)
+ dataframe["best_bid"] = ob["bids"][0][0]
+ dataframe["best_ask"] = ob["asks"][0][0]
"""
diff --git a/freqtrade/templates/strategy_subtemplates/plot_config_full.j2 b/freqtrade/templates/strategy_subtemplates/plot_config_full.j2
index e3f9e7ca0..08eb3c29f 100644
--- a/freqtrade/templates/strategy_subtemplates/plot_config_full.j2
+++ b/freqtrade/templates/strategy_subtemplates/plot_config_full.j2
@@ -3,18 +3,18 @@
def plot_config(self):
return {
# Main plot indicators (Moving averages, ...)
- 'main_plot': {
- 'tema': {},
- 'sar': {'color': 'white'},
+ "main_plot": {
+ "tema": {},
+ "sar": {"color": "white"},
},
- 'subplots': {
+ "subplots": {
# Subplots - each dict defines one additional plot
"MACD": {
- 'macd': {'color': 'blue'},
- 'macdsignal': {'color': 'orange'},
+ "macd": {"color": "blue"},
+ "macdsignal": {"color": "orange"},
},
"RSI": {
- 'rsi': {'color': 'red'},
+ "rsi": {"color": "red"},
}
}
}
diff --git a/freqtrade/templates/strategy_subtemplates/sell_trend_full.j2 b/freqtrade/templates/strategy_subtemplates/sell_trend_full.j2
index 3068d8d57..08cb68cd1 100644
--- a/freqtrade/templates/strategy_subtemplates/sell_trend_full.j2
+++ b/freqtrade/templates/strategy_subtemplates/sell_trend_full.j2
@@ -1,3 +1,3 @@
-(qtpylib.crossed_above(dataframe['rsi'], self.sell_rsi.value)) & # Signal: RSI crosses above sell_rsi
-(dataframe['tema'] > dataframe['bb_middleband']) & # Guard: tema above BB middle
-(dataframe['tema'] < dataframe['tema'].shift(1)) & # Guard: tema is falling
+(qtpylib.crossed_above(dataframe["rsi"], self.sell_rsi.value)) & # Signal: RSI crosses above sell_rsi
+(dataframe["tema"] > dataframe["bb_middleband"]) & # Guard: tema above BB middle
+(dataframe["tema"] < dataframe["tema"].shift(1)) & # Guard: tema is falling
diff --git a/freqtrade/templates/strategy_subtemplates/sell_trend_minimal.j2 b/freqtrade/templates/strategy_subtemplates/sell_trend_minimal.j2
index 5dabc5910..821b547c3 100644
--- a/freqtrade/templates/strategy_subtemplates/sell_trend_minimal.j2
+++ b/freqtrade/templates/strategy_subtemplates/sell_trend_minimal.j2
@@ -1 +1 @@
-(qtpylib.crossed_above(dataframe['rsi'], self.sell_rsi.value)) & # Signal: RSI crosses above sell_rsi
+(qtpylib.crossed_above(dataframe["rsi"], self.sell_rsi.value)) & # Signal: RSI crosses above sell_rsi
diff --git a/freqtrade/templates/strategy_subtemplates/strategy_attributes_full.j2 b/freqtrade/templates/strategy_subtemplates/strategy_attributes_full.j2
index 86445510d..5ae361996 100644
--- a/freqtrade/templates/strategy_subtemplates/strategy_attributes_full.j2
+++ b/freqtrade/templates/strategy_subtemplates/strategy_attributes_full.j2
@@ -1,13 +1,13 @@
# Optional order type mapping.
order_types = {
- 'entry': 'limit',
- 'exit': 'limit',
- 'stoploss': 'market',
- 'stoploss_on_exchange': False
+ "entry": "limit",
+ "exit": "limit",
+ "stoploss": "market",
+ "stoploss_on_exchange": False
}
# Optional order time in force.
order_time_in_force = {
- 'entry': 'GTC',
- 'exit': 'GTC'
+ "entry": "GTC",
+ "exit": "GTC"
}
diff --git a/freqtrade/templates/strategy_subtemplates/strategy_methods_advanced.j2 b/freqtrade/templates/strategy_subtemplates/strategy_methods_advanced.j2
index 1783e818c..07b72610a 100644
--- a/freqtrade/templates/strategy_subtemplates/strategy_methods_advanced.j2
+++ b/freqtrade/templates/strategy_subtemplates/strategy_methods_advanced.j2
@@ -13,9 +13,9 @@ def bot_loop_start(self, current_time: datetime, **kwargs) -> None:
"""
pass
-def custom_entry_price(self, pair: str, trade: Optional['Trade'],
- current_time: 'datetime', proposed_rate: float,
- entry_tag: 'Optional[str]', side: str, **kwargs) -> float:
+def custom_entry_price(self, pair: str, trade: Optional[Trade],
+ current_time: datetime, proposed_rate: float,
+ entry_tag: Optional[str], side: str, **kwargs) -> float:
"""
Custom entry price logic, returning the new entry price.
@@ -33,7 +33,7 @@ def custom_entry_price(self, pair: str, trade: Optional['Trade'],
"""
return proposed_rate
-def adjust_entry_price(self, trade: 'Trade', order: 'Optional[Order]', pair: str,
+def adjust_entry_price(self, trade: Trade, order: Optional[Order], pair: str,
current_time: datetime, proposed_rate: float, current_order_rate: float,
entry_tag: Optional[str], side: str, **kwargs) -> float:
"""
@@ -61,8 +61,8 @@ def adjust_entry_price(self, trade: 'Trade', order: 'Optional[Order]', pair: str
"""
return current_order_rate
-def custom_exit_price(self, pair: str, trade: 'Trade',
- current_time: 'datetime', proposed_rate: float,
+def custom_exit_price(self, pair: str, trade: Trade,
+ current_time: datetime, proposed_rate: float,
current_profit: float, exit_tag: Optional[str], **kwargs) -> float:
"""
Custom exit price logic, returning the new exit price.
@@ -104,7 +104,7 @@ def custom_stake_amount(self, pair: str, current_time: datetime, current_rate: f
use_custom_stoploss = True
-def custom_stoploss(self, pair: str, trade: 'Trade', current_time: 'datetime', current_rate: float,
+def custom_stoploss(self, pair: str, trade: Trade, current_time: datetime, current_rate: float,
current_profit: float, after_fill: bool, **kwargs) -> float:
"""
Custom stoploss logic, returning the new distance relative to current_rate (as ratio).
@@ -126,8 +126,8 @@ def custom_stoploss(self, pair: str, trade: 'Trade', current_time: 'datetime', c
:return float: New stoploss value, relative to the current_rate
"""
-def custom_exit(self, pair: str, trade: 'Trade', current_time: 'datetime', current_rate: float,
- current_profit: float, **kwargs) -> 'Optional[Union[str, bool]]':
+def custom_exit(self, pair: str, trade: Trade, current_time: datetime, current_rate: float,
+ current_profit: float, **kwargs) -> Optional[Union[str, bool]]:
"""
Custom exit signal logic indicating that specified position should be sold. Returning a
string or True from this method is equal to setting sell signal on a candle at specified
@@ -177,9 +177,9 @@ def confirm_trade_entry(self, pair: str, order_type: str, amount: float, rate: f
"""
return True
-def confirm_trade_exit(self, pair: str, trade: 'Trade', order_type: str, amount: float,
+def confirm_trade_exit(self, pair: str, trade: Trade, order_type: str, amount: float,
rate: float, time_in_force: str, exit_reason: str,
- current_time: 'datetime', **kwargs) -> bool:
+ current_time: datetime, **kwargs) -> bool:
"""
Called right before placing a regular exit order.
Timing for this function is critical, so avoid doing heavy computations or
@@ -206,7 +206,7 @@ def confirm_trade_exit(self, pair: str, trade: 'Trade', order_type: str, amount:
"""
return True
-def check_entry_timeout(self, pair: str, trade: 'Trade', order: 'Order',
+def check_entry_timeout(self, pair: str, trade: Trade, order: Order,
current_time: datetime, **kwargs) -> bool:
"""
Check entry timeout function callback.
@@ -228,7 +228,7 @@ def check_entry_timeout(self, pair: str, trade: 'Trade', order: 'Order',
"""
return False
-def check_exit_timeout(self, pair: str, trade: 'Trade', order: 'Order',
+def check_exit_timeout(self, pair: str, trade: Trade, order: Order,
current_time: datetime, **kwargs) -> bool:
"""
Check exit timeout function callback.
@@ -250,7 +250,7 @@ def check_exit_timeout(self, pair: str, trade: 'Trade', order: 'Order',
"""
return False
-def adjust_trade_position(self, trade: 'Trade', current_time: datetime,
+def adjust_trade_position(self, trade: Trade, current_time: datetime,
current_rate: float, current_profit: float,
min_stake: Optional[float], max_stake: float,
current_entry_rate: float, current_exit_rate: float,
@@ -302,7 +302,7 @@ def leverage(self, pair: str, current_time: datetime, current_rate: float,
return 1.0
-def order_filled(self, pair: str, trade: 'Trade', order: 'Order',
+def order_filled(self, pair: str, trade: Trade, order: Order,
current_time: datetime, **kwargs) -> None:
"""
Called right after an order fills.
diff --git a/freqtrade/types/valid_exchanges_type.py b/freqtrade/types/valid_exchanges_type.py
index 9de05b964..497ff8a93 100644
--- a/freqtrade/types/valid_exchanges_type.py
+++ b/freqtrade/types/valid_exchanges_type.py
@@ -1,5 +1,5 @@
# Used for list-exchanges
-from typing import List
+from typing import List, Optional
from typing_extensions import TypedDict
@@ -11,8 +11,11 @@ class TradeModeType(TypedDict):
class ValidExchangesType(TypedDict):
name: str
+ classname: str
valid: bool
supported: bool
comment: str
dex: bool
+ is_alias: bool
+ alias_for: Optional[str]
trade_modes: List[TradeModeType]
diff --git a/freqtrade/util/rich_progress.py b/freqtrade/util/rich_progress.py
index afa26683e..f4f993f7e 100644
--- a/freqtrade/util/rich_progress.py
+++ b/freqtrade/util/rich_progress.py
@@ -1,14 +1,18 @@
-from typing import Union
+from typing import Callable, List, Union
from rich.console import ConsoleRenderable, Group, RichCast
from rich.progress import Progress
class CustomProgress(Progress):
- def __init__(self, *args, cust_objs=[], **kwargs) -> None:
+ def __init__(self, *args, cust_objs=[], cust_callables: List[Callable] = [], **kwargs) -> None:
self._cust_objs = cust_objs
+ self._cust_callables = cust_callables
super().__init__(*args, **kwargs)
def get_renderable(self) -> Union[ConsoleRenderable, RichCast, str]:
- renderable = Group(*self._cust_objs, *self.get_renderables())
+ objs = [obj for obj in self._cust_objs]
+ for cust_call in self._cust_callables:
+ objs.append(cust_call())
+ renderable = Group(*objs, *self.get_renderables())
return renderable
diff --git a/freqtrade/util/rich_tables.py b/freqtrade/util/rich_tables.py
index d36bf9004..cfab5cd74 100644
--- a/freqtrade/util/rich_tables.py
+++ b/freqtrade/util/rich_tables.py
@@ -37,9 +37,11 @@ def print_rich_table(
row_to_add: List[Union[str, Text]] = [r if isinstance(r, Text) else str(r) for r in row]
table.add_row(*row_to_add)
- console = Console(
- width=200 if "pytest" in sys.modules else None,
- )
+ width = None
+ if any(module in ["pytest", "ipykernel"] for module in sys.modules):
+ width = 200
+
+ console = Console(width=width)
console.print(table)
@@ -71,7 +73,9 @@ def print_df_rich_table(
row = [_format_value(x, floatfmt=".3f") for x in value_list]
table.add_row(*row)
- console = Console(
- width=200 if "pytest" in sys.modules else None,
- )
+ width = None
+ if any(module in ["pytest", "ipykernel"] for module in sys.modules):
+ width = 200
+
+ console = Console(width=width)
console.print(table)
diff --git a/freqtrade/wallets.py b/freqtrade/wallets.py
index 7f839cb24..f888ef92e 100644
--- a/freqtrade/wallets.py
+++ b/freqtrade/wallets.py
@@ -29,7 +29,7 @@ class Wallet(NamedTuple):
class PositionWallet(NamedTuple):
symbol: str
position: float = 0
- leverage: float = 0
+ leverage: Optional[float] = 0 # Don't use this - it's not guaranteed to be set
collateral: float = 0
side: str = "long"
@@ -66,6 +66,17 @@ class Wallets:
else:
return 0
+ def get_owned(self, pair: str, base_currency: str) -> float:
+ """
+ Get currently owned value.
+ Designed to work across both spot and futures.
+ """
+ if self._config.get("trading_mode", "spot") != TradingMode.FUTURES:
+ return self.get_total(base_currency) or 0
+ if pos := self._positions.get(pair):
+ return pos.position
+ return 0
+
def _update_dry(self) -> None:
"""
Update from database in dry-run mode
@@ -82,7 +93,7 @@ class Wallets:
tot_profit = Trade.get_total_closed_profit()
else:
# Backtest mode
- tot_profit = LocalTrade.total_profit
+ tot_profit = LocalTrade.bt_total_profit
tot_profit += sum(trade.realized_profit for trade in open_trades)
tot_in_trades = sum(trade.stake_amount for trade in open_trades)
used_stake = 0.0
@@ -128,9 +139,9 @@ class Wallets:
if isinstance(balances[currency], dict):
self._wallets[currency] = Wallet(
currency,
- balances[currency].get("free"),
- balances[currency].get("used"),
- balances[currency].get("total"),
+ balances[currency].get("free", 0),
+ balances[currency].get("used", 0),
+ balances[currency].get("total", 0),
)
# Remove currencies no longer in get_balances output
for currency in deepcopy(self._wallets):
@@ -138,7 +149,7 @@ class Wallets:
del self._wallets[currency]
positions = self._exchange.fetch_positions()
- self._positions = {}
+ _parsed_positions = {}
for position in positions:
symbol = position["symbol"]
if position["side"] is None or position["collateral"] == 0.0:
@@ -146,14 +157,15 @@ class Wallets:
continue
size = self._exchange._contracts_to_amount(symbol, position["contracts"])
collateral = safe_value_fallback(position, "collateral", "initialMargin", 0.0)
- leverage = position["leverage"]
- self._positions[symbol] = PositionWallet(
+ leverage = position.get("leverage")
+ _parsed_positions[symbol] = PositionWallet(
symbol,
position=size,
leverage=leverage,
collateral=collateral,
side=position["side"],
)
+ self._positions = _parsed_positions
def update(self, require_update: bool = True) -> None:
"""
diff --git a/ft_client/freqtrade_client/__init__.py b/ft_client/freqtrade_client/__init__.py
index a730214eb..ea75c43e1 100644
--- a/ft_client/freqtrade_client/__init__.py
+++ b/ft_client/freqtrade_client/__init__.py
@@ -1,7 +1,7 @@
from freqtrade_client.ft_rest_client import FtRestClient
-__version__ = "2024.7.1"
+__version__ = "2024.8"
if "dev" in __version__:
from pathlib import Path
diff --git a/ft_client/requirements.txt b/ft_client/requirements.txt
index 5e6856e92..4e2983ba8 100644
--- a/ft_client/requirements.txt
+++ b/ft_client/requirements.txt
@@ -1,3 +1,3 @@
# Requirements for freqtrade client library
requests==2.32.3
-python-rapidjson==1.18
+python-rapidjson==1.20
diff --git a/mkdocs.yml b/mkdocs.yml
index 62274daa9..9e67f1f71 100644
--- a/mkdocs.yml
+++ b/mkdocs.yml
@@ -112,3 +112,12 @@ markdown_extensions:
custom_checkbox: true
- pymdownx.tilde
- mdx_truly_sane_lists
+extra:
+ version:
+ provider: mike
+ alias: true
+plugins:
+ - search:
+ enabled: true
+ - mike:
+ deploy_prefix: 'en'
diff --git a/pyproject.toml b/pyproject.toml
index 5918e92e1..49fdff752 100644
--- a/pyproject.toml
+++ b/pyproject.toml
@@ -86,6 +86,7 @@ log_format = "%(asctime)s %(levelname)s %(message)s"
log_date_format = "%Y-%m-%d %H:%M:%S"
asyncio_mode = "auto"
+asyncio_default_fixture_loop_scope = "function"
addopts = "--dist loadscope"
[tool.mypy]
diff --git a/requirements-dev.txt b/requirements-dev.txt
index a89604996..63c43e820 100644
--- a/requirements-dev.txt
+++ b/requirements-dev.txt
@@ -7,11 +7,11 @@
-r docs/requirements-docs.txt
coveralls==4.0.1
-ruff==0.5.4
-mypy==1.11.0
-pre-commit==3.7.1
-pytest==8.3.1
-pytest-asyncio==0.23.8
+ruff==0.6.2
+mypy==1.11.2
+pre-commit==3.8.0
+pytest==8.3.2
+pytest-asyncio==0.24.0
pytest-cov==5.0.0
pytest-mock==3.14.0
pytest-random-order==1.1.1
@@ -19,14 +19,14 @@ pytest-timeout==2.3.1
pytest-xdist==3.6.1
isort==5.13.2
# For datetime mocking
-time-machine==2.14.2
+time-machine==2.15.0
# Convert jupyter notebooks to markdown documents
nbconvert==7.16.4
# mypy types
-types-cachetools==5.4.0.20240717
+types-cachetools==5.5.0.20240820
types-filelock==3.2.7
types-requests==2.32.0.20240712
types-tabulate==0.9.0.20240106
-types-python-dateutil==2.9.0.20240316
+types-python-dateutil==2.9.0.20240821
diff --git a/requirements-freqai-rl.txt b/requirements-freqai-rl.txt
index c278716fb..9b808f66f 100644
--- a/requirements-freqai-rl.txt
+++ b/requirements-freqai-rl.txt
@@ -2,10 +2,10 @@
-r requirements-freqai.txt
# Required for freqai-rl
-torch==2.3.1; sys_platform != 'darwin' or platform_machine != 'x86_64'
torch==2.2.2; sys_platform == 'darwin' and platform_machine == 'x86_64'
+torch==2.4.0; sys_platform != 'darwin' or platform_machine != 'x86_64'
gymnasium==0.29.1
stable_baselines3==2.3.2
sb3_contrib>=2.2.1
# Progress bar for stable-baselines3 and sb3-contrib
-tqdm==4.66.4
+tqdm==4.66.5
diff --git a/requirements-freqai.txt b/requirements-freqai.txt
index c57e66d2d..0db247289 100644
--- a/requirements-freqai.txt
+++ b/requirements-freqai.txt
@@ -6,7 +6,10 @@
scikit-learn==1.5.1
joblib==1.4.2
catboost==1.2.5; 'arm' not in platform_machine
-lightgbm==4.4.0
+# Pin Matplotlib - it's depended on by catboost
+# Temporary downgrade of matplotlib due to https://github.com/matplotlib/matplotlib/issues/28551
+matplotlib==3.9.2
+lightgbm==4.5.0
xgboost==2.0.3
-tensorboard==2.17.0
+tensorboard==2.17.1
datasieve==0.1.7
diff --git a/requirements-hyperopt.txt b/requirements-hyperopt.txt
index 6475525de..3391d8c68 100644
--- a/requirements-hyperopt.txt
+++ b/requirements-hyperopt.txt
@@ -2,7 +2,7 @@
-r requirements.txt
# Required for hyperopt
-scipy==1.14.0; python_version >= "3.10"
+scipy==1.14.1; python_version >= "3.10"
scipy==1.13.1; python_version < "3.10"
scikit-learn==1.5.1
ft-scikit-optimize==0.9.2
diff --git a/requirements-plot.txt b/requirements-plot.txt
index b4dc2e46c..6641fe524 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.22.0
+plotly==5.23.0
diff --git a/requirements.txt b/requirements.txt
index 84ad2beb3..4cf3132b8 100644
--- a/requirements.txt
+++ b/requirements.txt
@@ -4,25 +4,27 @@ bottleneck==1.4.0
numexpr==2.10.1
pandas-ta==0.3.14b
-ccxt==4.3.65
+ccxt==4.3.88
cryptography==42.0.8; platform_machine == 'armv7l'
cryptography==43.0.0; platform_machine != 'armv7l'
-aiohttp==3.9.5
-SQLAlchemy==2.0.31
+aiohttp==3.10.5
+SQLAlchemy==2.0.32
python-telegram-bot==21.4
# can't be hard-pinned due to telegram-bot pinning httpx with ~
httpx>=0.24.1
humanize==4.10.0
-cachetools==5.4.0
+cachetools==5.5.0
requests==2.32.3
urllib3==2.2.2
jsonschema==4.23.0
TA-Lib==0.4.32
-technical==1.4.3
+technical==1.4.4
tabulate==0.9.0
pycoingecko==3.1.0
jinja2==3.1.4
-tables==3.9.1
+# Tables 3.10 dropped support for Python 3.9
+tables==3.9.1; python_version < "3.10"
+tables==3.10.1; python_version >= "3.10"
joblib==1.4.2
rich==13.7.1
pyarrow==17.0.0; platform_machine != 'armv7l'
@@ -31,18 +33,18 @@ pyarrow==17.0.0; platform_machine != 'armv7l'
py_find_1st==1.1.6
# Load ticker files 30% faster
-python-rapidjson==1.18
+python-rapidjson==1.20
# Properly format api responses
-orjson==3.10.6
+orjson==3.10.7
# Notify systemd
sdnotify==0.3.2
# API Server
-fastapi==0.111.1
+fastapi==0.112.2
pydantic==2.8.2
-uvicorn==0.30.3
-pyjwt==2.8.0
+uvicorn==0.30.6
+pyjwt==2.9.0
aiofiles==24.1.0
psutil==6.0.0
@@ -57,7 +59,7 @@ pytz==2024.1
schedule==1.2.2
#WS Messages
-websockets==12.0
+websockets==13.0
janus==1.0.0
ast-comments==1.2.2
diff --git a/setup.sh b/setup.sh
index f1317c02c..18f7682d8 100755
--- a/setup.sh
+++ b/setup.sh
@@ -49,7 +49,7 @@ function updateenv() {
source .venv/bin/activate
SYS_ARCH=$(uname -m)
echo "pip install in-progress. Please wait..."
- ${PYTHON} -m pip install --upgrade "pip<=24.0" wheel setuptools
+ ${PYTHON} -m pip install --upgrade pip wheel setuptools
REQUIREMENTS_HYPEROPT=""
REQUIREMENTS_PLOT=""
REQUIREMENTS_FREQAI=""
diff --git a/tests/commands/test_commands.py b/tests/commands/test_commands.py
index 687bff69f..c55126db1 100644
--- a/tests/commands/test_commands.py
+++ b/tests/commands/test_commands.py
@@ -1692,6 +1692,54 @@ def test_start_list_data(testdatadir, capsys):
)
+def test_start_list_trades_data(testdatadir, capsys):
+ args = [
+ "list-data",
+ "--datadir",
+ str(testdatadir),
+ "--trades",
+ ]
+ pargs = get_args(args)
+ pargs["config"] = None
+ start_list_data(pargs)
+ captured = capsys.readouterr()
+ assert "Found trades data for 1 pair." in captured.out
+ assert re.search(r".*Pair.*Type.*\n", captured.out)
+ assert re.search(r"\n.* XRP/ETH .* spot |\n", captured.out)
+
+ args = [
+ "list-data",
+ "--datadir",
+ str(testdatadir),
+ "--trades",
+ "--show-timerange",
+ ]
+ pargs = get_args(args)
+ pargs["config"] = None
+ start_list_data(pargs)
+ captured = capsys.readouterr()
+ assert "Found trades data for 1 pair." in captured.out
+ assert re.search(r".*Pair.*Type.*From.*To.*Trades.*\n", captured.out)
+ assert re.search(
+ r"\n.* XRP/ETH .* spot .* 2019-10-11 00:00:01 .* 2019-10-13 11:19:28 .* 12477 .*|\n",
+ captured.out,
+ )
+
+ args = [
+ "list-data",
+ "--datadir",
+ str(testdatadir),
+ "--trades",
+ "--trading-mode",
+ "futures",
+ ]
+ pargs = get_args(args)
+ pargs["config"] = None
+ start_list_data(pargs)
+ captured = capsys.readouterr()
+ assert "Found trades data for 0 pairs." in captured.out
+
+
@pytest.mark.usefixtures("init_persistence")
def test_show_trades(mocker, fee, capsys, caplog):
mocker.patch("freqtrade.persistence.init_db")
diff --git a/tests/conftest.py b/tests/conftest.py
index 2d56d9d9e..8f15388ef 100644
--- a/tests/conftest.py
+++ b/tests/conftest.py
@@ -243,6 +243,7 @@ def patch_exchange(
mocker.patch(f"{EXMS}.id", PropertyMock(return_value=exchange))
mocker.patch(f"{EXMS}.name", PropertyMock(return_value=exchange.title()))
mocker.patch(f"{EXMS}.precisionMode", PropertyMock(return_value=2))
+ mocker.patch(f"{EXMS}.precision_mode_price", PropertyMock(return_value=2))
# Temporary patch ...
mocker.patch("freqtrade.exchange.bybit.Bybit.cache_leverage_tiers")
@@ -2188,7 +2189,7 @@ def tickers():
"first": None,
"last": 530.21,
"change": 0.558,
- "percentage": None,
+ "percentage": 2.349,
"average": None,
"baseVolume": 72300.0659,
"quoteVolume": 37670097.3022171,
diff --git a/tests/data/test_datahandler.py b/tests/data/test_datahandler.py
index b8bb5661f..99af63eca 100644
--- a/tests/data/test_datahandler.py
+++ b/tests/data/test_datahandler.py
@@ -519,6 +519,39 @@ def test_datahandler_trades_purge(mocker, testdatadir, datahandler):
assert unlinkmock.call_count == 1
+def test_datahandler_trades_get_available_data(testdatadir):
+ paircombs = FeatherDataHandler.trades_get_available_data(testdatadir, TradingMode.SPOT)
+ # Convert to set to avoid failures due to sorting
+ assert set(paircombs) == {"XRP/ETH"}
+
+ paircombs = FeatherDataHandler.trades_get_available_data(testdatadir, TradingMode.FUTURES)
+ # Convert to set to avoid failures due to sorting
+ assert set(paircombs) == set()
+
+ paircombs = JsonGzDataHandler.trades_get_available_data(testdatadir, TradingMode.SPOT)
+ assert set(paircombs) == {"XRP/ETH", "XRP/OLD"}
+ paircombs = HDF5DataHandler.trades_get_available_data(testdatadir, TradingMode.SPOT)
+ assert set(paircombs) == {"XRP/ETH"}
+
+
+def test_datahandler_trades_data_min_max(testdatadir):
+ dh = FeatherDataHandler(testdatadir)
+ min_max = dh.trades_data_min_max("XRP/ETH", TradingMode.SPOT)
+ assert len(min_max) == 3
+
+ # Empty pair
+ min_max = dh.trades_data_min_max("NADA/ETH", TradingMode.SPOT)
+ assert len(min_max) == 3
+ assert min_max[0] == datetime.fromtimestamp(0, tz=timezone.utc)
+ assert min_max[0] == min_max[1]
+
+ # Existing pair ...
+ min_max = dh.trades_data_min_max("XRP/ETH", TradingMode.SPOT)
+ assert len(min_max) == 3
+ assert min_max[0] == datetime(2019, 10, 11, 0, 0, 11, 620000, tzinfo=timezone.utc)
+ assert min_max[1] == datetime(2019, 10, 13, 11, 19, 28, 844000, tzinfo=timezone.utc)
+
+
def test_gethandlerclass():
cl = get_datahandlerclass("json")
assert cl == JsonDataHandler
diff --git a/tests/exchange/test_binance.py b/tests/exchange/test_binance.py
index a5f9ea654..7b0831520 100644
--- a/tests/exchange/test_binance.py
+++ b/tests/exchange/test_binance.py
@@ -170,6 +170,106 @@ def test_stoploss_adjust_binance(mocker, default_conf, sl1, sl2, sl3, side):
assert not exchange.stoploss_adjust(sl2, order, side=side)
+@pytest.mark.parametrize(
+ "is_short, trading_mode, margin_mode, wallet_balance, "
+ "mm_ex_1, upnl_ex_1, maintenance_amt, amount, open_rate, "
+ "mm_ratio, expected",
+ [
+ (
+ False,
+ "futures",
+ "isolated",
+ 1535443.01,
+ 0.0,
+ 0.0,
+ 135365.00,
+ 3683.979,
+ 1456.84,
+ 0.10,
+ 1114.78,
+ ),
+ (
+ False,
+ "futures",
+ "isolated",
+ 1535443.01,
+ 0.0,
+ 0.0,
+ 16300.000,
+ 109.488,
+ 32481.980,
+ 0.025,
+ 18778.73,
+ ),
+ (
+ False,
+ "futures",
+ "cross",
+ 1535443.01,
+ 71200.81144,
+ -56354.57,
+ 135365.00,
+ 3683.979,
+ 1456.84,
+ 0.10,
+ 1153.26,
+ ),
+ (
+ False,
+ "futures",
+ "cross",
+ 1535443.01,
+ 356512.508,
+ -448192.89,
+ 16300.000,
+ 109.488,
+ 32481.980,
+ 0.025,
+ 26316.89,
+ ),
+ ],
+)
+def test_liquidation_price_binance(
+ mocker,
+ default_conf,
+ open_rate,
+ is_short,
+ trading_mode,
+ margin_mode,
+ wallet_balance,
+ mm_ex_1,
+ upnl_ex_1,
+ maintenance_amt,
+ amount,
+ mm_ratio,
+ expected,
+):
+ default_conf["trading_mode"] = trading_mode
+ default_conf["margin_mode"] = margin_mode
+ default_conf["liquidation_buffer"] = 0.0
+ exchange = get_patched_exchange(mocker, default_conf, exchange="binance")
+ exchange.get_maintenance_ratio_and_amt = MagicMock(return_value=(mm_ratio, maintenance_amt))
+ assert (
+ pytest.approx(
+ round(
+ exchange.get_liquidation_price(
+ pair="DOGE/USDT",
+ open_rate=open_rate,
+ is_short=is_short,
+ wallet_balance=wallet_balance,
+ mm_ex_1=mm_ex_1,
+ upnl_ex_1=upnl_ex_1,
+ amount=amount,
+ stake_amount=open_rate * amount,
+ leverage=5,
+ ),
+ 2,
+ )
+ )
+ == expected
+ )
+
+
def test_fill_leverage_tiers_binance(default_conf, mocker):
api_mock = MagicMock()
api_mock.fetch_leverage_tiers = MagicMock(
@@ -560,7 +660,6 @@ def test__set_leverage_binance(mocker, default_conf):
)
-@pytest.mark.asyncio
@pytest.mark.parametrize("candle_type", [CandleType.MARK, ""])
async def test__async_get_historic_ohlcv_binance(default_conf, mocker, caplog, candle_type):
ohlcv = [
diff --git a/tests/exchange/test_exchange.py b/tests/exchange/test_exchange.py
index 168157cea..6c9a1a9ba 100644
--- a/tests/exchange/test_exchange.py
+++ b/tests/exchange/test_exchange.py
@@ -342,6 +342,27 @@ def test_validate_orderflow(default_conf, mocker, caplog):
ex.validate_orderflow({"use_public_trades": True})
+def test_validate_freqai_compat(default_conf, mocker, caplog):
+ caplog.set_level(logging.INFO)
+ # Test kraken - as it doesn't support historic trades data.
+ ex = get_patched_exchange(mocker, default_conf, exchange="kraken")
+ mocker.patch(f"{EXMS}.exchange_has", return_value=True)
+
+ default_conf["freqai"] = {"enabled": False}
+ ex.validate_freqai(default_conf)
+
+ default_conf["freqai"] = {"enabled": True}
+ with pytest.raises(ConfigurationError, match=r"Historic OHLCV data not available for.*"):
+ ex.validate_freqai(default_conf)
+
+ # Binance supports historic data.
+ ex = get_patched_exchange(mocker, default_conf, exchange="binance")
+ default_conf["freqai"] = {"enabled": True}
+ ex.validate_freqai(default_conf)
+ default_conf["freqai"] = {"enabled": False}
+ ex.validate_freqai(default_conf)
+
+
@pytest.mark.parametrize(
"price,precision_mode,precision,expected",
[
@@ -365,6 +386,7 @@ def test_price_get_one_pip(default_conf, mocker, price, precision_mode, precisio
exchange = get_patched_exchange(mocker, default_conf, exchange="binance")
mocker.patch(f"{EXMS}.markets", markets)
mocker.patch(f"{EXMS}.precisionMode", PropertyMock(return_value=precision_mode))
+ mocker.patch(f"{EXMS}.precision_mode_price", PropertyMock(return_value=precision_mode))
pair = "ETH/BTC"
assert pytest.approx(exchange.price_get_one_pip(pair, price)) == expected
@@ -2239,7 +2261,6 @@ def test_get_historic_ohlcv(default_conf, mocker, caplog, exchange_name, candle_
assert log_has_re(r"Async code raised an exception: .*", caplog)
-@pytest.mark.asyncio
@pytest.mark.parametrize("exchange_name", EXCHANGES)
@pytest.mark.parametrize("candle_type", [CandleType.MARK, CandleType.SPOT])
async def test__async_get_historic_ohlcv(default_conf, mocker, caplog, exchange_name, candle_type):
@@ -3213,7 +3234,6 @@ def test_get_rates_testing_exit(
@pytest.mark.parametrize("exchange_name", EXCHANGES)
-@pytest.mark.asyncio
async def test___async_get_candle_history_sort(default_conf, mocker, exchange_name):
def sort_data(data, key):
return sorted(data, key=key)
@@ -3415,7 +3435,6 @@ async def test__async_fetch_trades_contract_size(
exchange.close()
-@pytest.mark.asyncio
@pytest.mark.parametrize("exchange_name", EXCHANGES)
async def test__async_get_trade_history_id(
default_conf, mocker, exchange_name, fetch_trades_result
@@ -3484,7 +3503,6 @@ def test__valid_trade_pagination_id(mocker, default_conf_usdt, exchange_name, tr
assert exchange._valid_trade_pagination_id("XRP/USDT", trade_id) == expected
-@pytest.mark.asyncio
@pytest.mark.parametrize("exchange_name", EXCHANGES)
async def test__async_get_trade_history_time(
default_conf, mocker, caplog, exchange_name, fetch_trades_result
@@ -3526,7 +3544,6 @@ async def test__async_get_trade_history_time(
assert log_has_re(r"Stopping because until was reached.*", caplog)
-@pytest.mark.asyncio
@pytest.mark.parametrize("exchange_name", EXCHANGES)
async def test__async_get_trade_history_time_empty(
default_conf, mocker, caplog, exchange_name, trades_history
@@ -5645,111 +5662,6 @@ def test_liquidation_price_is_none(
)
-@pytest.mark.parametrize(
- "exchange_name, is_short, trading_mode, margin_mode, wallet_balance, "
- "mm_ex_1, upnl_ex_1, maintenance_amt, amount, open_rate, "
- "mm_ratio, expected",
- [
- (
- "binance",
- False,
- "futures",
- "isolated",
- 1535443.01,
- 0.0,
- 0.0,
- 135365.00,
- 3683.979,
- 1456.84,
- 0.10,
- 1114.78,
- ),
- (
- "binance",
- False,
- "futures",
- "isolated",
- 1535443.01,
- 0.0,
- 0.0,
- 16300.000,
- 109.488,
- 32481.980,
- 0.025,
- 18778.73,
- ),
- (
- "binance",
- False,
- "futures",
- "cross",
- 1535443.01,
- 71200.81144,
- -56354.57,
- 135365.00,
- 3683.979,
- 1456.84,
- 0.10,
- 1153.26,
- ),
- (
- "binance",
- False,
- "futures",
- "cross",
- 1535443.01,
- 356512.508,
- -448192.89,
- 16300.000,
- 109.488,
- 32481.980,
- 0.025,
- 26316.89,
- ),
- ],
-)
-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,
-):
- default_conf["trading_mode"] = trading_mode
- default_conf["margin_mode"] = margin_mode
- default_conf["liquidation_buffer"] = 0.0
- exchange = get_patched_exchange(mocker, default_conf, exchange=exchange_name)
- exchange.get_maintenance_ratio_and_amt = MagicMock(return_value=(mm_ratio, maintenance_amt))
- assert (
- pytest.approx(
- round(
- exchange.get_liquidation_price(
- pair="DOGE/USDT",
- open_rate=open_rate,
- is_short=is_short,
- wallet_balance=wallet_balance,
- mm_ex_1=mm_ex_1,
- upnl_ex_1=upnl_ex_1,
- amount=amount,
- stake_amount=open_rate * amount,
- leverage=5,
- ),
- 2,
- )
- )
- == expected
- )
-
-
def test_get_max_pair_stake_amount(
mocker,
default_conf,
diff --git a/tests/exchange/test_okx.py b/tests/exchange/test_okx.py
index df428010f..97f8a3a4c 100644
--- a/tests/exchange/test_okx.py
+++ b/tests/exchange/test_okx.py
@@ -640,7 +640,7 @@ def test__get_stop_params_okx(mocker, default_conf):
default_conf["trading_mode"] = "futures"
default_conf["margin_mode"] = "isolated"
exchange = get_patched_exchange(mocker, default_conf, exchange="okx")
- params = exchange._get_stop_params("ETH/USDT:USDT", 1500, "sell")
+ params = exchange._get_stop_params("sell", "market", 1500)
assert params["tdMode"] == "isolated"
assert params["posSide"] == "net"
diff --git a/tests/exchange_online/test_ccxt_compat.py b/tests/exchange_online/test_ccxt_compat.py
index 49fbfc60d..408f47e7d 100644
--- a/tests/exchange_online/test_ccxt_compat.py
+++ b/tests/exchange_online/test_ccxt_compat.py
@@ -106,9 +106,7 @@ class TestCCXTExchange:
assert isinstance(fees, list)
for fee in fees:
assert isinstance(fee, dict)
- assert isinstance(fee["cost"], str)
- # TODO: this should be a float!
- # assert isinstance(fee["cost"], float)
+ assert isinstance(fee["cost"], float)
assert isinstance(fee["currency"], str)
else:
diff --git a/tests/exchange_online/test_ccxt_ws_compat.py b/tests/exchange_online/test_ccxt_ws_compat.py
index ed449bb58..8083a926c 100644
--- a/tests/exchange_online/test_ccxt_ws_compat.py
+++ b/tests/exchange_online/test_ccxt_ws_compat.py
@@ -30,6 +30,12 @@ class TestCCXTExchangeWs:
m_hist = mocker.spy(exch, "_async_get_historic_ohlcv")
m_cand = mocker.spy(exch, "_async_get_candle_history")
+ while True:
+ # Don't start the test if we are too close to the end of the minute.
+ if dt_now().second < 50 and dt_now().second > 1:
+ break
+ sleep(1)
+
res = exch.refresh_latest_ohlcv([pair_tf])
assert m_cand.call_count == 1
diff --git a/tests/freqtradebot/test_freqtradebot.py b/tests/freqtradebot/test_freqtradebot.py
index 23dfbb785..7c45928a2 100644
--- a/tests/freqtradebot/test_freqtradebot.py
+++ b/tests/freqtradebot/test_freqtradebot.py
@@ -4909,7 +4909,7 @@ def test_handle_onexchange_order_changed_amount(
leverage=1,
)
freqtrade.wallets = MagicMock()
- freqtrade.wallets.get_total = MagicMock(return_value=entry_order["amount"] * factor)
+ freqtrade.wallets.get_owned = MagicMock(return_value=entry_order["amount"] * factor)
trade.orders.append(Order.parse_from_ccxt_object(entry_order, "ADA/USDT", entry_side(is_short)))
Trade.session.add(trade)
diff --git a/tests/optimize/test_backtest_detail.py b/tests/optimize/test_backtest_detail.py
index edaedb81e..e0a9e4480 100644
--- a/tests/optimize/test_backtest_detail.py
+++ b/tests/optimize/test_backtest_detail.py
@@ -1249,7 +1249,7 @@ def test_backtest_results(default_conf, mocker, caplog, data: BTContainer) -> No
assert res.open_date == _get_frame_time_from_offset(trade.open_tick)
assert res.close_date == _get_frame_time_from_offset(trade.close_tick)
assert res.is_short == trade.is_short
- assert len(LocalTrade.trades) == len(data.trades)
- assert len(LocalTrade.trades_open) == 0
+ assert len(LocalTrade.bt_trades) == len(data.trades)
+ assert len(LocalTrade.bt_trades_open) == 0
backtesting.cleanup()
del backtesting
diff --git a/tests/optimize/test_backtesting.py b/tests/optimize/test_backtesting.py
index e9829a8cc..d159c8602 100644
--- a/tests/optimize/test_backtesting.py
+++ b/tests/optimize/test_backtesting.py
@@ -1,6 +1,7 @@
# pragma pylint: disable=missing-docstring, W0212, line-too-long, C0103, unused-argument
import random
+from collections import defaultdict
from copy import deepcopy
from datetime import datetime, timedelta, timezone
from pathlib import Path
@@ -15,7 +16,7 @@ from freqtrade.commands.optimize_commands import setup_optimize_configuration, s
from freqtrade.configuration import TimeRange
from freqtrade.data import history
from freqtrade.data.btanalysis import BT_DATA_COLUMNS, evaluate_result_multi
-from freqtrade.data.converter import clean_ohlcv_dataframe
+from freqtrade.data.converter import clean_ohlcv_dataframe, ohlcv_fill_up_missing_data
from freqtrade.data.dataprovider import DataProvider
from freqtrade.data.history import get_timerange
from freqtrade.enums import CandleType, ExitType, RunMode
@@ -29,6 +30,7 @@ from freqtrade.util.datetime_helpers import dt_utc
from tests.conftest import (
CURRENT_TEST_STRATEGY,
EXMS,
+ generate_test_data,
get_args,
log_has,
log_has_re,
@@ -529,39 +531,39 @@ def test_backtest__enter_trade(default_conf, fee, mocker) -> None:
assert trade.stake_amount == 495
# Fake 2 trades, so there's not enough amount for the next trade left.
- LocalTrade.trades_open.append(trade)
+ LocalTrade.bt_trades_open.append(trade)
backtesting.wallets.update()
trade = backtesting._enter_trade(pair, row=row, direction="long")
assert trade is None
- LocalTrade.trades_open.pop()
+ LocalTrade.bt_trades_open.pop()
trade = backtesting._enter_trade(pair, row=row, direction="long")
assert trade is not None
- LocalTrade.trades_open.pop()
+ LocalTrade.bt_trades_open.pop()
backtesting.strategy.custom_stake_amount = lambda **kwargs: 123.5
backtesting.wallets.update()
trade = backtesting._enter_trade(pair, row=row, direction="long")
- LocalTrade.trades_open.pop()
+ LocalTrade.bt_trades_open.pop()
assert trade
assert trade.stake_amount == 123.5
# In case of error - use proposed stake
backtesting.strategy.custom_stake_amount = lambda **kwargs: 20 / 0
trade = backtesting._enter_trade(pair, row=row, direction="long")
- LocalTrade.trades_open.pop()
+ LocalTrade.bt_trades_open.pop()
assert trade
assert trade.stake_amount == 495
assert trade.is_short is False
trade = backtesting._enter_trade(pair, row=row, direction="short")
- LocalTrade.trades_open.pop()
+ LocalTrade.bt_trades_open.pop()
assert trade
assert trade.stake_amount == 495
assert trade.is_short is True
mocker.patch(f"{EXMS}.get_max_pair_stake_amount", return_value=300.0)
trade = backtesting._enter_trade(pair, row=row, direction="long")
- LocalTrade.trades_open.pop()
+ LocalTrade.bt_trades_open.pop()
assert trade
assert trade.stake_amount == 300.0
@@ -1041,7 +1043,7 @@ def test_backtest_one_detail_futures(
<= round(t["close_rate"], 6)
<= round(ln2.iloc[0]["high"], 6)
)
- assert pytest.approx(Trade.trades[1].funding_fees) == exp_funding_fee
+ assert pytest.approx(Trade.bt_trades[1].funding_fees) == exp_funding_fee
assert ff_spy.call_count == exp_ff_updates
# assert late_entry > 0
@@ -1136,7 +1138,7 @@ def test_backtest_one_detail_futures_funding_fees(
# Additional counts will happen due each successful entry, which needs to call this, too.
assert ff_spy.call_count == ff_updates
- for t in Trade.trades:
+ for t in Trade.bt_trades:
# At least 6 adjustment orders
assert t.nr_of_successful_entries == entries
# Funding fees will vary depending on the number of adjustment orders
@@ -1485,7 +1487,9 @@ def test_backtest_multi_pair(default_conf, fee, mocker, tres, pair, testdatadir)
default_conf["max_open_trades"] = 3
backtesting = Backtesting(default_conf)
+ vr_spy = mocker.spy(backtesting, "validate_row")
backtesting._set_strategy(backtesting.strategylist[0])
+ backtesting.strategy.bot_loop_start = MagicMock()
backtesting.strategy.advise_entry = _trend_alternate_hold # Override
backtesting.strategy.advise_exit = _trend_alternate_hold # Override
@@ -1500,6 +1504,38 @@ def test_backtest_multi_pair(default_conf, fee, mocker, tres, pair, testdatadir)
results = backtesting.backtest(**backtest_conf)
+ # bot_loop_start is called once per candle.
+ assert backtesting.strategy.bot_loop_start.call_count == 499
+ # Validated row once per candle and pair
+ assert vr_spy.call_count == 2495
+ # List of calls pair args - in batches of 5 (s)
+ calls_per_candle = defaultdict(list)
+ for call in vr_spy.call_args_list:
+ calls_per_candle[call[0][3]].append(call[0][1])
+
+ all_orients = [x for _, x in calls_per_candle.items()]
+
+ distinct_calls = [list(x) for x in set(tuple(x) for x in all_orients)]
+
+ # All calls must be made for the full pairlist
+ assert all(len(x) == 5 for x in distinct_calls)
+
+ # order varied - and is not always identical
+ assert not all(
+ x == ["ADA/BTC", "DASH/BTC", "ETH/BTC", "LTC/BTC", "NXT/BTC"] for x in distinct_calls
+ )
+ # But some calls should've kept the original ordering
+ assert any(
+ x == ["ADA/BTC", "DASH/BTC", "ETH/BTC", "LTC/BTC", "NXT/BTC"] for x in distinct_calls
+ )
+ assert (
+ # Ordering can be different, but should be one of the following
+ any(x == ["ETH/BTC", "ADA/BTC", "DASH/BTC", "LTC/BTC", "NXT/BTC"] for x in distinct_calls)
+ or any(
+ x == ["ETH/BTC", "LTC/BTC", "ADA/BTC", "DASH/BTC", "NXT/BTC"] for x in distinct_calls
+ )
+ )
+
# Make sure we have parallel trades
assert len(evaluate_result_multi(results["results"], "5m", 2)) > 0
# make sure we don't have trades with more than configured max_open_trades
@@ -1525,6 +1561,232 @@ def test_backtest_multi_pair(default_conf, fee, mocker, tres, pair, testdatadir)
assert len(evaluate_result_multi(results["results"], "5m", 1)) == 0
+@pytest.mark.parametrize("use_detail", [True, False])
+@pytest.mark.parametrize("pair", ["ADA/USDT", "LTC/USDT"])
+@pytest.mark.parametrize("tres", [0, 20, 30])
+def test_backtest_multi_pair_detail(
+ default_conf_usdt,
+ fee,
+ mocker,
+ tres,
+ pair,
+ use_detail,
+):
+ """
+ literally the same as test_backtest_multi_pair - but with artificial data
+ and detail timeframe.
+ """
+
+ def _trend_alternate_hold(dataframe=None, metadata=None):
+ """
+ Buy every xth candle - sell every other xth -2 (hold on to pairs a bit)
+ """
+ if metadata["pair"] in ("ETH/USDT", "LTC/USDT"):
+ multi = 20
+ else:
+ multi = 18
+ dataframe["enter_long"] = np.where(dataframe.index % multi == 0, 1, 0)
+ dataframe["exit_long"] = np.where((dataframe.index + multi - 2) % multi == 0, 1, 0)
+ dataframe["enter_short"] = 0
+ dataframe["exit_short"] = 0
+ return dataframe
+
+ default_conf_usdt.update(
+ {
+ "runmode": "backtest",
+ "stoploss": -1.0,
+ "minimal_roi": {"0": 100},
+ }
+ )
+
+ if use_detail:
+ default_conf_usdt["timeframe_detail"] = "1m"
+
+ mocker.patch(f"{EXMS}.get_min_pair_stake_amount", return_value=0.00001)
+ mocker.patch(f"{EXMS}.get_max_pair_stake_amount", return_value=float("inf"))
+ mocker.patch(f"{EXMS}.get_fee", fee)
+ patch_exchange(mocker)
+
+ raw_candles_1m = generate_test_data("1m", 1000, "2022-01-03 12:00:00+00:00")
+ raw_candles = ohlcv_fill_up_missing_data(raw_candles_1m, "5m", "dummy")
+
+ pairs = ["ADA/USDT", "DASH/USDT", "ETH/USDT", "LTC/USDT", "NXT/USDT"]
+ data = {pair: raw_candles for pair in pairs}
+ detail_data = {pair: raw_candles_1m for pair in pairs}
+
+ # Only use 500 lines to increase performance
+ data = trim_dictlist(data, -200)
+
+ # Remove data for one pair from the beginning of the data
+ if tres > 0:
+ data[pair] = data[pair][tres:].reset_index()
+ default_conf_usdt["timeframe"] = "5m"
+ default_conf_usdt["max_open_trades"] = 3
+
+ backtesting = Backtesting(default_conf_usdt)
+ vr_spy = mocker.spy(backtesting, "validate_row")
+ bl_spy = mocker.spy(backtesting, "backtest_loop")
+ backtesting.detail_data = detail_data
+ backtesting._set_strategy(backtesting.strategylist[0])
+ backtesting.strategy.bot_loop_start = MagicMock()
+ backtesting.strategy.advise_entry = _trend_alternate_hold # Override
+ backtesting.strategy.advise_exit = _trend_alternate_hold # Override
+
+ processed = backtesting.strategy.advise_all_indicators(data)
+ min_date, max_date = get_timerange(processed)
+
+ backtest_conf = {
+ "processed": deepcopy(processed),
+ "start_date": min_date,
+ "end_date": max_date,
+ }
+
+ results = backtesting.backtest(**backtest_conf)
+
+ # bot_loop_start is called once per candle.
+ assert backtesting.strategy.bot_loop_start.call_count == 199
+ # Validated row once per candle and pair
+ assert vr_spy.call_count == 995
+
+ if use_detail:
+ # Backtest loop is called once per candle per pair
+ # Exact numbers depend on trade state - but should be around 3_800
+ assert bl_spy.call_count > 1_350
+ assert bl_spy.call_count < 1_500
+ else:
+ assert bl_spy.call_count < 995
+
+ # Make sure we have parallel trades
+ assert len(evaluate_result_multi(results["results"], "5m", 2)) > 0
+ # make sure we don't have trades with more than configured max_open_trades
+ assert len(evaluate_result_multi(results["results"], "5m", 3)) == 0
+
+ # Cached data correctly removed amounts
+ offset = 1 if tres == 0 else 0
+ removed_candles = len(data[pair]) - offset
+ assert len(backtesting.dataprovider.get_analyzed_dataframe(pair, "5m")[0]) == removed_candles
+ assert (
+ len(backtesting.dataprovider.get_analyzed_dataframe("NXT/USDT", "5m")[0])
+ == len(data["NXT/USDT"]) - 1
+ )
+
+ backtesting.strategy.max_open_trades = 1
+ backtesting.config.update({"max_open_trades": 1})
+ backtest_conf = {
+ "processed": deepcopy(processed),
+ "start_date": min_date,
+ "end_date": max_date,
+ }
+ results = backtesting.backtest(**backtest_conf)
+ assert len(evaluate_result_multi(results["results"], "5m", 1)) == 0
+
+
+@pytest.mark.parametrize("use_detail", [True, False])
+def test_backtest_multi_pair_long_short_switch(
+ default_conf_usdt,
+ fee,
+ mocker,
+ use_detail,
+):
+ """
+ literally the same as test_backtest_multi_pair - but with artificial data
+ and detail timeframe.
+ """
+
+ def _trend_alternate_hold(dataframe=None, metadata=None):
+ """
+ Buy every xth candle - sell every other xth -2 (hold on to pairs a bit)
+ """
+ if metadata["pair"] in ("ETH/USDT", "LTC/USDT"):
+ multi = 20
+ else:
+ multi = 18
+ dataframe["enter_long"] = np.where(dataframe.index % multi == 0, 1, 0)
+ dataframe["exit_long"] = np.where((dataframe.index + multi - 2) % multi == 0, 1, 0)
+ dataframe["enter_short"] = dataframe["exit_long"]
+ dataframe["exit_short"] = dataframe["enter_long"]
+ return dataframe
+
+ default_conf_usdt.update(
+ {
+ "runmode": "backtest",
+ "timeframe": "5m",
+ "max_open_trades": 1,
+ "stoploss": -1.0,
+ "minimal_roi": {"0": 100},
+ "margin_mode": "isolated",
+ "trading_mode": "futures",
+ }
+ )
+
+ if use_detail:
+ default_conf_usdt["timeframe_detail"] = "1m"
+
+ mocker.patch(f"{EXMS}.get_min_pair_stake_amount", return_value=0.00001)
+ mocker.patch(f"{EXMS}.get_max_pair_stake_amount", return_value=float("inf"))
+ mocker.patch(f"{EXMS}.get_fee", fee)
+ patch_exchange(mocker)
+
+ raw_candles_1m = generate_test_data("1m", 2500, "2022-01-03 12:00:00+00:00")
+ raw_candles = ohlcv_fill_up_missing_data(raw_candles_1m, "5m", "dummy")
+
+ pairs = [
+ "ETH/USDT:USDT",
+ ]
+ default_conf_usdt["exchange"]["pair_whitelist"] = pairs
+ # Fake whitelist to avoid some mock data issues
+ mocker.patch(f"{EXMS}.get_maintenance_ratio_and_amt", return_value=(0.01, 0.01))
+
+ data = {pair: raw_candles for pair in pairs}
+ detail_data = {pair: raw_candles_1m for pair in pairs}
+
+ # Only use 500 lines to increase performance
+ data = trim_dictlist(data, -500)
+
+ backtesting = Backtesting(default_conf_usdt)
+ vr_spy = mocker.spy(backtesting, "validate_row")
+ bl_spy = mocker.spy(backtesting, "backtest_loop")
+ backtesting.detail_data = detail_data
+ backtesting.funding_fee_timeframe_secs = 3600 * 8 # 8h
+ backtesting.futures_data = {pair: pd.DataFrame() for pair in pairs}
+
+ backtesting.strategylist[0].can_short = True
+ backtesting._set_strategy(backtesting.strategylist[0])
+ backtesting.strategy.bot_loop_start = MagicMock()
+ backtesting.strategy.advise_entry = _trend_alternate_hold # Override
+ backtesting.strategy.advise_exit = _trend_alternate_hold # Override
+
+ processed = backtesting.strategy.advise_all_indicators(data)
+ min_date, max_date = get_timerange(processed)
+
+ backtest_conf = {
+ "processed": deepcopy(processed),
+ "start_date": min_date,
+ "end_date": max_date,
+ }
+
+ results = backtesting.backtest(**backtest_conf)
+
+ # bot_loop_start is called once per candle.
+ assert backtesting.strategy.bot_loop_start.call_count == 499
+ # Validated row once per candle and pair
+ assert vr_spy.call_count == 499
+
+ if use_detail:
+ # Backtest loop is called once per candle per pair
+ assert bl_spy.call_count == 1071
+ else:
+ assert bl_spy.call_count == 479
+
+ # Make sure we have parallel trades
+ assert len(evaluate_result_multi(results["results"], "5m", 0)) > 0
+ # make sure we don't have trades with more than configured max_open_trades
+ assert len(evaluate_result_multi(results["results"], "5m", 1)) == 0
+
+ # Expect 26 results initially
+ assert len(results["results"]) == 30
+
+
def test_backtest_start_timerange(default_conf, mocker, caplog, testdatadir):
patch_exchange(mocker)
mocker.patch("freqtrade.optimize.backtesting.Backtesting.backtest")
diff --git a/tests/persistence/test_persistence.py b/tests/persistence/test_persistence.py
index 0545ac861..05160c74a 100644
--- a/tests/persistence/test_persistence.py
+++ b/tests/persistence/test_persistence.py
@@ -1404,6 +1404,7 @@ def test_to_json(fee):
exchange="binance",
enter_tag=None,
precision_mode=1,
+ precision_mode_price=1,
amount_precision=8.0,
price_precision=7.0,
contract_size=1,
@@ -1473,6 +1474,7 @@ def test_to_json(fee):
"amount_precision": 8.0,
"price_precision": 7.0,
"precision_mode": 1,
+ "precision_mode_price": 1,
"contract_size": 1,
"orders": [],
"has_open_orders": False,
@@ -1493,6 +1495,7 @@ def test_to_json(fee):
enter_tag="buys_signal_001",
exchange="binance",
precision_mode=2,
+ precision_mode_price=1,
amount_precision=7.0,
price_precision=8.0,
contract_size=1,
@@ -1562,6 +1565,7 @@ def test_to_json(fee):
"amount_precision": 7.0,
"price_precision": 8.0,
"precision_mode": 2,
+ "precision_mode_price": 1,
"contract_size": 1,
"orders": [],
"has_open_orders": False,
@@ -2136,11 +2140,11 @@ def test_Trade_object_idem():
"custom_data",
)
EXCLUDES2 = (
- "trades",
- "trades_open",
+ "bt_trades",
+ "bt_trades_open",
"bt_trades_open_pp",
"bt_open_open_trade_count",
- "total_profit",
+ "bt_total_profit",
"from_json",
)
@@ -2571,7 +2575,7 @@ def test_recalc_trade_from_orders_ignores_bad_orders(fee, is_short):
assert trade.amount == 2 * o1_amount
assert trade.stake_amount == 2 * o1_amount
assert trade.open_rate == o1_rate
- assert trade.fee_open_cost == 2 * o1_fee_cost
+ assert trade.fee_open_cost == trade.nr_of_successful_entries * o1_fee_cost
assert trade.open_trade_value == 2 * o1_trade_val
assert trade.nr_of_successful_entries == 2
@@ -2598,7 +2602,7 @@ def test_recalc_trade_from_orders_ignores_bad_orders(fee, is_short):
assert trade.amount == o1_amount
assert trade.stake_amount == o1_amount
assert trade.open_rate == o1_rate
- assert trade.fee_open_cost == o1_fee_cost
+ assert trade.fee_open_cost == trade.nr_of_successful_entries * o1_fee_cost
assert trade.open_trade_value == o1_trade_val
assert trade.nr_of_successful_entries == 2
@@ -2626,7 +2630,7 @@ def test_recalc_trade_from_orders_ignores_bad_orders(fee, is_short):
assert trade.amount == 2 * o1_amount
assert trade.stake_amount == 2 * o1_amount
assert trade.open_rate == o1_rate
- assert trade.fee_open_cost == 2 * o1_fee_cost
+ assert trade.fee_open_cost == trade.nr_of_successful_entries * o1_fee_cost
assert trade.open_trade_value == 2 * o1_trade_val
assert trade.nr_of_successful_entries == 3
diff --git a/tests/plugins/test_percentchangepairlist.py b/tests/plugins/test_percentchangepairlist.py
new file mode 100644
index 000000000..df165cf98
--- /dev/null
+++ b/tests/plugins/test_percentchangepairlist.py
@@ -0,0 +1,370 @@
+from datetime import datetime, timezone
+from unittest.mock import MagicMock
+
+import pandas as pd
+import pytest
+
+from freqtrade.data.converter import ohlcv_to_dataframe
+from freqtrade.enums import CandleType
+from freqtrade.exceptions import OperationalException
+from freqtrade.plugins.pairlist.PercentChangePairList import PercentChangePairList
+from freqtrade.plugins.pairlistmanager import PairListManager
+from tests.conftest import (
+ EXMS,
+ generate_test_data_raw,
+ get_patched_exchange,
+ get_patched_freqtradebot,
+)
+
+
+@pytest.fixture(scope="function")
+def rpl_config(default_conf):
+ default_conf["stake_currency"] = "USDT"
+
+ default_conf["exchange"]["pair_whitelist"] = [
+ "ETH/USDT",
+ "XRP/USDT",
+ ]
+ default_conf["exchange"]["pair_blacklist"] = ["BLK/USDT"]
+
+ return default_conf
+
+
+def test_volume_change_pair_list_init_exchange_support(mocker, rpl_config):
+ rpl_config["pairlists"] = [
+ {
+ "method": "PercentChangePairList",
+ "number_assets": 2,
+ "sort_key": "percentage",
+ "min_value": 0,
+ "refresh_period": 86400,
+ }
+ ]
+
+ with pytest.raises(
+ OperationalException,
+ match=r"Exchange does not support dynamic whitelist in this configuration. "
+ r"Please edit your config and either remove PercentChangePairList, "
+ r"or switch to using candles. and restart the bot.",
+ ):
+ get_patched_freqtradebot(mocker, rpl_config)
+
+
+def test_volume_change_pair_list_init_wrong_refresh_period(mocker, rpl_config):
+ rpl_config["pairlists"] = [
+ {
+ "method": "PercentChangePairList",
+ "number_assets": 2,
+ "sort_key": "percentage",
+ "min_value": 0,
+ "refresh_period": 1800,
+ "lookback_days": 4,
+ }
+ ]
+
+ with pytest.raises(
+ OperationalException,
+ match=r"Refresh period of 1800 seconds is smaller than one "
+ r"timeframe of 1d. Please adjust refresh_period "
+ r"to at least 86400 and restart the bot.",
+ ):
+ get_patched_freqtradebot(mocker, rpl_config)
+
+
+def test_volume_change_pair_list_init_wrong_lookback_period(mocker, rpl_config):
+ rpl_config["pairlists"] = [
+ {
+ "method": "PercentChangePairList",
+ "number_assets": 2,
+ "sort_key": "percentage",
+ "min_value": 0,
+ "refresh_period": 86400,
+ "lookback_days": 3,
+ "lookback_period": 3,
+ }
+ ]
+
+ with pytest.raises(
+ OperationalException,
+ match=r"Ambiguous configuration: lookback_days "
+ r"and lookback_period both set in pairlist config. "
+ r"Please set lookback_days only or lookback_period "
+ r"and lookback_timeframe and restart the bot.",
+ ):
+ get_patched_freqtradebot(mocker, rpl_config)
+
+ rpl_config["pairlists"] = [
+ {
+ "method": "PercentChangePairList",
+ "number_assets": 2,
+ "sort_key": "percentage",
+ "min_value": 0,
+ "refresh_period": 86400,
+ "lookback_days": 1001,
+ }
+ ]
+
+ with pytest.raises(
+ OperationalException,
+ match=r"ChangeFilter requires lookback_period to not exceed"
+ r" exchange max request size \(1000\)",
+ ):
+ get_patched_freqtradebot(mocker, rpl_config)
+
+
+def test_volume_change_pair_list_init_wrong_config(mocker, rpl_config):
+ rpl_config["pairlists"] = [
+ {
+ "method": "PercentChangePairList",
+ "sort_key": "percentage",
+ "min_value": 0,
+ "refresh_period": 86400,
+ }
+ ]
+
+ with pytest.raises(
+ OperationalException,
+ match=r"`number_assets` not specified. Please check your configuration "
+ r'for "pairlist.config.number_assets"',
+ ):
+ get_patched_freqtradebot(mocker, rpl_config)
+
+
+def test_gen_pairlist_with_valid_change_pair_list_config(mocker, rpl_config, tickers, time_machine):
+ rpl_config["pairlists"] = [
+ {
+ "method": "PercentChangePairList",
+ "number_assets": 2,
+ "sort_key": "percentage",
+ "min_value": 0,
+ "refresh_period": 86400,
+ "lookback_days": 4,
+ }
+ ]
+ start = datetime(2024, 8, 1, 0, 0, 0, 0, tzinfo=timezone.utc)
+ time_machine.move_to(start, tick=False)
+
+ mock_ohlcv_data = {
+ ("ETH/USDT", "1d", CandleType.SPOT): pd.DataFrame(
+ ohlcv_to_dataframe(
+ generate_test_data_raw("1d", 100, start.strftime("%Y-%m-%d"), random_seed=12),
+ "1d",
+ pair="ETH/USDT",
+ fill_missing=True,
+ )
+ ),
+ ("BTC/USDT", "1d", CandleType.SPOT): pd.DataFrame(
+ ohlcv_to_dataframe(
+ generate_test_data_raw("1d", 100, start.strftime("%Y-%m-%d"), random_seed=13),
+ "1d",
+ pair="BTC/USDT",
+ fill_missing=True,
+ )
+ ),
+ ("XRP/USDT", "1d", CandleType.SPOT): pd.DataFrame(
+ ohlcv_to_dataframe(
+ generate_test_data_raw("1d", 100, start.strftime("%Y-%m-%d"), random_seed=14),
+ "1d",
+ pair="XRP/USDT",
+ fill_missing=True,
+ )
+ ),
+ ("NEO/USDT", "1d", CandleType.SPOT): pd.DataFrame(
+ ohlcv_to_dataframe(
+ generate_test_data_raw("1d", 100, start.strftime("%Y-%m-%d"), random_seed=15),
+ "1d",
+ pair="NEO/USDT",
+ fill_missing=True,
+ )
+ ),
+ ("TKN/USDT", "1d", CandleType.SPOT): pd.DataFrame(
+ # Make sure always have highest percentage
+ {
+ "timestamp": [
+ "2024-07-01 00:00:00",
+ "2024-07-01 01:00:00",
+ "2024-07-01 02:00:00",
+ "2024-07-01 03:00:00",
+ "2024-07-01 04:00:00",
+ "2024-07-01 05:00:00",
+ ],
+ "open": [100, 102, 101, 103, 104, 105],
+ "high": [102, 103, 102, 104, 105, 106],
+ "low": [99, 101, 100, 102, 103, 104],
+ "close": [101, 102, 103, 104, 105, 106],
+ "volume": [1000, 1500, 2000, 2500, 3000, 3500],
+ }
+ ),
+ }
+
+ mocker.patch(f"{EXMS}.refresh_latest_ohlcv", MagicMock(return_value=mock_ohlcv_data))
+
+ exchange = get_patched_exchange(mocker, rpl_config, exchange="binance")
+ pairlistmanager = PairListManager(exchange, rpl_config)
+
+ remote_pairlist = PercentChangePairList(
+ exchange, pairlistmanager, rpl_config, rpl_config["pairlists"][0], 0
+ )
+
+ result = remote_pairlist.gen_pairlist(tickers)
+
+ assert len(result) == 2
+ assert result == ["NEO/USDT", "TKN/USDT"]
+
+
+def test_filter_pairlist_with_empty_ticker(mocker, rpl_config, tickers, time_machine):
+ rpl_config["pairlists"] = [
+ {
+ "method": "PercentChangePairList",
+ "number_assets": 2,
+ "sort_key": "percentage",
+ "min_value": 0,
+ "refresh_period": 86400,
+ "sort_direction": "asc",
+ "lookback_days": 4,
+ }
+ ]
+ start = datetime(2024, 8, 1, 0, 0, 0, 0, tzinfo=timezone.utc)
+ time_machine.move_to(start, tick=False)
+
+ mock_ohlcv_data = {
+ ("ETH/USDT", "1d", CandleType.SPOT): pd.DataFrame(
+ {
+ "timestamp": [
+ "2024-07-01 00:00:00",
+ "2024-07-01 01:00:00",
+ "2024-07-01 02:00:00",
+ "2024-07-01 03:00:00",
+ "2024-07-01 04:00:00",
+ "2024-07-01 05:00:00",
+ ],
+ "open": [100, 102, 101, 103, 104, 105],
+ "high": [102, 103, 102, 104, 105, 106],
+ "low": [99, 101, 100, 102, 103, 104],
+ "close": [101, 102, 103, 104, 105, 105],
+ "volume": [1000, 1500, 2000, 2500, 3000, 3500],
+ }
+ ),
+ ("XRP/USDT", "1d", CandleType.SPOT): pd.DataFrame(
+ {
+ "timestamp": [
+ "2024-07-01 00:00:00",
+ "2024-07-01 01:00:00",
+ "2024-07-01 02:00:00",
+ "2024-07-01 03:00:00",
+ "2024-07-01 04:00:00",
+ "2024-07-01 05:00:00",
+ ],
+ "open": [100, 102, 101, 103, 104, 105],
+ "high": [102, 103, 102, 104, 105, 106],
+ "low": [99, 101, 100, 102, 103, 104],
+ "close": [101, 102, 103, 104, 105, 104],
+ "volume": [1000, 1500, 2000, 2500, 3000, 3400],
+ }
+ ),
+ }
+
+ mocker.patch(f"{EXMS}.refresh_latest_ohlcv", MagicMock(return_value=mock_ohlcv_data))
+ exchange = get_patched_exchange(mocker, rpl_config, exchange="binance")
+ pairlistmanager = PairListManager(exchange, rpl_config)
+
+ remote_pairlist = PercentChangePairList(
+ exchange, pairlistmanager, rpl_config, rpl_config["pairlists"][0], 0
+ )
+
+ result = remote_pairlist.filter_pairlist(rpl_config["exchange"]["pair_whitelist"], {})
+
+ assert len(result) == 2
+ assert result == ["XRP/USDT", "ETH/USDT"]
+
+
+def test_filter_pairlist_with_max_value_set(mocker, rpl_config, tickers, time_machine):
+ rpl_config["pairlists"] = [
+ {
+ "method": "PercentChangePairList",
+ "number_assets": 2,
+ "sort_key": "percentage",
+ "min_value": 0,
+ "max_value": 15,
+ "refresh_period": 86400,
+ "lookback_days": 4,
+ }
+ ]
+
+ start = datetime(2024, 8, 1, 0, 0, 0, 0, tzinfo=timezone.utc)
+ time_machine.move_to(start, tick=False)
+
+ mock_ohlcv_data = {
+ ("ETH/USDT", "1d", CandleType.SPOT): pd.DataFrame(
+ {
+ "timestamp": [
+ "2024-07-01 00:00:00",
+ "2024-07-01 01:00:00",
+ "2024-07-01 02:00:00",
+ "2024-07-01 03:00:00",
+ "2024-07-01 04:00:00",
+ "2024-07-01 05:00:00",
+ ],
+ "open": [100, 102, 101, 103, 104, 105],
+ "high": [102, 103, 102, 104, 105, 106],
+ "low": [99, 101, 100, 102, 103, 104],
+ "close": [101, 102, 103, 104, 105, 106],
+ "volume": [1000, 1500, 2000, 1800, 2400, 2500],
+ }
+ ),
+ ("XRP/USDT", "1d", CandleType.SPOT): pd.DataFrame(
+ {
+ "timestamp": [
+ "2024-07-01 00:00:00",
+ "2024-07-01 01:00:00",
+ "2024-07-01 02:00:00",
+ "2024-07-01 03:00:00",
+ "2024-07-01 04:00:00",
+ "2024-07-01 05:00:00",
+ ],
+ "open": [100, 102, 101, 103, 104, 105],
+ "high": [102, 103, 102, 104, 105, 106],
+ "low": [99, 101, 100, 102, 103, 104],
+ "close": [101, 102, 103, 104, 105, 101],
+ "volume": [1000, 1500, 2000, 2500, 3000, 3500],
+ }
+ ),
+ }
+
+ mocker.patch(f"{EXMS}.refresh_latest_ohlcv", MagicMock(return_value=mock_ohlcv_data))
+ exchange = get_patched_exchange(mocker, rpl_config, exchange="binance")
+ pairlistmanager = PairListManager(exchange, rpl_config)
+
+ remote_pairlist = PercentChangePairList(
+ exchange, pairlistmanager, rpl_config, rpl_config["pairlists"][0], 0
+ )
+
+ result = remote_pairlist.filter_pairlist(rpl_config["exchange"]["pair_whitelist"], {})
+
+ assert len(result) == 1
+ assert result == ["ETH/USDT"]
+
+
+def test_gen_pairlist_from_tickers(mocker, rpl_config, tickers):
+ rpl_config["pairlists"] = [
+ {
+ "method": "PercentChangePairList",
+ "number_assets": 2,
+ "sort_key": "percentage",
+ "min_value": 0,
+ }
+ ]
+
+ mocker.patch(f"{EXMS}.exchange_has", MagicMock(return_value=True))
+
+ exchange = get_patched_exchange(mocker, rpl_config, exchange="binance")
+ pairlistmanager = PairListManager(exchange, rpl_config)
+
+ remote_pairlist = PercentChangePairList(
+ exchange, pairlistmanager, rpl_config, rpl_config["pairlists"][0], 0
+ )
+
+ result = remote_pairlist.gen_pairlist(tickers.return_value)
+
+ assert len(result) == 1
+ assert result == ["ETH/USDT"]
diff --git a/tests/plugins/test_protections.py b/tests/plugins/test_protections.py
index c8a8fdf20..3fb27ce3d 100644
--- a/tests/plugins/test_protections.py
+++ b/tests/plugins/test_protections.py
@@ -102,56 +102,94 @@ def test_protectionmanager(mocker, default_conf):
@pytest.mark.parametrize(
- "timeframe,expected,protconf",
+ "timeframe,expected_lookback,expected_stop,protconf",
[
(
"1m",
- [20, 10],
+ 20,
+ 10,
[{"method": "StoplossGuard", "lookback_period_candles": 20, "stop_duration": 10}],
),
(
"5m",
- [100, 15],
+ 100,
+ 15,
[{"method": "StoplossGuard", "lookback_period_candles": 20, "stop_duration": 15}],
),
(
"1h",
- [1200, 40],
+ 1200,
+ 40,
[{"method": "StoplossGuard", "lookback_period_candles": 20, "stop_duration": 40}],
),
(
"1d",
- [1440, 5],
+ 1440,
+ 5,
[{"method": "StoplossGuard", "lookback_period_candles": 1, "stop_duration": 5}],
),
(
"1m",
- [20, 5],
+ 20,
+ 5,
[{"method": "StoplossGuard", "lookback_period": 20, "stop_duration_candles": 5}],
),
(
"5m",
- [15, 25],
+ 15,
+ 25,
[{"method": "StoplossGuard", "lookback_period": 15, "stop_duration_candles": 5}],
),
(
"1h",
- [50, 600],
+ 50,
+ 600,
[{"method": "StoplossGuard", "lookback_period": 50, "stop_duration_candles": 10}],
),
(
"1h",
- [60, 540],
+ 60,
+ 540,
[{"method": "StoplossGuard", "lookback_period_candles": 1, "stop_duration_candles": 9}],
),
+ (
+ "1m",
+ 20,
+ "01:00",
+ [{"method": "StoplossGuard", "lookback_period_candles": 20, "unlock_at": "01:00"}],
+ ),
+ (
+ "5m",
+ 100,
+ "02:00",
+ [{"method": "StoplossGuard", "lookback_period_candles": 20, "unlock_at": "02:00"}],
+ ),
+ (
+ "1h",
+ 1200,
+ "03:00",
+ [{"method": "StoplossGuard", "lookback_period_candles": 20, "unlock_at": "03:00"}],
+ ),
+ (
+ "1d",
+ 1440,
+ "04:00",
+ [{"method": "StoplossGuard", "lookback_period_candles": 1, "unlock_at": "04:00"}],
+ ),
],
)
-def test_protections_init(default_conf, timeframe, expected, protconf):
+def test_protections_init(default_conf, timeframe, expected_lookback, expected_stop, protconf):
+ """
+ Test the initialization of protections with different configurations, including unlock_at.
+ """
default_conf["timeframe"] = timeframe
man = ProtectionManager(default_conf, protconf)
assert len(man._protection_handlers) == len(protconf)
- assert man._protection_handlers[0]._lookback_period == expected[0]
- assert man._protection_handlers[0]._stop_duration == expected[1]
+ assert man._protection_handlers[0]._lookback_period == expected_lookback
+ if isinstance(expected_stop, int):
+ assert man._protection_handlers[0]._stop_duration == expected_stop
+ else:
+ assert man._protection_handlers[0]._unlock_at == expected_stop
@pytest.mark.parametrize("is_short", [False, True])
@@ -385,6 +423,89 @@ def test_CooldownPeriod(mocker, default_conf, fee, caplog):
assert not PairLocks.is_global_lock()
+@pytest.mark.usefixtures("init_persistence")
+def test_CooldownPeriod_unlock_at(mocker, default_conf, fee, caplog, time_machine):
+ default_conf["protections"] = [
+ {
+ "method": "CooldownPeriod",
+ "unlock_at": "05:00",
+ }
+ ]
+ freqtrade = get_patched_freqtradebot(mocker, default_conf)
+ message = r"Trading stopped due to .*"
+ assert not freqtrade.protections.global_stop()
+ assert not freqtrade.protections.stop_per_pair("XRP/BTC")
+
+ assert not log_has_re(message, caplog)
+ caplog.clear()
+
+ start_dt = datetime(2024, 5, 2, 0, 30, 0, tzinfo=timezone.utc)
+ time_machine.move_to(start_dt, tick=False)
+
+ generate_mock_trade(
+ "XRP/BTC",
+ fee.return_value,
+ False,
+ exit_reason=ExitType.STOP_LOSS.value,
+ min_ago_open=20,
+ min_ago_close=10,
+ )
+
+ assert not freqtrade.protections.global_stop()
+ assert freqtrade.protections.stop_per_pair("XRP/BTC")
+ assert PairLocks.is_pair_locked("XRP/BTC")
+ assert not PairLocks.is_global_lock()
+
+ # Move time to "4:30"
+ time_machine.move_to(start_dt + timedelta(hours=4), tick=False)
+ assert PairLocks.is_pair_locked("XRP/BTC")
+ assert not PairLocks.is_global_lock()
+
+ # Move time to "past 5:00"
+ time_machine.move_to(start_dt + timedelta(hours=5), tick=False)
+ assert not PairLocks.is_pair_locked("XRP/BTC")
+ assert not PairLocks.is_global_lock()
+
+ # Force rollover to the next day.
+ start_dt = datetime(2024, 5, 2, 22, 00, 0, tzinfo=timezone.utc)
+ time_machine.move_to(start_dt, tick=False)
+ generate_mock_trade(
+ "ETH/BTC",
+ fee.return_value,
+ False,
+ exit_reason=ExitType.ROI.value,
+ min_ago_open=20,
+ min_ago_close=10,
+ )
+
+ assert not freqtrade.protections.global_stop()
+ assert not PairLocks.is_pair_locked("ETH/BTC")
+ assert freqtrade.protections.stop_per_pair("ETH/BTC")
+ assert PairLocks.is_pair_locked("ETH/BTC")
+ assert not PairLocks.is_global_lock()
+ # Move to 23:00
+ time_machine.move_to(start_dt + timedelta(hours=1), tick=False)
+ assert PairLocks.is_pair_locked("ETH/BTC")
+ assert not PairLocks.is_global_lock()
+
+ # Move to 04:59 (should still be locked)
+ time_machine.move_to(start_dt + timedelta(hours=6, minutes=59), tick=False)
+ assert PairLocks.is_pair_locked("ETH/BTC")
+ assert not PairLocks.is_global_lock()
+
+ # Move to 05:01 (should still be locked - it unlocks once the 05:00 candle stops at 05:05)
+ time_machine.move_to(start_dt + timedelta(hours=7, minutes=1), tick=False)
+
+ assert PairLocks.is_pair_locked("ETH/BTC")
+ assert not PairLocks.is_global_lock()
+
+ # Move to 05:01 (unlocked).
+ time_machine.move_to(start_dt + timedelta(hours=7, minutes=5), tick=False)
+
+ assert not PairLocks.is_pair_locked("ETH/BTC")
+ assert not PairLocks.is_global_lock()
+
+
@pytest.mark.parametrize("only_per_side", [False, True])
@pytest.mark.usefixtures("init_persistence")
def test_LowProfitPairs(mocker, default_conf, fee, caplog, only_per_side):
@@ -610,7 +731,7 @@ def test_MaxDrawdown(mocker, default_conf, fee, caplog):
),
(
{"method": "CooldownPeriod", "stop_duration": 60},
- "[{'CooldownPeriod': 'CooldownPeriod - Cooldown period of 60 minutes.'}]",
+ "[{'CooldownPeriod': 'CooldownPeriod - Cooldown period for 60 minutes.'}]",
None,
),
(
@@ -639,7 +760,7 @@ def test_MaxDrawdown(mocker, default_conf, fee, caplog):
),
(
{"method": "CooldownPeriod", "stop_duration_candles": 5},
- "[{'CooldownPeriod': 'CooldownPeriod - Cooldown period of 5 candles.'}]",
+ "[{'CooldownPeriod': 'CooldownPeriod - Cooldown period for 5 candles.'}]",
None,
),
(
@@ -654,6 +775,38 @@ def test_MaxDrawdown(mocker, default_conf, fee, caplog):
"if drawdown is > 0.0 within 20 candles.'}]",
None,
),
+ (
+ {
+ "method": "CooldownPeriod",
+ "unlock_at": "01:00",
+ },
+ "[{'CooldownPeriod': 'CooldownPeriod - Cooldown period until 01:00.'}]",
+ None,
+ ),
+ (
+ {
+ "method": "StoplossGuard",
+ "lookback_period_candles": 12,
+ "trade_limit": 2,
+ "required_profit": -0.05,
+ "unlock_at": "01:00",
+ },
+ "[{'StoplossGuard': 'StoplossGuard - Frequent Stoploss Guard, "
+ "2 stoplosses with profit < -5.00% within 12 candles.'}]",
+ None,
+ ),
+ (
+ {"method": "LowProfitPairs", "lookback_period_candles": 11, "unlock_at": "03:00"},
+ "[{'LowProfitPairs': 'LowProfitPairs - Low Profit Protection, locks pairs with "
+ "profit < 0.0 within 11 candles.'}]",
+ None,
+ ),
+ (
+ {"method": "MaxDrawdown", "lookback_period_candles": 20, "unlock_at": "04:00"},
+ "[{'MaxDrawdown': 'MaxDrawdown - Max drawdown protection, stop trading "
+ "if drawdown is > 0.0 within 20 candles.'}]",
+ None,
+ ),
],
)
def test_protection_manager_desc(
diff --git a/tests/rpc/test_rpc.py b/tests/rpc/test_rpc.py
index a0c235cd5..feb7f9f1a 100644
--- a/tests/rpc/test_rpc.py
+++ b/tests/rpc/test_rpc.py
@@ -96,6 +96,7 @@ def test_rpc_trade_status(default_conf, ticker, fee, mocker) -> None:
"amount_precision": 8.0,
"price_precision": 8.0,
"precision_mode": 2,
+ "precision_mode_price": 2,
"contract_size": 1,
"has_open_orders": False,
"orders": [
@@ -600,7 +601,6 @@ def test_rpc_balance_handle(default_conf_usdt, mocker, tickers):
"est_stake_bot": 0,
"stake": "USDT",
"side": "long",
- "leverage": 1,
"position": 0,
"is_bot_managed": False,
"is_position": False,
@@ -615,7 +615,6 @@ def test_rpc_balance_handle(default_conf_usdt, mocker, tickers):
"est_stake_bot": 0,
"stake": "USDT",
"side": "long",
- "leverage": 1,
"position": 0,
"is_bot_managed": False,
"is_position": False,
@@ -630,7 +629,6 @@ def test_rpc_balance_handle(default_conf_usdt, mocker, tickers):
"est_stake_bot": 49.5,
"stake": "USDT",
"side": "long",
- "leverage": 1,
"position": 0,
"is_bot_managed": True,
"is_position": False,
@@ -644,7 +642,6 @@ def test_rpc_balance_handle(default_conf_usdt, mocker, tickers):
"est_stake": 20,
"est_stake_bot": 20,
"stake": "USDT",
- "leverage": 5.0,
"side": "short",
"is_bot_managed": True,
"is_position": True,
diff --git a/tests/rpc/test_rpc_apiserver.py b/tests/rpc/test_rpc_apiserver.py
index 22264ae54..4f5ef7860 100644
--- a/tests/rpc/test_rpc_apiserver.py
+++ b/tests/rpc/test_rpc_apiserver.py
@@ -574,7 +574,6 @@ def test_api_balance(botclient, mocker, rpc_balance, tickers):
"est_stake_bot": pytest.approx(11.879999),
"stake": "BTC",
"is_position": False,
- "leverage": 1.0,
"position": 0.0,
"side": "long",
"is_bot_managed": True,
@@ -2148,35 +2147,44 @@ def test_api_exchanges(botclient):
response = rc.json()
assert isinstance(response["exchanges"], list)
assert len(response["exchanges"]) > 20
- okx = [x for x in response["exchanges"] if x["name"] == "okx"][0]
+ okx = [x for x in response["exchanges"] if x["classname"] == "okx"][0]
assert okx == {
- "name": "okx",
+ "classname": "okx",
+ "name": "OKX",
"valid": True,
"supported": True,
"comment": "",
"dex": False,
+ "is_alias": False,
+ "alias_for": None,
"trade_modes": [
{"trading_mode": "spot", "margin_mode": ""},
{"trading_mode": "futures", "margin_mode": "isolated"},
],
}
- mexc = [x for x in response["exchanges"] if x["name"] == "mexc"][0]
+ mexc = [x for x in response["exchanges"] if x["classname"] == "mexc"][0]
assert mexc == {
- "name": "mexc",
+ "classname": "mexc",
+ "name": "MEXC Global",
"valid": True,
"supported": False,
"dex": False,
"comment": "",
+ "is_alias": False,
+ "alias_for": None,
"trade_modes": [{"trading_mode": "spot", "margin_mode": ""}],
}
- waves = [x for x in response["exchanges"] if x["name"] == "wavesexchange"][0]
+ waves = [x for x in response["exchanges"] if x["classname"] == "wavesexchange"][0]
assert waves == {
- "name": "wavesexchange",
+ "classname": "wavesexchange",
+ "name": "Waves.Exchange",
"valid": True,
"supported": False,
"dex": True,
"comment": ANY,
+ "is_alias": False,
+ "alias_for": None,
"trade_modes": [{"trading_mode": "spot", "margin_mode": ""}],
}
diff --git a/tests/rpc/test_rpc_telegram.py b/tests/rpc/test_rpc_telegram.py
index 6e4aa3384..971e13846 100644
--- a/tests/rpc/test_rpc_telegram.py
+++ b/tests/rpc/test_rpc_telegram.py
@@ -990,6 +990,79 @@ async def test_telegram_balance_handle(default_conf, update, mocker, rpc_balance
assert "*Estimated Value (Bot managed assets only)*:" in result
+async def test_telegram_balance_handle_futures(
+ default_conf, update, rpc_balance, mocker, tickers
+) -> None:
+ default_conf.update(
+ {
+ "dry_run": False,
+ "trading_mode": "futures",
+ "margin_mode": "isolated",
+ }
+ )
+ mock_pos = [
+ {
+ "symbol": "ETH/USDT:USDT",
+ "timestamp": None,
+ "datetime": None,
+ "initialMargin": 0.0,
+ "initialMarginPercentage": None,
+ "maintenanceMargin": 0.0,
+ "maintenanceMarginPercentage": 0.005,
+ "entryPrice": 0.0,
+ "notional": 10.0,
+ "leverage": 5.0,
+ "unrealizedPnl": 0.0,
+ "contracts": 1.0,
+ "contractSize": 1,
+ "marginRatio": None,
+ "liquidationPrice": 0.0,
+ "markPrice": 2896.41,
+ "collateral": 20,
+ "marginType": "isolated",
+ "side": "short",
+ "percentage": None,
+ },
+ {
+ "symbol": "XRP/USDT:USDT",
+ "timestamp": None,
+ "datetime": None,
+ "initialMargin": 0.0,
+ "initialMarginPercentage": None,
+ "maintenanceMargin": 0.0,
+ "maintenanceMarginPercentage": 0.005,
+ "entryPrice": 0.0,
+ "notional": 10.0,
+ "leverage": None,
+ "unrealizedPnl": 0.0,
+ "contracts": 1.0,
+ "contractSize": 1,
+ "marginRatio": None,
+ "liquidationPrice": 0.0,
+ "markPrice": 2896.41,
+ "collateral": 20,
+ "marginType": "isolated",
+ "side": "short",
+ "percentage": None,
+ },
+ ]
+ mocker.patch(f"{EXMS}.get_balances", return_value=rpc_balance)
+ mocker.patch(f"{EXMS}.fetch_positions", return_value=mock_pos)
+ mocker.patch(f"{EXMS}.get_tickers", tickers)
+ mocker.patch(f"{EXMS}.get_valid_pair_combination", side_effect=lambda a, b: f"{a}/{b}")
+
+ telegram, freqtradebot, msg_mock = get_telegram_testobject(mocker, default_conf)
+ patch_get_signal(freqtradebot)
+
+ await telegram._balance(update=update, context=MagicMock())
+ result = msg_mock.call_args_list[0][0][0]
+ assert msg_mock.call_count == 1
+
+ assert "ETH/USDT:USDT" in result
+ assert "`short: 10" in result
+ assert "XRP/USDT:USDT" in result
+
+
async def test_balance_handle_empty_response(default_conf, update, mocker) -> None:
default_conf["dry_run"] = False
mocker.patch(f"{EXMS}.get_balances", return_value={})
diff --git a/tests/strategy/test_interface.py b/tests/strategy/test_interface.py
index ab87e110e..5459a0ff2 100644
--- a/tests/strategy/test_interface.py
+++ b/tests/strategy/test_interface.py
@@ -579,6 +579,7 @@ def test_ft_stoploss_reached(
liquidation_price=liq,
price_precision=4,
precision_mode=2,
+ precision_mode_price=2,
)
trade.adjust_min_max_rates(trade.open_rate, trade.open_rate)
strategy.trailing_stop = trailing
diff --git a/tests/test_configuration.py b/tests/test_configuration.py
index e160130c7..d77fae6a8 100644
--- a/tests/test_configuration.py
+++ b/tests/test_configuration.py
@@ -840,6 +840,25 @@ def test_validate_whitelist(default_conf):
],
r"Protections must specify either `stop_duration`.*",
),
+ (
+ [
+ {
+ "method": "StoplossGuard",
+ "lookback_period": 20,
+ "stop_duration": 10,
+ "unlock_at": "20:02",
+ }
+ ],
+ r"Protections must specify either `unlock_at`, `stop_duration` or.*",
+ ),
+ (
+ [{"method": "StoplossGuard", "lookback_period_candles": 20, "unlock_at": "20:02"}],
+ None,
+ ),
+ (
+ [{"method": "StoplossGuard", "lookback_period_candles": 20, "unlock_at": "55:102"}],
+ "Invalid date format for unlock_at: 55:102.",
+ ),
],
)
def test_validate_protections(default_conf, protconf, expected):
@@ -1634,9 +1653,12 @@ def test_sanitize_config(default_conf_usdt):
res = sanitize_config(default_conf_usdt)
# Didn't modify original dict
assert default_conf_usdt["exchange"]["key"] != "REDACTED"
+ assert "accountId" not in default_conf_usdt["exchange"]
assert res["exchange"]["key"] == "REDACTED"
assert res["exchange"]["secret"] == "REDACTED"
+ # Didn't add a non-existing key
+ assert "accountId" not in res["exchange"]
res = sanitize_config(default_conf_usdt, show_sensitive=True)
assert res["exchange"]["key"] == default_conf_usdt["exchange"]["key"]
diff --git a/tests/test_wallets.py b/tests/test_wallets.py
index f33222b7c..a2aebeea4 100644
--- a/tests/test_wallets.py
+++ b/tests/test_wallets.py
@@ -65,6 +65,7 @@ def test_sync_wallet_at_boot(mocker, default_conf):
assert freqtrade.wallets.get_free("GAS") == 0.270739
assert freqtrade.wallets.get_used("GAS") == 0.1
assert freqtrade.wallets.get_total("GAS") == 0.260439
+ assert freqtrade.wallets.get_owned("GAS/USDT", "GAS") == 0.260439
update_mock = mocker.patch("freqtrade.wallets.Wallets._update_live")
freqtrade.wallets.update(False)
assert update_mock.call_count == 0
@@ -74,6 +75,7 @@ def test_sync_wallet_at_boot(mocker, default_conf):
assert freqtrade.wallets.get_free("NOCURRENCY") == 0
assert freqtrade.wallets.get_used("NOCURRENCY") == 0
assert freqtrade.wallets.get_total("NOCURRENCY") == 0
+ assert freqtrade.wallets.get_owned("NOCURRENCY/USDT", "NOCURRENCY") == 0
def test_sync_wallet_missing_data(mocker, default_conf):
@@ -95,7 +97,7 @@ def test_sync_wallet_missing_data(mocker, default_conf):
assert freqtrade.wallets._wallets["BNT"].used == 2.0
assert freqtrade.wallets._wallets["BNT"].total == 3.0
assert freqtrade.wallets._wallets["GAS"].free == 0.260739
- assert freqtrade.wallets._wallets["GAS"].used is None
+ assert freqtrade.wallets._wallets["GAS"].used == 0.0
assert freqtrade.wallets._wallets["GAS"].total == 0.260739
assert freqtrade.wallets.get_free("GAS") == 0.260739
@@ -336,6 +338,8 @@ def test_sync_wallet_futures_live(mocker, default_conf):
assert "USDT" in freqtrade.wallets._wallets
assert "ETH/USDT:USDT" in freqtrade.wallets._positions
assert freqtrade.wallets._last_wallet_refresh is not None
+ assert freqtrade.wallets.get_owned("ETH/USDT:USDT", "ETH") == 1000
+ assert freqtrade.wallets.get_owned("SOL/USDT:USDT", "SOL") == 0
# Remove ETH/USDT:USDT position
del mock_result[0]