diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml index 7bc2ac1f5e7..fc8dbecbe21 100644 --- a/.github/workflows/ci.yml +++ b/.github/workflows/ci.yml @@ -318,6 +318,17 @@ jobs: run: | mypy freqtrade scripts tests + - name: Run Pester tests (PowerShell) + run: | + $PSVersionTable + Set-PSRepository psgallery -InstallationPolicy trusted + Install-Module -Name Pester -RequiredVersion 5.3.1 -Confirm:$false -Force + $Error.clear() + Invoke-Pester -Path "tests" -CI + if ($Error.Length -gt 0) {exit 1} + + shell: powershell + - name: Discord notification uses: rjstone/discord-webhook-notify@v1 if: failure() && ( github.event_name != 'pull_request' || github.event.pull_request.head.repo.fork == false) @@ -565,12 +576,12 @@ jobs: sudo systemctl restart docker docker version -f '{{.Server.Experimental}}' + - name: Set up QEMU + uses: docker/setup-qemu-action@v3 + - name: Set up Docker Buildx id: buildx - uses: crazy-max/ghaction-docker-buildx@v3.3.1 - with: - buildx-version: latest - qemu-version: latest + uses: docker/setup-buildx-action@v3 - name: Available platforms run: echo ${{ steps.buildx.outputs.platforms }} diff --git a/.github/workflows/pre-commit-update.yml b/.github/workflows/pre-commit-update.yml index e451e5a53db..d30fdd1bfe3 100644 --- a/.github/workflows/pre-commit-update.yml +++ b/.github/workflows/pre-commit-update.yml @@ -26,9 +26,6 @@ jobs: - name: Run auto-update run: pre-commit autoupdate - - name: Run pre-commit - run: pre-commit run --all-files - - uses: peter-evans/create-pull-request@v6 with: token: ${{ secrets.REPO_SCOPED_TOKEN }} diff --git a/.pre-commit-config.yaml b/.pre-commit-config.yaml index 1fdec721b3c..992e9b37358 100644 --- a/.pre-commit-config.yaml +++ b/.pre-commit-config.yaml @@ -16,7 +16,7 @@ repos: additional_dependencies: - types-cachetools==5.3.0.7 - types-filelock==3.2.7 - - types-requests==2.31.0.20240406 + - types-requests==2.32.0.20240602 - types-tabulate==0.9.0.20240106 - types-python-dateutil==2.9.0.20240316 - SQLAlchemy==2.0.30 @@ -31,7 +31,7 @@ repos: - repo: https://github.com/charliermarsh/ruff-pre-commit # Ruff version. - rev: 'v0.4.4' + rev: 'v0.4.7' hooks: - id: ruff @@ -56,7 +56,7 @@ repos: )$ - repo: https://github.com/codespell-project/codespell - rev: v2.2.6 + rev: v2.3.0 hooks: - id: codespell additional_dependencies: diff --git a/README.md b/README.md index c6e54b112db..d7ab7c05c94 100644 --- a/README.md +++ b/README.md @@ -29,6 +29,7 @@ Please read the [exchange specific notes](docs/exchanges.md) to learn about even - [X] [Binance](https://www.binance.com/) - [X] [Bitmart](https://bitmart.com/) +- [X] [BingX](https://bingx.com/invite/0EM9RX) - [X] [Gate.io](https://www.gate.io/ref/6266643) - [X] [HTX](https://www.htx.com/) (Former Huobi) - [X] [Kraken](https://kraken.com/) diff --git a/build_helpers/TA_Lib-0.4.28-cp310-cp310-win_amd64.whl b/build_helpers/TA_Lib-0.4.28-cp310-cp310-win_amd64.whl deleted file mode 100644 index cb7fdf907e5..00000000000 Binary files a/build_helpers/TA_Lib-0.4.28-cp310-cp310-win_amd64.whl and /dev/null differ diff --git a/build_helpers/TA_Lib-0.4.28-cp311-cp311-linux_armv7l.whl b/build_helpers/TA_Lib-0.4.28-cp311-cp311-linux_armv7l.whl deleted file mode 100644 index f88bc800230..00000000000 Binary files a/build_helpers/TA_Lib-0.4.28-cp311-cp311-linux_armv7l.whl and /dev/null differ diff --git a/build_helpers/TA_Lib-0.4.28-cp311-cp311-win_amd64.whl b/build_helpers/TA_Lib-0.4.28-cp311-cp311-win_amd64.whl deleted file mode 100644 index 4cb9021912f..00000000000 Binary files a/build_helpers/TA_Lib-0.4.28-cp311-cp311-win_amd64.whl and /dev/null differ diff --git a/build_helpers/TA_Lib-0.4.28-cp312-cp312-win_amd64.whl b/build_helpers/TA_Lib-0.4.28-cp312-cp312-win_amd64.whl deleted file mode 100644 index 81ccc4818de..00000000000 Binary files a/build_helpers/TA_Lib-0.4.28-cp312-cp312-win_amd64.whl and /dev/null differ diff --git a/build_helpers/TA_Lib-0.4.28-cp39-cp39-linux_armv7l.whl b/build_helpers/TA_Lib-0.4.28-cp39-cp39-linux_armv7l.whl deleted file mode 100644 index 596b5923de9..00000000000 Binary files a/build_helpers/TA_Lib-0.4.28-cp39-cp39-linux_armv7l.whl and /dev/null differ diff --git a/build_helpers/TA_Lib-0.4.28-cp39-cp39-win_amd64.whl b/build_helpers/TA_Lib-0.4.28-cp39-cp39-win_amd64.whl deleted file mode 100644 index 64a61ff0dd9..00000000000 Binary files a/build_helpers/TA_Lib-0.4.28-cp39-cp39-win_amd64.whl and /dev/null differ diff --git a/build_helpers/TA_Lib-0.4.30-cp310-cp310-win_amd64.whl b/build_helpers/TA_Lib-0.4.30-cp310-cp310-win_amd64.whl new file mode 100644 index 00000000000..68d6df6b37f Binary files /dev/null and b/build_helpers/TA_Lib-0.4.30-cp310-cp310-win_amd64.whl differ diff --git a/build_helpers/TA_Lib-0.4.30-cp311-cp311-linux_armv7l.whl b/build_helpers/TA_Lib-0.4.30-cp311-cp311-linux_armv7l.whl new file mode 100644 index 00000000000..941a2aa9d24 Binary files /dev/null and b/build_helpers/TA_Lib-0.4.30-cp311-cp311-linux_armv7l.whl differ diff --git a/build_helpers/TA_Lib-0.4.30-cp311-cp311-win_amd64.whl b/build_helpers/TA_Lib-0.4.30-cp311-cp311-win_amd64.whl new file mode 100644 index 00000000000..702f51559ba Binary files /dev/null and b/build_helpers/TA_Lib-0.4.30-cp311-cp311-win_amd64.whl differ diff --git a/build_helpers/TA_Lib-0.4.30-cp312-cp312-win_amd64.whl b/build_helpers/TA_Lib-0.4.30-cp312-cp312-win_amd64.whl new file mode 100644 index 00000000000..2a8253e3924 Binary files /dev/null and b/build_helpers/TA_Lib-0.4.30-cp312-cp312-win_amd64.whl differ diff --git a/build_helpers/TA_Lib-0.4.30-cp39-cp39-linux_armv7l.whl b/build_helpers/TA_Lib-0.4.30-cp39-cp39-linux_armv7l.whl new file mode 100644 index 00000000000..a5cab7919df Binary files /dev/null and b/build_helpers/TA_Lib-0.4.30-cp39-cp39-linux_armv7l.whl differ diff --git a/build_helpers/TA_Lib-0.4.30-cp39-cp39-win_amd64.whl b/build_helpers/TA_Lib-0.4.30-cp39-cp39-win_amd64.whl new file mode 100644 index 00000000000..a03c3ca8cef Binary files /dev/null and b/build_helpers/TA_Lib-0.4.30-cp39-cp39-win_amd64.whl differ diff --git a/docker/Dockerfile.armhf b/docker/Dockerfile.armhf index 1165f305c6f..6882541223e 100644 --- a/docker/Dockerfile.armhf +++ b/docker/Dockerfile.armhf @@ -35,7 +35,7 @@ COPY build_helpers/* /tmp/ COPY --chown=ftuser:ftuser requirements.txt /freqtrade/ USER ftuser RUN pip install --user --no-cache-dir numpy \ - && pip install --user --no-index --find-links /tmp/ pyarrow TA-Lib==0.4.28 \ + && pip install --user --no-index --find-links /tmp/ pyarrow TA-Lib \ && pip install --user --no-cache-dir -r requirements.txt # Copy dependencies to runtime-image diff --git a/docs/exchanges.md b/docs/exchanges.md index d4437cfff20..f3550e97e76 100644 --- a/docs/exchanges.md +++ b/docs/exchanges.md @@ -127,6 +127,13 @@ These settings will be checked on startup, and freqtrade will show an error if t Freqtrade will not attempt to change these settings. +## Bingx + +BingX supports [time_in_force](configuration.md#understand-order_time_in_force) with settings "GTC" (good till cancelled), "IOC" (immediate-or-cancel) and "PO" (Post only) settings. + +!!! Tip "Stoploss on Exchange" + Bingx supports `stoploss_on_exchange` and can use both stop-limit and stop-market orders. It provides great advantages, so we recommend to benefit from it by enabling stoploss on exchange. + ## Kraken Kraken supports [time_in_force](configuration.md#understand-order_time_in_force) with settings "GTC" (good till cancelled), "IOC" (immediate-or-cancel) and "PO" (Post only) settings. diff --git a/docs/freqai-feature-engineering.md b/docs/freqai-feature-engineering.md index f603baca3c7..d250512911f 100644 --- a/docs/freqai-feature-engineering.md +++ b/docs/freqai-feature-engineering.md @@ -224,7 +224,7 @@ where $W_i$ is the weight of data point $i$ in a total set of $n$ data points. B ## Building the data pipeline -By default, FreqAI builds a dynamic pipeline based on user congfiguration settings. The default settings are robust and designed to work with a variety of methods. These two steps are a `MinMaxScaler(-1,1)` and a `VarianceThreshold` which removes any column that has 0 variance. Users can activate other steps with more configuration parameters. For example if users add `use_SVM_to_remove_outliers: true` to the `freqai` config, then FreqAI will automatically add the [`SVMOutlierExtractor`](#identifying-outliers-using-a-support-vector-machine-svm) to the pipeline. Likewise, users can add `principal_component_analysis: true` to the `freqai` config to activate PCA. The [DissimilarityIndex](#identifying-outliers-with-the-dissimilarity-index-di) is activated with `DI_threshold: 1`. Finally, noise can also be added to the data with `noise_standard_deviation: 0.1`. Finally, users can add [DBSCAN](#identifying-outliers-with-dbscan) outlier removal with `use_DBSCAN_to_remove_outliers: true`. +By default, FreqAI builds a dynamic pipeline based on user configuration settings. The default settings are robust and designed to work with a variety of methods. These two steps are a `MinMaxScaler(-1,1)` and a `VarianceThreshold` which removes any column that has 0 variance. Users can activate other steps with more configuration parameters. For example if users add `use_SVM_to_remove_outliers: true` to the `freqai` config, then FreqAI will automatically add the [`SVMOutlierExtractor`](#identifying-outliers-using-a-support-vector-machine-svm) to the pipeline. Likewise, users can add `principal_component_analysis: true` to the `freqai` config to activate PCA. The [DissimilarityIndex](#identifying-outliers-with-the-dissimilarity-index-di) is activated with `DI_threshold: 1`. Finally, noise can also be added to the data with `noise_standard_deviation: 0.1`. Finally, users can add [DBSCAN](#identifying-outliers-with-dbscan) outlier removal with `use_DBSCAN_to_remove_outliers: true`. !!! note "More information available" Please review the [parameter table](freqai-parameter-table.md) for more information on these parameters. diff --git a/docs/index.md b/docs/index.md index 26debe993ec..55835f55564 100644 --- a/docs/index.md +++ b/docs/index.md @@ -41,6 +41,7 @@ Please read the [exchange specific notes](exchanges.md) to learn about eventual, - [X] [Binance](https://www.binance.com/) - [X] [Bitmart](https://bitmart.com/) +- [X] [BingX](https://bingx.com/invite/0EM9RX) - [X] [Gate.io](https://www.gate.io/ref/6266643) - [X] [HTX](https://www.htx.com/) (Former Huobi) - [X] [Kraken](https://kraken.com/) diff --git a/docs/requirements-docs.txt b/docs/requirements-docs.txt index 956d2288c33..c290fb85208 100644 --- a/docs/requirements-docs.txt +++ b/docs/requirements-docs.txt @@ -1,6 +1,6 @@ markdown==3.6 mkdocs==1.6.0 -mkdocs-material==9.5.23 +mkdocs-material==9.5.25 mdx_truly_sane_lists==1.3 pymdown-extensions==10.8.1 jinja2==3.1.4 diff --git a/docs/rest-api.md b/docs/rest-api.md index 51573b77fff..2b55c25631b 100644 --- a/docs/rest-api.md +++ b/docs/rest-api.md @@ -161,7 +161,7 @@ freqtrade-client --config rest_config.json [optional parameters] | `delete_lock ` | Deletes (disables) the lock by id. | `locks add , , [side], [reason]` | Locks a pair until "until". (Until will be rounded up to the nearest timeframe). | `profit` | Display a summary of your profit/loss from close trades and some stats about your performance. -| `forceexit ` | Instantly exits the given trade (Ignoring `minimum_roi`). +| `forceexit [order_type] [amount]` | Instantly exits the given trade (ignoring `minimum_roi`), using the given order type ("market" or "limit", uses your config setting if not specified), and the chosen amount (full sell if not specified). | `forceexit all` | Instantly exits all open trades (Ignoring `minimum_roi`). | `forceenter [rate]` | Instantly enters the given pair. Rate is optional. (`force_entry_enable` must be set to True) | `forceenter [rate]` | Instantly longs or shorts the given pair. Rate is optional. (`force_entry_enable` must be set to True) diff --git a/docs/stoploss.md b/docs/stoploss.md index a1095b46551..e0353d4da4b 100644 --- a/docs/stoploss.md +++ b/docs/stoploss.md @@ -30,6 +30,7 @@ The Order-type will be ignored if only one mode is available. |----------|-------------| | Binance | limit | | Binance Futures | market, limit | +| Bingx | market, limit | | HTX (former Huobi) | limit | | kraken | market, limit | | Gate | limit | diff --git a/docs/windows_installation.md b/docs/windows_installation.md index d513c0af5b6..5e28d98fb4e 100644 --- a/docs/windows_installation.md +++ b/docs/windows_installation.md @@ -5,6 +5,30 @@ We **strongly** recommend that Windows users use [Docker](docker_quickstart.md) If that is not possible, try using the Windows Linux subsystem (WSL) - for which the Ubuntu instructions should work. Otherwise, please follow the instructions below. +All instructions assume that python 3.9+ is installed and available. + +## Clone the git repository + +First of all clone the repository by running: + +``` powershell +git clone https://github.com/freqtrade/freqtrade.git +``` + +Now, choose your installation method, either automatically via script (recommended) or manually following the corresponding instructions. + +## Install freqtrade automatically + +### Run the installation script + +The script will ask you a few questions to determine which parts should be installed. + +```powershell +Set-ExecutionPolicy -ExecutionPolicy Bypass +cd freqtrade +. .\setup.ps1 +``` + ## Install freqtrade manually !!! Note "64bit Python version" @@ -14,13 +38,7 @@ Otherwise, please follow the instructions below. !!! Hint Using the [Anaconda Distribution](https://www.anaconda.com/distribution/) under Windows can greatly help with installation problems. Check out the [Anaconda installation section](installation.md#installation-with-conda) in the documentation for more information. -### 1. Clone the git repository - -```bash -git clone https://github.com/freqtrade/freqtrade.git -``` - -### 2. Install ta-lib +### Install ta-lib Install ta-lib according to the [ta-lib documentation](https://github.com/TA-Lib/ta-lib-python#windows). diff --git a/freqtrade/__init__.py b/freqtrade/__init__.py index d1cef043d40..932f9b70156 100644 --- a/freqtrade/__init__.py +++ b/freqtrade/__init__.py @@ -1,6 +1,6 @@ """Freqtrade bot""" -__version__ = "2024.5-dev" +__version__ = "2024.6-dev" if "dev" in __version__: from pathlib import Path diff --git a/freqtrade/commands/build_config_commands.py b/freqtrade/commands/build_config_commands.py index 76f3bb2af21..1e771a372cc 100644 --- a/freqtrade/commands/build_config_commands.py +++ b/freqtrade/commands/build_config_commands.py @@ -113,6 +113,7 @@ def ask_user_config() -> Dict[str, Any]: "choices": [ "binance", "binanceus", + "bingx", "gate", "htx", "kraken", @@ -128,7 +129,7 @@ def ask_user_config() -> Dict[str, Any]: "message": "Do you want to trade Perpetual Swaps (perpetual futures)?", "default": False, "filter": lambda val: "futures" if val else "spot", - "when": lambda x: x["exchange_name"] in ["binance", "gate", "okx"], + "when": lambda x: x["exchange_name"] in ["binance", "gate", "okx", "bybit"], }, { "type": "autocomplete", diff --git a/freqtrade/enums/runmode.py b/freqtrade/enums/runmode.py index d5c2cf6524a..a24dd6e2c59 100644 --- a/freqtrade/enums/runmode.py +++ b/freqtrade/enums/runmode.py @@ -1,7 +1,7 @@ from enum import Enum -class RunMode(Enum): +class RunMode(str, Enum): """ Bot running mode (backtest, hyperopt, ...) can be "live", "dry-run", "backtest", "edge", "hyperopt". diff --git a/freqtrade/exchange/binance_leverage_tiers.json b/freqtrade/exchange/binance_leverage_tiers.json index 56cf9e482c9..71744bf36d6 100644 --- a/freqtrade/exchange/binance_leverage_tiers.json +++ b/freqtrade/exchange/binance_leverage_tiers.json @@ -3171,104 +3171,6 @@ } } ], - "ANT/USDT:USDT": [ - { - "tier": 1.0, - "currency": "USDT", - "minNotional": 0.0, - "maxNotional": 5000.0, - "maintenanceMarginRate": 0.015, - "maxLeverage": 21.0, - "info": { - "bracket": "1", - "initialLeverage": "21", - "notionalCap": "5000", - "notionalFloor": "0", - "maintMarginRatio": "0.015", - "cum": "0.0" - } - }, - { - "tier": 2.0, - "currency": "USDT", - "minNotional": 5000.0, - "maxNotional": 25000.0, - "maintenanceMarginRate": 0.025, - "maxLeverage": 20.0, - "info": { - "bracket": "2", - "initialLeverage": "20", - "notionalCap": "25000", - "notionalFloor": "5000", - "maintMarginRatio": "0.025", - "cum": "50.0" - } - }, - { - "tier": 3.0, - "currency": "USDT", - "minNotional": 25000.0, - "maxNotional": 100000.0, - "maintenanceMarginRate": 0.05, - "maxLeverage": 10.0, - "info": { - "bracket": "3", - "initialLeverage": "10", - "notionalCap": "100000", - "notionalFloor": "25000", - "maintMarginRatio": "0.05", - "cum": "675.0" - } - }, - { - "tier": 4.0, - "currency": "USDT", - "minNotional": 100000.0, - "maxNotional": 250000.0, - "maintenanceMarginRate": 0.1, - "maxLeverage": 5.0, - "info": { - "bracket": "4", - "initialLeverage": "5", - "notionalCap": "250000", - "notionalFloor": "100000", - "maintMarginRatio": "0.1", - "cum": "5675.0" - } - }, - { - "tier": 5.0, - "currency": "USDT", - "minNotional": 250000.0, - "maxNotional": 1000000.0, - "maintenanceMarginRate": 0.125, - "maxLeverage": 2.0, - "info": { - "bracket": "5", - "initialLeverage": "2", - "notionalCap": "1000000", - "notionalFloor": "250000", - "maintMarginRatio": "0.125", - "cum": "11925.0" - } - }, - { - "tier": 6.0, - "currency": "USDT", - "minNotional": 1000000.0, - "maxNotional": 1500000.0, - "maintenanceMarginRate": 0.5, - "maxLeverage": 1.0, - "info": { - "bracket": "6", - "initialLeverage": "1", - "notionalCap": "1500000", - "notionalFloor": "1000000", - "maintMarginRatio": "0.5", - "cum": "386925.0" - } - } - ], "APE/USDT:USDT": [ { "tier": 1.0, @@ -4831,104 +4733,6 @@ } } ], - "AUDIO/USDT:USDT": [ - { - "tier": 1.0, - "currency": "USDT", - "minNotional": 0.0, - "maxNotional": 5000.0, - "maintenanceMarginRate": 0.02, - "maxLeverage": 20.0, - "info": { - "bracket": "1", - "initialLeverage": "20", - "notionalCap": "5000", - "notionalFloor": "0", - "maintMarginRatio": "0.02", - "cum": "0.0" - } - }, - { - "tier": 2.0, - "currency": "USDT", - "minNotional": 5000.0, - "maxNotional": 25000.0, - "maintenanceMarginRate": 0.025, - "maxLeverage": 10.0, - "info": { - "bracket": "2", - "initialLeverage": "10", - "notionalCap": "25000", - "notionalFloor": "5000", - "maintMarginRatio": "0.025", - "cum": "25.0" - } - }, - { - "tier": 3.0, - "currency": "USDT", - "minNotional": 25000.0, - "maxNotional": 100000.0, - "maintenanceMarginRate": 0.05, - "maxLeverage": 8.0, - "info": { - "bracket": "3", - "initialLeverage": "8", - "notionalCap": "100000", - "notionalFloor": "25000", - "maintMarginRatio": "0.05", - "cum": "650.0" - } - }, - { - "tier": 4.0, - "currency": "USDT", - "minNotional": 100000.0, - "maxNotional": 250000.0, - "maintenanceMarginRate": 0.1, - "maxLeverage": 5.0, - "info": { - "bracket": "4", - "initialLeverage": "5", - "notionalCap": "250000", - "notionalFloor": "100000", - "maintMarginRatio": "0.1", - "cum": "5650.0" - } - }, - { - "tier": 5.0, - "currency": "USDT", - "minNotional": 250000.0, - "maxNotional": 1000000.0, - "maintenanceMarginRate": 0.125, - "maxLeverage": 2.0, - "info": { - "bracket": "5", - "initialLeverage": "2", - "notionalCap": "1000000", - "notionalFloor": "250000", - "maintMarginRatio": "0.125", - "cum": "11900.0" - } - }, - { - "tier": 6.0, - "currency": "USDT", - "minNotional": 1000000.0, - "maxNotional": 1500000.0, - "maintenanceMarginRate": 0.5, - "maxLeverage": 1.0, - "info": { - "bracket": "6", - "initialLeverage": "1", - "notionalCap": "1500000", - "notionalFloor": "1000000", - "maintMarginRatio": "0.5", - "cum": "386900.0" - } - } - ], "AVAX/USDC:USDC": [ { "tier": 1.0, @@ -6929,20 +6733,20 @@ } } ], - "BLUEBIRD/USDT:USDT": [ + "BLUR/USDT:USDT": [ { "tier": 1.0, "currency": "USDT", "minNotional": 0.0, "maxNotional": 5000.0, - "maintenanceMarginRate": 0.01, - "maxLeverage": 11.0, + "maintenanceMarginRate": 0.015, + "maxLeverage": 50.0, "info": { "bracket": "1", - "initialLeverage": "11", + "initialLeverage": "50", "notionalCap": "5000", "notionalFloor": "0", - "maintMarginRatio": "0.01", + "maintMarginRatio": "0.015", "cum": "0.0" } }, @@ -6951,15 +6755,15 @@ "currency": "USDT", "minNotional": 5000.0, "maxNotional": 25000.0, - "maintenanceMarginRate": 0.025, - "maxLeverage": 10.0, + "maintenanceMarginRate": 0.02, + "maxLeverage": 25.0, "info": { "bracket": "2", - "initialLeverage": "10", + "initialLeverage": "25", "notionalCap": "25000", "notionalFloor": "5000", - "maintMarginRatio": "0.025", - "cum": "75.0" + "maintMarginRatio": "0.02", + "cum": "25.0" } }, { @@ -6967,145 +6771,47 @@ "currency": "USDT", "minNotional": 25000.0, "maxNotional": 100000.0, - "maintenanceMarginRate": 0.05, - "maxLeverage": 8.0, + "maintenanceMarginRate": 0.025, + "maxLeverage": 20.0, "info": { "bracket": "3", - "initialLeverage": "8", + "initialLeverage": "20", "notionalCap": "100000", "notionalFloor": "25000", - "maintMarginRatio": "0.05", - "cum": "700.0" + "maintMarginRatio": "0.025", + "cum": "150.0" } }, { "tier": 4.0, "currency": "USDT", "minNotional": 100000.0, - "maxNotional": 250000.0, - "maintenanceMarginRate": 0.1, - "maxLeverage": 5.0, + "maxNotional": 600000.0, + "maintenanceMarginRate": 0.05, + "maxLeverage": 10.0, "info": { "bracket": "4", - "initialLeverage": "5", - "notionalCap": "250000", + "initialLeverage": "10", + "notionalCap": "600000", "notionalFloor": "100000", - "maintMarginRatio": "0.1", - "cum": "5700.0" + "maintMarginRatio": "0.05", + "cum": "2650.0" } }, { "tier": 5.0, "currency": "USDT", - "minNotional": 250000.0, - "maxNotional": 1000000.0, - "maintenanceMarginRate": 0.125, - "maxLeverage": 2.0, + "minNotional": 600000.0, + "maxNotional": 1600000.0, + "maintenanceMarginRate": 0.1, + "maxLeverage": 5.0, "info": { "bracket": "5", - "initialLeverage": "2", - "notionalCap": "1000000", - "notionalFloor": "250000", - "maintMarginRatio": "0.125", - "cum": "11950.0" - } - }, - { - "tier": 6.0, - "currency": "USDT", - "minNotional": 1000000.0, - "maxNotional": 1500000.0, - "maintenanceMarginRate": 0.5, - "maxLeverage": 1.0, - "info": { - "bracket": "6", - "initialLeverage": "1", - "notionalCap": "1500000", - "notionalFloor": "1000000", - "maintMarginRatio": "0.5", - "cum": "386950.0" - } - } - ], - "BLUR/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": 25000.0, - "maintenanceMarginRate": 0.02, - "maxLeverage": 25.0, - "info": { - "bracket": "2", - "initialLeverage": "25", - "notionalCap": "25000", - "notionalFloor": "5000", - "maintMarginRatio": "0.02", - "cum": "25.0" - } - }, - { - "tier": 3.0, - "currency": "USDT", - "minNotional": 25000.0, - "maxNotional": 100000.0, - "maintenanceMarginRate": 0.025, - "maxLeverage": 20.0, - "info": { - "bracket": "3", - "initialLeverage": "20", - "notionalCap": "100000", - "notionalFloor": "25000", - "maintMarginRatio": "0.025", - "cum": "150.0" - } - }, - { - "tier": 4.0, - "currency": "USDT", - "minNotional": 100000.0, - "maxNotional": 600000.0, - "maintenanceMarginRate": 0.05, - "maxLeverage": 10.0, - "info": { - "bracket": "4", - "initialLeverage": "10", - "notionalCap": "600000", - "notionalFloor": "100000", - "maintMarginRatio": "0.05", - "cum": "2650.0" - } - }, - { - "tier": 5.0, - "currency": "USDT", - "minNotional": 600000.0, - "maxNotional": 1600000.0, - "maintenanceMarginRate": 0.1, - "maxLeverage": 5.0, - "info": { - "bracket": "5", - "initialLeverage": "5", - "notionalCap": "1600000", - "notionalFloor": "600000", - "maintMarginRatio": "0.1", - "cum": "32650.0" + "initialLeverage": "5", + "notionalCap": "1600000", + "notionalFloor": "600000", + "maintMarginRatio": "0.1", + "cum": "32650.0" } }, { @@ -8462,13 +8168,13 @@ "tier": 2.0, "currency": "USDT", "minNotional": 50000.0, - "maxNotional": 500000.0, + "maxNotional": 600000.0, "maintenanceMarginRate": 0.005, "maxLeverage": 100.0, "info": { "bracket": "2", "initialLeverage": "100", - "notionalCap": "500000", + "notionalCap": "600000", "notionalFloor": "50000", "maintMarginRatio": "0.005", "cum": "50.0" @@ -8477,129 +8183,161 @@ { "tier": 3.0, "currency": "USDT", - "minNotional": 500000.0, - "maxNotional": 10000000.0, + "minNotional": 600000.0, + "maxNotional": 3000000.0, + "maintenanceMarginRate": 0.0065, + "maxLeverage": 75.0, + "info": { + "bracket": "3", + "initialLeverage": "75", + "notionalCap": "3000000", + "notionalFloor": "600000", + "maintMarginRatio": "0.0065", + "cum": "950.0" + } + }, + { + "tier": 4.0, + "currency": "USDT", + "minNotional": 3000000.0, + "maxNotional": 12000000.0, "maintenanceMarginRate": 0.01, "maxLeverage": 50.0, "info": { - "bracket": "3", + "bracket": "4", "initialLeverage": "50", - "notionalCap": "10000000", - "notionalFloor": "500000", + "notionalCap": "12000000", + "notionalFloor": "3000000", "maintMarginRatio": "0.01", - "cum": "2550.0" + "cum": "11450.0" } }, { - "tier": 4.0, + "tier": 5.0, "currency": "USDT", - "minNotional": 10000000.0, - "maxNotional": 80000000.0, + "minNotional": 12000000.0, + "maxNotional": 70000000.0, + "maintenanceMarginRate": 0.02, + "maxLeverage": 25.0, + "info": { + "bracket": "5", + "initialLeverage": "25", + "notionalCap": "70000000", + "notionalFloor": "12000000", + "maintMarginRatio": "0.02", + "cum": "131450.0" + } + }, + { + "tier": 6.0, + "currency": "USDT", + "minNotional": 70000000.0, + "maxNotional": 100000000.0, "maintenanceMarginRate": 0.025, "maxLeverage": 20.0, "info": { - "bracket": "4", + "bracket": "6", "initialLeverage": "20", - "notionalCap": "80000000", - "notionalFloor": "10000000", + "notionalCap": "100000000", + "notionalFloor": "70000000", "maintMarginRatio": "0.025", - "cum": "152550.0" + "cum": "481450.0" } }, { - "tier": 5.0, + "tier": 7.0, "currency": "USDT", - "minNotional": 80000000.0, - "maxNotional": 150000000.0, + "minNotional": 100000000.0, + "maxNotional": 230000000.0, "maintenanceMarginRate": 0.05, "maxLeverage": 10.0, "info": { - "bracket": "5", + "bracket": "7", "initialLeverage": "10", - "notionalCap": "150000000", - "notionalFloor": "80000000", + "notionalCap": "230000000", + "notionalFloor": "100000000", "maintMarginRatio": "0.05", - "cum": "2152550.0" + "cum": "2981450.0" } }, { - "tier": 6.0, + "tier": 8.0, "currency": "USDT", - "minNotional": 150000000.0, - "maxNotional": 300000000.0, + "minNotional": 230000000.0, + "maxNotional": 480000000.0, "maintenanceMarginRate": 0.1, "maxLeverage": 5.0, "info": { - "bracket": "6", + "bracket": "8", "initialLeverage": "5", - "notionalCap": "300000000", - "notionalFloor": "150000000", + "notionalCap": "480000000", + "notionalFloor": "230000000", "maintMarginRatio": "0.1", - "cum": "9652550.0" + "cum": "14481450.0" } }, { - "tier": 7.0, + "tier": 9.0, "currency": "USDT", - "minNotional": 300000000.0, - "maxNotional": 450000000.0, + "minNotional": 480000000.0, + "maxNotional": 600000000.0, "maintenanceMarginRate": 0.125, "maxLeverage": 4.0, "info": { - "bracket": "7", + "bracket": "9", "initialLeverage": "4", - "notionalCap": "450000000", - "notionalFloor": "300000000", + "notionalCap": "600000000", + "notionalFloor": "480000000", "maintMarginRatio": "0.125", - "cum": "17152550.0" + "cum": "26481450.0" } }, { - "tier": 8.0, + "tier": 10.0, "currency": "USDT", - "minNotional": 450000000.0, - "maxNotional": 600000000.0, + "minNotional": 600000000.0, + "maxNotional": 800000000.0, "maintenanceMarginRate": 0.15, "maxLeverage": 3.0, "info": { - "bracket": "8", + "bracket": "10", "initialLeverage": "3", - "notionalCap": "600000000", - "notionalFloor": "450000000", + "notionalCap": "800000000", + "notionalFloor": "600000000", "maintMarginRatio": "0.15", - "cum": "28402550.0" + "cum": "41481450.0" } }, { - "tier": 9.0, + "tier": 11.0, "currency": "USDT", - "minNotional": 600000000.0, - "maxNotional": 800000000.0, + "minNotional": 800000000.0, + "maxNotional": 1200000000.0, "maintenanceMarginRate": 0.25, "maxLeverage": 2.0, "info": { - "bracket": "9", + "bracket": "11", "initialLeverage": "2", - "notionalCap": "800000000", - "notionalFloor": "600000000", + "notionalCap": "1200000000", + "notionalFloor": "800000000", "maintMarginRatio": "0.25", - "cum": "88402550.0" + "cum": "121481450.0" } }, { - "tier": 10.0, + "tier": 12.0, "currency": "USDT", - "minNotional": 800000000.0, - "maxNotional": 1000000000.0, + "minNotional": 1200000000.0, + "maxNotional": 1800000000.0, "maintenanceMarginRate": 0.5, "maxLeverage": 1.0, "info": { - "bracket": "10", + "bracket": "12", "initialLeverage": "1", - "notionalCap": "1000000000", - "notionalFloor": "800000000", + "notionalCap": "1800000000", + "notionalFloor": "1200000000", "maintMarginRatio": "0.5", - "cum": "288402550.0" + "cum": "421481450.0" } } ], @@ -9059,104 +8797,6 @@ } } ], - "BTS/USDT:USDT": [ - { - "tier": 1.0, - "currency": "USDT", - "minNotional": 0.0, - "maxNotional": 5000.0, - "maintenanceMarginRate": 0.01, - "maxLeverage": 50.0, - "info": { - "bracket": "1", - "initialLeverage": "50", - "notionalCap": "5000", - "notionalFloor": "0", - "maintMarginRatio": "0.01", - "cum": "0.0" - } - }, - { - "tier": 2.0, - "currency": "USDT", - "minNotional": 5000.0, - "maxNotional": 25000.0, - "maintenanceMarginRate": 0.025, - "maxLeverage": 20.0, - "info": { - "bracket": "2", - "initialLeverage": "20", - "notionalCap": "25000", - "notionalFloor": "5000", - "maintMarginRatio": "0.025", - "cum": "75.0" - } - }, - { - "tier": 3.0, - "currency": "USDT", - "minNotional": 25000.0, - "maxNotional": 100000.0, - "maintenanceMarginRate": 0.05, - "maxLeverage": 10.0, - "info": { - "bracket": "3", - "initialLeverage": "10", - "notionalCap": "100000", - "notionalFloor": "25000", - "maintMarginRatio": "0.05", - "cum": "700.0" - } - }, - { - "tier": 4.0, - "currency": "USDT", - "minNotional": 100000.0, - "maxNotional": 250000.0, - "maintenanceMarginRate": 0.1, - "maxLeverage": 5.0, - "info": { - "bracket": "4", - "initialLeverage": "5", - "notionalCap": "250000", - "notionalFloor": "100000", - "maintMarginRatio": "0.1", - "cum": "5700.0" - } - }, - { - "tier": 5.0, - "currency": "USDT", - "minNotional": 250000.0, - "maxNotional": 1000000.0, - "maintenanceMarginRate": 0.125, - "maxLeverage": 2.0, - "info": { - "bracket": "5", - "initialLeverage": "2", - "notionalCap": "1000000", - "notionalFloor": "250000", - "maintMarginRatio": "0.125", - "cum": "11950.0" - } - }, - { - "tier": 6.0, - "currency": "USDT", - "minNotional": 1000000.0, - "maxNotional": 5000000.0, - "maintenanceMarginRate": 0.5, - "maxLeverage": 1.0, - "info": { - "bracket": "6", - "initialLeverage": "1", - "notionalCap": "5000000", - "notionalFloor": "1000000", - "maintMarginRatio": "0.5", - "cum": "386950.0" - } - } - ], "C98/USDT:USDT": [ { "tier": 1.0, @@ -9976,127 +9616,13 @@ "tier": 3.0, "currency": "USDT", "minNotional": 25000.0, - "maxNotional": 200000.0, - "maintenanceMarginRate": 0.05, - "maxLeverage": 10.0, - "info": { - "bracket": "3", - "initialLeverage": "10", - "notionalCap": "200000", - "notionalFloor": "25000", - "maintMarginRatio": "0.05", - "cum": "650.0" - } - }, - { - "tier": 4.0, - "currency": "USDT", - "minNotional": 200000.0, - "maxNotional": 500000.0, - "maintenanceMarginRate": 0.1, - "maxLeverage": 5.0, - "info": { - "bracket": "4", - "initialLeverage": "5", - "notionalCap": "500000", - "notionalFloor": "200000", - "maintMarginRatio": "0.1", - "cum": "10650.0" - } - }, - { - "tier": 5.0, - "currency": "USDT", - "minNotional": 500000.0, - "maxNotional": 1000000.0, - "maintenanceMarginRate": 0.125, - "maxLeverage": 4.0, - "info": { - "bracket": "5", - "initialLeverage": "4", - "notionalCap": "1000000", - "notionalFloor": "500000", - "maintMarginRatio": "0.125", - "cum": "23150.0" - } - }, - { - "tier": 6.0, - "currency": "USDT", - "minNotional": 1000000.0, - "maxNotional": 3000000.0, - "maintenanceMarginRate": 0.25, - "maxLeverage": 2.0, - "info": { - "bracket": "6", - "initialLeverage": "2", - "notionalCap": "3000000", - "notionalFloor": "1000000", - "maintMarginRatio": "0.25", - "cum": "148150.0" - } - }, - { - "tier": 7.0, - "currency": "USDT", - "minNotional": 3000000.0, - "maxNotional": 5000000.0, - "maintenanceMarginRate": 0.5, - "maxLeverage": 1.0, - "info": { - "bracket": "7", - "initialLeverage": "1", - "notionalCap": "5000000", - "notionalFloor": "3000000", - "maintMarginRatio": "0.5", - "cum": "898150.0" - } - } - ], - "COCOS/USDT:USDT": [ - { - "tier": 1.0, - "currency": "USDT", - "minNotional": 0.0, - "maxNotional": 5000.0, - "maintenanceMarginRate": 0.02, - "maxLeverage": 8.0, - "info": { - "bracket": "1", - "initialLeverage": "8", - "notionalCap": "5000", - "notionalFloor": "0", - "maintMarginRatio": "0.02", - "cum": "0.0" - } - }, - { - "tier": 2.0, - "currency": "USDT", - "minNotional": 5000.0, - "maxNotional": 25000.0, - "maintenanceMarginRate": 0.025, - "maxLeverage": 7.0, - "info": { - "bracket": "2", - "initialLeverage": "7", - "notionalCap": "25000", - "notionalFloor": "5000", - "maintMarginRatio": "0.025", - "cum": "25.0" - } - }, - { - "tier": 3.0, - "currency": "USDT", - "minNotional": 25000.0, - "maxNotional": 100000.0, + "maxNotional": 200000.0, "maintenanceMarginRate": 0.05, - "maxLeverage": 6.0, + "maxLeverage": 10.0, "info": { "bracket": "3", - "initialLeverage": "6", - "notionalCap": "100000", + "initialLeverage": "10", + "notionalCap": "200000", "notionalFloor": "25000", "maintMarginRatio": "0.05", "cum": "650.0" @@ -10105,49 +9631,65 @@ { "tier": 4.0, "currency": "USDT", - "minNotional": 100000.0, - "maxNotional": 250000.0, + "minNotional": 200000.0, + "maxNotional": 500000.0, "maintenanceMarginRate": 0.1, "maxLeverage": 5.0, "info": { "bracket": "4", "initialLeverage": "5", - "notionalCap": "250000", - "notionalFloor": "100000", + "notionalCap": "500000", + "notionalFloor": "200000", "maintMarginRatio": "0.1", - "cum": "5650.0" + "cum": "10650.0" } }, { "tier": 5.0, "currency": "USDT", - "minNotional": 250000.0, + "minNotional": 500000.0, "maxNotional": 1000000.0, "maintenanceMarginRate": 0.125, - "maxLeverage": 2.0, + "maxLeverage": 4.0, "info": { "bracket": "5", - "initialLeverage": "2", + "initialLeverage": "4", "notionalCap": "1000000", - "notionalFloor": "250000", + "notionalFloor": "500000", "maintMarginRatio": "0.125", - "cum": "11900.0" + "cum": "23150.0" } }, { "tier": 6.0, "currency": "USDT", "minNotional": 1000000.0, - "maxNotional": 1500000.0, + "maxNotional": 3000000.0, + "maintenanceMarginRate": 0.25, + "maxLeverage": 2.0, + "info": { + "bracket": "6", + "initialLeverage": "2", + "notionalCap": "3000000", + "notionalFloor": "1000000", + "maintMarginRatio": "0.25", + "cum": "148150.0" + } + }, + { + "tier": 7.0, + "currency": "USDT", + "minNotional": 3000000.0, + "maxNotional": 5000000.0, "maintenanceMarginRate": 0.5, "maxLeverage": 1.0, "info": { - "bracket": "6", + "bracket": "7", "initialLeverage": "1", - "notionalCap": "1500000", - "notionalFloor": "1000000", + "notionalCap": "5000000", + "notionalFloor": "3000000", "maintMarginRatio": "0.5", - "cum": "386900.0" + "cum": "898150.0" } } ], @@ -13962,13 +13504,13 @@ "tier": 2.0, "currency": "USDT", "minNotional": 50000.0, - "maxNotional": 500000.0, + "maxNotional": 600000.0, "maintenanceMarginRate": 0.005, "maxLeverage": 100.0, "info": { "bracket": "2", "initialLeverage": "100", - "notionalCap": "500000", + "notionalCap": "600000", "notionalFloor": "50000", "maintMarginRatio": "0.005", "cum": "50.0" @@ -13977,145 +13519,161 @@ { "tier": 3.0, "currency": "USDT", - "minNotional": 500000.0, - "maxNotional": 1000000.0, + "minNotional": 600000.0, + "maxNotional": 3000000.0, "maintenanceMarginRate": 0.0065, "maxLeverage": 75.0, "info": { "bracket": "3", "initialLeverage": "75", - "notionalCap": "1000000", - "notionalFloor": "500000", + "notionalCap": "3000000", + "notionalFloor": "600000", "maintMarginRatio": "0.0065", - "cum": "800.0" + "cum": "950.0" } }, { "tier": 4.0, "currency": "USDT", - "minNotional": 1000000.0, - "maxNotional": 5000000.0, + "minNotional": 3000000.0, + "maxNotional": 12000000.0, "maintenanceMarginRate": 0.01, "maxLeverage": 50.0, "info": { "bracket": "4", "initialLeverage": "50", - "notionalCap": "5000000", - "notionalFloor": "1000000", + "notionalCap": "12000000", + "notionalFloor": "3000000", "maintMarginRatio": "0.01", - "cum": "4300.0" + "cum": "11450.0" } }, { "tier": 5.0, "currency": "USDT", - "minNotional": 5000000.0, + "minNotional": 12000000.0, "maxNotional": 50000000.0, "maintenanceMarginRate": 0.02, - "maxLeverage": 20.0, + "maxLeverage": 25.0, "info": { "bracket": "5", - "initialLeverage": "20", + "initialLeverage": "25", "notionalCap": "50000000", - "notionalFloor": "5000000", + "notionalFloor": "12000000", "maintMarginRatio": "0.02", - "cum": "54300.0" + "cum": "131450.0" } }, { "tier": 6.0, "currency": "USDT", "minNotional": 50000000.0, - "maxNotional": 100000000.0, - "maintenanceMarginRate": 0.05, - "maxLeverage": 10.0, + "maxNotional": 65000000.0, + "maintenanceMarginRate": 0.025, + "maxLeverage": 20.0, "info": { "bracket": "6", - "initialLeverage": "10", - "notionalCap": "100000000", + "initialLeverage": "20", + "notionalCap": "65000000", "notionalFloor": "50000000", - "maintMarginRatio": "0.05", - "cum": "1554300.0" + "maintMarginRatio": "0.025", + "cum": "381450.0" } }, { "tier": 7.0, "currency": "USDT", - "minNotional": 100000000.0, + "minNotional": 65000000.0, "maxNotional": 150000000.0, - "maintenanceMarginRate": 0.1, - "maxLeverage": 5.0, + "maintenanceMarginRate": 0.05, + "maxLeverage": 10.0, "info": { "bracket": "7", - "initialLeverage": "5", + "initialLeverage": "10", "notionalCap": "150000000", - "notionalFloor": "100000000", - "maintMarginRatio": "0.1", - "cum": "6554300.0" + "notionalFloor": "65000000", + "maintMarginRatio": "0.05", + "cum": "2006450.0" } }, { "tier": 8.0, "currency": "USDT", "minNotional": 150000000.0, - "maxNotional": 300000000.0, - "maintenanceMarginRate": 0.125, - "maxLeverage": 4.0, + "maxNotional": 320000000.0, + "maintenanceMarginRate": 0.1, + "maxLeverage": 5.0, "info": { "bracket": "8", - "initialLeverage": "4", - "notionalCap": "300000000", + "initialLeverage": "5", + "notionalCap": "320000000", "notionalFloor": "150000000", - "maintMarginRatio": "0.125", - "cum": "10304300.0" + "maintMarginRatio": "0.1", + "cum": "9506450.0" } }, { "tier": 9.0, "currency": "USDT", - "minNotional": 300000000.0, + "minNotional": 320000000.0, "maxNotional": 400000000.0, - "maintenanceMarginRate": 0.15, - "maxLeverage": 3.0, + "maintenanceMarginRate": 0.125, + "maxLeverage": 4.0, "info": { "bracket": "9", - "initialLeverage": "3", + "initialLeverage": "4", "notionalCap": "400000000", - "notionalFloor": "300000000", - "maintMarginRatio": "0.15", - "cum": "17804300.0" + "notionalFloor": "320000000", + "maintMarginRatio": "0.125", + "cum": "17506450.0" } }, { "tier": 10.0, "currency": "USDT", "minNotional": 400000000.0, - "maxNotional": 500000000.0, - "maintenanceMarginRate": 0.25, - "maxLeverage": 2.0, + "maxNotional": 530000000.0, + "maintenanceMarginRate": 0.15, + "maxLeverage": 3.0, "info": { "bracket": "10", - "initialLeverage": "2", - "notionalCap": "500000000", + "initialLeverage": "3", + "notionalCap": "530000000", "notionalFloor": "400000000", - "maintMarginRatio": "0.25", - "cum": "57804300.0" + "maintMarginRatio": "0.15", + "cum": "27506450.0" } }, { "tier": 11.0, "currency": "USDT", - "minNotional": 500000000.0, + "minNotional": 530000000.0, "maxNotional": 800000000.0, + "maintenanceMarginRate": 0.25, + "maxLeverage": 2.0, + "info": { + "bracket": "11", + "initialLeverage": "2", + "notionalCap": "800000000", + "notionalFloor": "530000000", + "maintMarginRatio": "0.25", + "cum": "80506450.0" + } + }, + { + "tier": 12.0, + "currency": "USDT", + "minNotional": 800000000.0, + "maxNotional": 1200000000.0, "maintenanceMarginRate": 0.5, "maxLeverage": 1.0, "info": { - "bracket": "11", + "bracket": "12", "initialLeverage": "1", - "notionalCap": "800000000", - "notionalFloor": "500000000", + "notionalCap": "1200000000", + "notionalFloor": "800000000", "maintMarginRatio": "0.5", - "cum": "182804300.0" + "cum": "280506450.0" } } ], @@ -15260,131 +14818,33 @@ "tier": 6.0, "currency": "USDT", "minNotional": 1000000.0, - "maxNotional": 3000000.0, - "maintenanceMarginRate": 0.5, - "maxLeverage": 1.0, - "info": { - "bracket": "6", - "initialLeverage": "1", - "notionalCap": "3000000", - "notionalFloor": "1000000", - "maintMarginRatio": "0.5", - "cum": "386925.0" - } - } - ], - "FLOW/USDT:USDT": [ - { - "tier": 1.0, - "currency": "USDT", - "minNotional": 0.0, - "maxNotional": 5000.0, - "maintenanceMarginRate": 0.01, - "maxLeverage": 20.0, - "info": { - "bracket": "1", - "initialLeverage": "20", - "notionalCap": "5000", - "notionalFloor": "0", - "maintMarginRatio": "0.01", - "cum": "0.0" - } - }, - { - "tier": 2.0, - "currency": "USDT", - "minNotional": 5000.0, - "maxNotional": 25000.0, - "maintenanceMarginRate": 0.025, - "maxLeverage": 10.0, - "info": { - "bracket": "2", - "initialLeverage": "10", - "notionalCap": "25000", - "notionalFloor": "5000", - "maintMarginRatio": "0.025", - "cum": "75.0" - } - }, - { - "tier": 3.0, - "currency": "USDT", - "minNotional": 25000.0, - "maxNotional": 100000.0, - "maintenanceMarginRate": 0.05, - "maxLeverage": 8.0, - "info": { - "bracket": "3", - "initialLeverage": "8", - "notionalCap": "100000", - "notionalFloor": "25000", - "maintMarginRatio": "0.05", - "cum": "700.0" - } - }, - { - "tier": 4.0, - "currency": "USDT", - "minNotional": 100000.0, - "maxNotional": 250000.0, - "maintenanceMarginRate": 0.1, - "maxLeverage": 5.0, - "info": { - "bracket": "4", - "initialLeverage": "5", - "notionalCap": "250000", - "notionalFloor": "100000", - "maintMarginRatio": "0.1", - "cum": "5700.0" - } - }, - { - "tier": 5.0, - "currency": "USDT", - "minNotional": 250000.0, - "maxNotional": 1000000.0, - "maintenanceMarginRate": 0.125, - "maxLeverage": 2.0, - "info": { - "bracket": "5", - "initialLeverage": "2", - "notionalCap": "1000000", - "notionalFloor": "250000", - "maintMarginRatio": "0.125", - "cum": "11950.0" - } - }, - { - "tier": 6.0, - "currency": "USDT", - "minNotional": 1000000.0, - "maxNotional": 5000000.0, + "maxNotional": 3000000.0, "maintenanceMarginRate": 0.5, "maxLeverage": 1.0, "info": { "bracket": "6", "initialLeverage": "1", - "notionalCap": "5000000", + "notionalCap": "3000000", "notionalFloor": "1000000", "maintMarginRatio": "0.5", - "cum": "386950.0" + "cum": "386925.0" } } ], - "FOOTBALL/USDT:USDT": [ + "FLOW/USDT:USDT": [ { "tier": 1.0, "currency": "USDT", "minNotional": 0.0, "maxNotional": 5000.0, - "maintenanceMarginRate": 0.02, - "maxLeverage": 11.0, + "maintenanceMarginRate": 0.01, + "maxLeverage": 20.0, "info": { "bracket": "1", - "initialLeverage": "11", + "initialLeverage": "20", "notionalCap": "5000", "notionalFloor": "0", - "maintMarginRatio": "0.02", + "maintMarginRatio": "0.01", "cum": "0.0" } }, @@ -15401,7 +14861,7 @@ "notionalCap": "25000", "notionalFloor": "5000", "maintMarginRatio": "0.025", - "cum": "25.0" + "cum": "75.0" } }, { @@ -15417,7 +14877,7 @@ "notionalCap": "100000", "notionalFloor": "25000", "maintMarginRatio": "0.05", - "cum": "650.0" + "cum": "700.0" } }, { @@ -15433,7 +14893,7 @@ "notionalCap": "250000", "notionalFloor": "100000", "maintMarginRatio": "0.1", - "cum": "5650.0" + "cum": "5700.0" } }, { @@ -15449,23 +14909,23 @@ "notionalCap": "1000000", "notionalFloor": "250000", "maintMarginRatio": "0.125", - "cum": "11900.0" + "cum": "11950.0" } }, { "tier": 6.0, "currency": "USDT", "minNotional": 1000000.0, - "maxNotional": 1500000.0, + "maxNotional": 5000000.0, "maintenanceMarginRate": 0.5, "maxLeverage": 1.0, "info": { "bracket": "6", "initialLeverage": "1", - "notionalCap": "1500000", + "notionalCap": "5000000", "notionalFloor": "1000000", "maintMarginRatio": "0.5", - "cum": "386900.0" + "cum": "386950.0" } } ], @@ -17359,120 +16819,6 @@ } } ], - "HNT/USDT:USDT": [ - { - "tier": 1.0, - "currency": "USDT", - "minNotional": 0.0, - "maxNotional": 5000.0, - "maintenanceMarginRate": 0.02, - "maxLeverage": 10.0, - "info": { - "bracket": "1", - "initialLeverage": "10", - "notionalCap": "5000", - "notionalFloor": "0", - "maintMarginRatio": "0.02", - "cum": "0.0" - } - }, - { - "tier": 2.0, - "currency": "USDT", - "minNotional": 5000.0, - "maxNotional": 25000.0, - "maintenanceMarginRate": 0.025, - "maxLeverage": 8.0, - "info": { - "bracket": "2", - "initialLeverage": "8", - "notionalCap": "25000", - "notionalFloor": "5000", - "maintMarginRatio": "0.025", - "cum": "25.0" - } - }, - { - "tier": 3.0, - "currency": "USDT", - "minNotional": 25000.0, - "maxNotional": 300000.0, - "maintenanceMarginRate": 0.05, - "maxLeverage": 6.0, - "info": { - "bracket": "3", - "initialLeverage": "6", - "notionalCap": "300000", - "notionalFloor": "25000", - "maintMarginRatio": "0.05", - "cum": "650.0" - } - }, - { - "tier": 4.0, - "currency": "USDT", - "minNotional": 300000.0, - "maxNotional": 800000.0, - "maintenanceMarginRate": 0.1, - "maxLeverage": 5.0, - "info": { - "bracket": "4", - "initialLeverage": "5", - "notionalCap": "800000", - "notionalFloor": "300000", - "maintMarginRatio": "0.1", - "cum": "15650.0" - } - }, - { - "tier": 5.0, - "currency": "USDT", - "minNotional": 800000.0, - "maxNotional": 1000000.0, - "maintenanceMarginRate": 0.125, - "maxLeverage": 4.0, - "info": { - "bracket": "5", - "initialLeverage": "4", - "notionalCap": "1000000", - "notionalFloor": "800000", - "maintMarginRatio": "0.125", - "cum": "35650.0" - } - }, - { - "tier": 6.0, - "currency": "USDT", - "minNotional": 1000000.0, - "maxNotional": 1500000.0, - "maintenanceMarginRate": 0.25, - "maxLeverage": 2.0, - "info": { - "bracket": "6", - "initialLeverage": "2", - "notionalCap": "1500000", - "notionalFloor": "1000000", - "maintMarginRatio": "0.25", - "cum": "160650.0" - } - }, - { - "tier": 7.0, - "currency": "USDT", - "minNotional": 1500000.0, - "maxNotional": 2000000.0, - "maintenanceMarginRate": 0.5, - "maxLeverage": 1.0, - "info": { - "bracket": "7", - "initialLeverage": "1", - "notionalCap": "2000000", - "notionalFloor": "1500000", - "maintMarginRatio": "0.5", - "cum": "535650.0" - } - } - ], "HOOK/USDT:USDT": [ { "tier": 1.0, @@ -22399,156 +21745,42 @@ "bracket": "8", "initialLeverage": "2", "notionalCap": "18000000", - "notionalFloor": "6000000", - "maintMarginRatio": "0.25", - "cum": "991585.0" - } - }, - { - "tier": 9.0, - "currency": "USDT", - "minNotional": 18000000.0, - "maxNotional": 30000000.0, - "maintenanceMarginRate": 0.5, - "maxLeverage": 1.0, - "info": { - "bracket": "9", - "initialLeverage": "1", - "notionalCap": "30000000", - "notionalFloor": "18000000", - "maintMarginRatio": "0.5", - "cum": "5491585.0" - } - } - ], - "MAV/USDT:USDT": [ - { - "tier": 1.0, - "currency": "USDT", - "minNotional": 0.0, - "maxNotional": 5000.0, - "maintenanceMarginRate": 0.02, - "maxLeverage": 20.0, - "info": { - "bracket": "1", - "initialLeverage": "20", - "notionalCap": "5000", - "notionalFloor": "0", - "maintMarginRatio": "0.02", - "cum": "0.0" - } - }, - { - "tier": 2.0, - "currency": "USDT", - "minNotional": 5000.0, - "maxNotional": 25000.0, - "maintenanceMarginRate": 0.025, - "maxLeverage": 15.0, - "info": { - "bracket": "2", - "initialLeverage": "15", - "notionalCap": "25000", - "notionalFloor": "5000", - "maintMarginRatio": "0.025", - "cum": "25.0" - } - }, - { - "tier": 3.0, - "currency": "USDT", - "minNotional": 25000.0, - "maxNotional": 200000.0, - "maintenanceMarginRate": 0.05, - "maxLeverage": 10.0, - "info": { - "bracket": "3", - "initialLeverage": "10", - "notionalCap": "200000", - "notionalFloor": "25000", - "maintMarginRatio": "0.05", - "cum": "650.0" - } - }, - { - "tier": 4.0, - "currency": "USDT", - "minNotional": 200000.0, - "maxNotional": 500000.0, - "maintenanceMarginRate": 0.1, - "maxLeverage": 5.0, - "info": { - "bracket": "4", - "initialLeverage": "5", - "notionalCap": "500000", - "notionalFloor": "200000", - "maintMarginRatio": "0.1", - "cum": "10650.0" - } - }, - { - "tier": 5.0, - "currency": "USDT", - "minNotional": 500000.0, - "maxNotional": 1000000.0, - "maintenanceMarginRate": 0.125, - "maxLeverage": 4.0, - "info": { - "bracket": "5", - "initialLeverage": "4", - "notionalCap": "1000000", - "notionalFloor": "500000", - "maintMarginRatio": "0.125", - "cum": "23150.0" - } - }, - { - "tier": 6.0, - "currency": "USDT", - "minNotional": 1000000.0, - "maxNotional": 3000000.0, - "maintenanceMarginRate": 0.25, - "maxLeverage": 2.0, - "info": { - "bracket": "6", - "initialLeverage": "2", - "notionalCap": "3000000", - "notionalFloor": "1000000", + "notionalFloor": "6000000", "maintMarginRatio": "0.25", - "cum": "148150.0" + "cum": "991585.0" } }, { - "tier": 7.0, + "tier": 9.0, "currency": "USDT", - "minNotional": 3000000.0, - "maxNotional": 5000000.0, + "minNotional": 18000000.0, + "maxNotional": 30000000.0, "maintenanceMarginRate": 0.5, "maxLeverage": 1.0, "info": { - "bracket": "7", + "bracket": "9", "initialLeverage": "1", - "notionalCap": "5000000", - "notionalFloor": "3000000", + "notionalCap": "30000000", + "notionalFloor": "18000000", "maintMarginRatio": "0.5", - "cum": "898150.0" + "cum": "5491585.0" } } ], - "MAVIA/USDT:USDT": [ + "MAV/USDT:USDT": [ { "tier": 1.0, "currency": "USDT", "minNotional": 0.0, "maxNotional": 5000.0, - "maintenanceMarginRate": 0.015, - "maxLeverage": 50.0, + "maintenanceMarginRate": 0.02, + "maxLeverage": 20.0, "info": { "bracket": "1", - "initialLeverage": "50", + "initialLeverage": "20", "notionalCap": "5000", "notionalFloor": "0", - "maintMarginRatio": "0.015", + "maintMarginRatio": "0.02", "cum": "0.0" } }, @@ -22558,108 +21790,108 @@ "minNotional": 5000.0, "maxNotional": 25000.0, "maintenanceMarginRate": 0.025, - "maxLeverage": 20.0, + "maxLeverage": 15.0, "info": { "bracket": "2", - "initialLeverage": "20", + "initialLeverage": "15", "notionalCap": "25000", "notionalFloor": "5000", "maintMarginRatio": "0.025", - "cum": "50.0" + "cum": "25.0" } }, { "tier": 3.0, "currency": "USDT", "minNotional": 25000.0, - "maxNotional": 100000.0, + "maxNotional": 200000.0, "maintenanceMarginRate": 0.05, "maxLeverage": 10.0, "info": { "bracket": "3", "initialLeverage": "10", - "notionalCap": "100000", + "notionalCap": "200000", "notionalFloor": "25000", "maintMarginRatio": "0.05", - "cum": "675.0" + "cum": "650.0" } }, { "tier": 4.0, "currency": "USDT", - "minNotional": 100000.0, - "maxNotional": 200000.0, + "minNotional": 200000.0, + "maxNotional": 500000.0, "maintenanceMarginRate": 0.1, "maxLeverage": 5.0, "info": { "bracket": "4", "initialLeverage": "5", - "notionalCap": "200000", - "notionalFloor": "100000", + "notionalCap": "500000", + "notionalFloor": "200000", "maintMarginRatio": "0.1", - "cum": "5675.0" + "cum": "10650.0" } }, { "tier": 5.0, "currency": "USDT", - "minNotional": 200000.0, - "maxNotional": 500000.0, + "minNotional": 500000.0, + "maxNotional": 1000000.0, "maintenanceMarginRate": 0.125, "maxLeverage": 4.0, "info": { "bracket": "5", "initialLeverage": "4", - "notionalCap": "500000", - "notionalFloor": "200000", + "notionalCap": "1000000", + "notionalFloor": "500000", "maintMarginRatio": "0.125", - "cum": "10675.0" + "cum": "23150.0" } }, { "tier": 6.0, "currency": "USDT", - "minNotional": 500000.0, - "maxNotional": 1000000.0, + "minNotional": 1000000.0, + "maxNotional": 3000000.0, "maintenanceMarginRate": 0.25, "maxLeverage": 2.0, "info": { "bracket": "6", "initialLeverage": "2", - "notionalCap": "1000000", - "notionalFloor": "500000", + "notionalCap": "3000000", + "notionalFloor": "1000000", "maintMarginRatio": "0.25", - "cum": "73175.0" + "cum": "148150.0" } }, { "tier": 7.0, "currency": "USDT", - "minNotional": 1000000.0, - "maxNotional": 2000000.0, + "minNotional": 3000000.0, + "maxNotional": 5000000.0, "maintenanceMarginRate": 0.5, "maxLeverage": 1.0, "info": { "bracket": "7", "initialLeverage": "1", - "notionalCap": "2000000", - "notionalFloor": "1000000", + "notionalCap": "5000000", + "notionalFloor": "3000000", "maintMarginRatio": "0.5", - "cum": "323175.0" + "cum": "898150.0" } } ], - "MBL/USDT:USDT": [ + "MAVIA/USDT:USDT": [ { "tier": 1.0, "currency": "USDT", "minNotional": 0.0, "maxNotional": 5000.0, "maintenanceMarginRate": 0.015, - "maxLeverage": 21.0, + "maxLeverage": 50.0, "info": { "bracket": "1", - "initialLeverage": "21", + "initialLeverage": "50", "notionalCap": "5000", "notionalFloor": "0", "maintMarginRatio": "0.015", @@ -30835,88 +30067,6 @@ } } ], - "SRM/USDT:USDT": [ - { - "tier": 1.0, - "currency": "USDT", - "minNotional": 0.0, - "maxNotional": 15000.0, - "maintenanceMarginRate": 0.025, - "maxLeverage": 8.0, - "info": { - "bracket": "1", - "initialLeverage": "8", - "notionalCap": "15000", - "notionalFloor": "0", - "maintMarginRatio": "0.025", - "cum": "0.0" - } - }, - { - "tier": 2.0, - "currency": "USDT", - "minNotional": 15000.0, - "maxNotional": 50000.0, - "maintenanceMarginRate": 0.05, - "maxLeverage": 6.0, - "info": { - "bracket": "2", - "initialLeverage": "6", - "notionalCap": "50000", - "notionalFloor": "15000", - "maintMarginRatio": "0.05", - "cum": "375.0" - } - }, - { - "tier": 3.0, - "currency": "USDT", - "minNotional": 50000.0, - "maxNotional": 200000.0, - "maintenanceMarginRate": 0.1, - "maxLeverage": 5.0, - "info": { - "bracket": "3", - "initialLeverage": "5", - "notionalCap": "200000", - "notionalFloor": "50000", - "maintMarginRatio": "0.1", - "cum": "2875.0" - } - }, - { - "tier": 4.0, - "currency": "USDT", - "minNotional": 200000.0, - "maxNotional": 1000000.0, - "maintenanceMarginRate": 0.125, - "maxLeverage": 2.0, - "info": { - "bracket": "4", - "initialLeverage": "2", - "notionalCap": "1000000", - "notionalFloor": "200000", - "maintMarginRatio": "0.125", - "cum": "7875.0" - } - }, - { - "tier": 5.0, - "currency": "USDT", - "minNotional": 1000000.0, - "maxNotional": 1500000.0, - "maintenanceMarginRate": 0.5, - "maxLeverage": 1.0, - "info": { - "bracket": "5", - "initialLeverage": "1", - "notionalCap": "1500000", - "notionalFloor": "1000000", - "maintMarginRatio": "0.5", - "cum": "382875.0" - } - } - ], "SSV/USDT:USDT": [ { "tier": 1.0, @@ -33489,104 +32639,6 @@ } } ], - "TOMO/USDT:USDT": [ - { - "tier": 1.0, - "currency": "USDT", - "minNotional": 0.0, - "maxNotional": 50000.0, - "maintenanceMarginRate": 0.025, - "maxLeverage": 8.0, - "info": { - "bracket": "1", - "initialLeverage": "8", - "notionalCap": "50000", - "notionalFloor": "0", - "maintMarginRatio": "0.025", - "cum": "0.0" - } - }, - { - "tier": 2.0, - "currency": "USDT", - "minNotional": 50000.0, - "maxNotional": 600000.0, - "maintenanceMarginRate": 0.05, - "maxLeverage": 6.0, - "info": { - "bracket": "2", - "initialLeverage": "6", - "notionalCap": "600000", - "notionalFloor": "50000", - "maintMarginRatio": "0.05", - "cum": "1250.0" - } - }, - { - "tier": 3.0, - "currency": "USDT", - "minNotional": 600000.0, - "maxNotional": 1280000.0, - "maintenanceMarginRate": 0.1, - "maxLeverage": 5.0, - "info": { - "bracket": "3", - "initialLeverage": "5", - "notionalCap": "1280000", - "notionalFloor": "600000", - "maintMarginRatio": "0.1", - "cum": "31250.0" - } - }, - { - "tier": 4.0, - "currency": "USDT", - "minNotional": 1280000.0, - "maxNotional": 1600000.0, - "maintenanceMarginRate": 0.125, - "maxLeverage": 4.0, - "info": { - "bracket": "4", - "initialLeverage": "4", - "notionalCap": "1600000", - "notionalFloor": "1280000", - "maintMarginRatio": "0.125", - "cum": "63250.0" - } - }, - { - "tier": 5.0, - "currency": "USDT", - "minNotional": 1600000.0, - "maxNotional": 4800000.0, - "maintenanceMarginRate": 0.25, - "maxLeverage": 2.0, - "info": { - "bracket": "5", - "initialLeverage": "2", - "notionalCap": "4800000", - "notionalFloor": "1600000", - "maintMarginRatio": "0.25", - "cum": "263250.0" - } - }, - { - "tier": 6.0, - "currency": "USDT", - "minNotional": 4800000.0, - "maxNotional": 5000000.0, - "maintenanceMarginRate": 0.5, - "maxLeverage": 1.0, - "info": { - "bracket": "6", - "initialLeverage": "1", - "notionalCap": "5000000", - "notionalFloor": "4800000", - "maintMarginRatio": "0.5", - "cum": "1463250.0" - } - } - ], "TON/USDT:USDT": [ { "tier": 1.0, diff --git a/freqtrade/exchange/bingx.py b/freqtrade/exchange/bingx.py index 4efd621e830..2d81643a1b3 100644 --- a/freqtrade/exchange/bingx.py +++ b/freqtrade/exchange/bingx.py @@ -17,6 +17,7 @@ class Bingx(Exchange): _ft_has: Dict = { "ohlcv_candle_limit": 1000, - "stoploss_on_exchange": False, + "stoploss_on_exchange": True, "stoploss_order_types": {"limit": "limit", "market": "market"}, + "order_time_in_force": ["GTC", "IOC", "PO"], } diff --git a/freqtrade/exchange/common.py b/freqtrade/exchange/common.py index 5035d0dd8e1..99f8918364b 100644 --- a/freqtrade/exchange/common.py +++ b/freqtrade/exchange/common.py @@ -53,6 +53,7 @@ def _get_logging_mixin(): SUPPORTED_EXCHANGES = [ "binance", + "bingx", "bitmart", "gate", "htx", diff --git a/freqtrade/exchange/exchange.py b/freqtrade/exchange/exchange.py index 9d7c4eabbe6..5771682b525 100644 --- a/freqtrade/exchange/exchange.py +++ b/freqtrade/exchange/exchange.py @@ -151,7 +151,7 @@ def __init__( :return: None """ self._api: ccxt.Exchange - self._api_async: ccxt_async.Exchange = None + self._api_async: ccxt_async.Exchange self._markets: Dict = {} self._trading_fees: Dict[str, Any] = {} self._leverage_tiers: Dict[str, List[Dict]] = {} @@ -233,7 +233,7 @@ def __init__( self.required_candle_call_count = 1 if validate: # Initial markets load - self._load_markets() + self.reload_markets(True, load_leverage_tiers=False) self.validate_config(config) self._startup_candle_count: int = config.get("startup_candle_count", 0) self.required_candle_call_count = self.validate_required_startup_candles( @@ -354,7 +354,7 @@ def markets(self) -> Dict[str, Any]: """exchange ccxt markets""" if not self._markets: logger.info("Markets were not loaded. Loading them now..") - self._load_markets() + self.reload_markets(True) return self._markets @property @@ -530,30 +530,26 @@ def amount_to_contract_precision(self, pair: str, amount: float) -> float: amount, self.get_precision_amount(pair), self.precisionMode, contract_size ) - def _load_async_markets(self, reload: bool = False) -> None: + def _load_async_markets(self, reload: bool = False) -> Dict[str, Any]: try: - if self._api_async: - self.loop.run_until_complete(self._api_async.load_markets(reload=reload, params={})) - - except (asyncio.TimeoutError, ccxt.BaseError) as e: - logger.warning("Could not load async markets. Reason: %s", e) - return + markets = self.loop.run_until_complete( + self._api_async.load_markets(reload=reload, params={}) + ) - def _load_markets(self) -> None: - """Initialize markets both sync and async""" - try: - self._markets = self._api.load_markets(params={}) - self._load_async_markets() - self._last_markets_refresh = dt_ts() - if self._ft_has["needs_trading_fees"]: - self._trading_fees = self.fetch_trading_fees() + if isinstance(markets, Exception): + raise markets + return markets + except asyncio.TimeoutError as e: + logger.warning("Could not load markets. Reason: %s", e) + raise TemporaryError from e - except ccxt.BaseError: - logger.exception("Unable to initialize markets.") + def reload_markets(self, force: bool = False, *, load_leverage_tiers: bool = True) -> None: + """ + Reload / Initialize markets both sync and async if refresh interval has passed - def reload_markets(self, force: bool = False) -> None: - """Reload markets both sync and async if refresh interval has passed""" + """ # Check whether markets have to be reloaded + is_initial = self._last_markets_refresh == 0 if ( not force and self._last_markets_refresh > 0 @@ -562,13 +558,18 @@ def reload_markets(self, force: bool = False) -> None: return None logger.debug("Performing scheduled market reload..") try: - self._markets = self._api.load_markets(reload=True, params={}) - # Also reload async markets to avoid issues with newly listed pairs - self._load_async_markets(reload=True) + # Reload async markets, then assign them to sync api + self._markets = self._load_async_markets(reload=True) + self._api.set_markets(self._api_async.markets, self._api_async.currencies) self._last_markets_refresh = dt_ts() - self.fill_leverage_tiers() - except ccxt.BaseError: - logger.exception("Could not reload markets.") + + if is_initial and self._ft_has["needs_trading_fees"]: + self._trading_fees = self.fetch_trading_fees() + + if load_leverage_tiers and self.trading_mode == TradingMode.FUTURES: + self.fill_leverage_tiers() + except (ccxt.BaseError, TemporaryError): + logger.exception("Could not load markets.") def validate_stakecurrency(self, stake_currency: str) -> None: """ diff --git a/freqtrade/exchange/htx.py b/freqtrade/exchange/htx.py index 58eb919bc88..f939534e915 100644 --- a/freqtrade/exchange/htx.py +++ b/freqtrade/exchange/htx.py @@ -24,6 +24,10 @@ class Htx(Exchange): "ohlcv_candle_limit": 1000, "l2_limit_range": [5, 10, 20], "l2_limit_range_required": False, + "ohlcv_candle_limit_per_timeframe": { + "1w": 500, + "1M": 500, + }, } def _get_stop_params(self, side: BuySell, ordertype: str, stop_price: float) -> Dict: diff --git a/freqtrade/freqai/data_kitchen.py b/freqtrade/freqai/data_kitchen.py index b17fffe0aa3..d43f569d8be 100644 --- a/freqtrade/freqai/data_kitchen.py +++ b/freqtrade/freqai/data_kitchen.py @@ -960,7 +960,7 @@ def remove_special_chars_from_feature_names(self, dataframe: pd.DataFrame) -> pd """ Remove all special characters from feature strings (:) :param dataframe: the dataframe that just finished indicator population. (unfiltered) - :return: dataframe with cleaned featrue names + :return: dataframe with cleaned feature names """ spec_chars = [":"] diff --git a/freqtrade/freqtradebot.py b/freqtrade/freqtradebot.py index 5981434e176..acec9932e5c 100644 --- a/freqtrade/freqtradebot.py +++ b/freqtrade/freqtradebot.py @@ -518,10 +518,11 @@ def handle_insufficient_funds(self, trade: Trade): except ExchangeError: logger.warning(f"Error updating {order.order_id}.") - def handle_onexchange_order(self, trade: Trade): + def handle_onexchange_order(self, trade: Trade) -> bool: """ Try refinding a order that is not in the database. Only used balance disappeared, which would make exiting impossible. + :return: True if the trade was deleted, False otherwise """ try: orders = self.exchange.fetch_orders( @@ -567,6 +568,19 @@ def handle_onexchange_order(self, trade: Trade): trade.exit_reason = prev_exit_reason total = self.wallets.get_total(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( + f"Trade only had fully canceled entry orders. " + f"Removing {trade} from database." + ) + + self._notify_enter_cancel( + trade, + order_type=self.strategy.order_types["entry"], + reason=constants.CANCEL_REASON["FULLY_CANCELLED"], + ) + trade.delete() + return True if total > trade.amount * 0.98: logger.warning( f"{trade} has a total of {trade.amount} {trade.base_currency}, " @@ -592,6 +606,7 @@ def handle_onexchange_order(self, trade: Trade): except Exception: # catching https://github.com/freqtrade/freqtrade/issues/9025 logger.warning("Error finding onexchange order", exc_info=True) + return False # # enter positions / open trades logic and methods @@ -1041,7 +1056,13 @@ def execute_entry( # Update fees if order is non-opened if order_status in constants.NON_OPEN_EXCHANGE_STATES: - self.update_trade_state(trade, order_id, order) + fully_canceled = self.update_trade_state(trade, order_id, order) + if fully_canceled and mode != "replace": + # Fully canceled orders, may happen with some time in force setups (IOC). + # Should be handled immediately. + self.handle_cancel_enter( + trade, order, order_obj, constants.CANCEL_REASON["TIMEOUT"] + ) return True @@ -1263,7 +1284,9 @@ def exit_positions(self, trades: List[Trade]) -> int: f"Not enough {trade.safe_base_currency} in wallet to exit {trade}. " "Trying to recover." ) - self.handle_onexchange_order(trade) + if self.handle_onexchange_order(trade): + # Trade was deleted. Don't continue. + continue try: try: diff --git a/freqtrade/persistence/trade_model.py b/freqtrade/persistence/trade_model.py index 45004abccf2..6e0ac167d29 100644 --- a/freqtrade/persistence/trade_model.py +++ b/freqtrade/persistence/trade_model.py @@ -959,7 +959,24 @@ def fee_updated(self, side: str) -> bool: def update_order(self, order: Dict) -> None: Order.update_orders(self.orders, order) - def get_canceled_exit_order_count(self) -> int: + @property + def fully_canceled_entry_order_count(self) -> int: + """ + Get amount of failed exiting orders + assumes full exits. + """ + return len( + [ + o + for o in self.orders + if o.ft_order_side == self.entry_side + and o.status in CANCELED_EXCHANGE_STATES + and o.filled == 0 + ] + ) + + @property + def canceled_exit_order_count(self) -> int: """ Get amount of failed exiting orders assumes full exits. @@ -972,6 +989,13 @@ def get_canceled_exit_order_count(self) -> int: ] ) + def get_canceled_exit_order_count(self) -> int: + """ + Get amount of failed exiting orders + assumes full exits. + """ + return self.canceled_exit_order_count + def _calc_open_trade_value(self, amount: float, open_rate: float) -> float: """ Calculate the open_rate including open_fee. diff --git a/ft_client/freqtrade_client/__init__.py b/ft_client/freqtrade_client/__init__.py index ac0b906b496..9ede4dd124a 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.5-dev" +__version__ = "2024.6-dev" if "dev" in __version__: from pathlib import Path diff --git a/ft_client/requirements.txt b/ft_client/requirements.txt index 36cd79635ac..35406c1d06d 100644 --- a/ft_client/requirements.txt +++ b/ft_client/requirements.txt @@ -1,3 +1,3 @@ # Requirements for freqtrade client library -requests==2.32.2 +requests==2.32.3 python-rapidjson==1.17 diff --git a/mkdocs.yml b/mkdocs.yml index ef8b7181b17..896dcd3f4c2 100644 --- a/mkdocs.yml +++ b/mkdocs.yml @@ -1,5 +1,6 @@ site_name: Freqtrade site_url: !ENV [READTHEDOCS_CANONICAL_URL, 'https://www.freqtrade.io/en/latest/'] +site_description: Freqtrade is a free and open source crypto trading bot written in Python, designed to support all major exchanges and be controlled via Telegram or builtin Web UI repo_url: https://github.com/freqtrade/freqtrade edit_uri: edit/develop/docs/ use_directory_urls: True diff --git a/pyproject.toml b/pyproject.toml index cfdbb3ece41..0c92225300e 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -82,6 +82,9 @@ skip_glob = ["**/.env*", "**/env/*", "**/.venv/*", "**/docs/*", "**/user_data/*" known_first_party = ["freqtrade_client"] [tool.pytest.ini_options] +log_format = "%(asctime)s %(levelname)s %(message)s" +log_date_format = "%Y-%m-%d %H:%M:%S" + asyncio_mode = "auto" addopts = "--dist loadscope" @@ -181,4 +184,4 @@ exclude = [ [tool.codespell] ignore-words-list = "coo,fo,strat,zar,selectin" -skip="*.svg,./user_data,./freqtrade/rpc/api_server/ui/installed" +skip="*.svg,./user_data,freqtrade/rpc/api_server/ui/installed,freqtrade/exchange/*.json" diff --git a/requirements-dev.txt b/requirements-dev.txt index 745773abb0d..b0ce069da42 100644 --- a/requirements-dev.txt +++ b/requirements-dev.txt @@ -7,7 +7,7 @@ -r docs/requirements-docs.txt coveralls==4.0.1 -ruff==0.4.4 +ruff==0.4.7 mypy==1.10.0 pre-commit==3.7.1 pytest==8.2.1 @@ -26,6 +26,6 @@ nbconvert==7.16.4 # mypy types types-cachetools==5.3.0.7 types-filelock==3.2.7 -types-requests==2.31.0.20240406 +types-requests==2.32.0.20240602 types-tabulate==0.9.0.20240106 types-python-dateutil==2.9.0.20240316 diff --git a/requirements-freqai.txt b/requirements-freqai.txt index f508e42c38f..cdc32e11bbc 100644 --- a/requirements-freqai.txt +++ b/requirements-freqai.txt @@ -3,7 +3,7 @@ -r requirements-plot.txt # Required for freqai -scikit-learn==1.4.2 +scikit-learn==1.5.0 joblib==1.4.2 catboost==1.2.5; 'arm' not in platform_machine lightgbm==4.3.0 diff --git a/requirements-hyperopt.txt b/requirements-hyperopt.txt index 54643d69752..99d4ee5c6a4 100644 --- a/requirements-hyperopt.txt +++ b/requirements-hyperopt.txt @@ -2,7 +2,7 @@ -r requirements.txt # Required for hyperopt -scipy==1.13.0 -scikit-learn==1.4.2 +scipy==1.13.1 +scikit-learn==1.5.0 ft-scikit-optimize==0.9.2 filelock==3.14.0 diff --git a/requirements.txt b/requirements.txt index 971d0601b76..c24a33d0da9 100644 --- a/requirements.txt +++ b/requirements.txt @@ -1,22 +1,24 @@ numpy==1.26.4 pandas==2.2.2 +bottleneck==1.3.8 +numexpr==2.10.0 pandas-ta==0.3.14b finta>=1.3 ta>=0.10.1 -ccxt==4.3.27 +ccxt==4.3.38 cryptography==42.0.7 aiohttp==3.9.5 SQLAlchemy==2.0.30 -python-telegram-bot==21.1.1 +python-telegram-bot==21.2 # can't be hard-pinned due to telegram-bot pinning httpx with ~ httpx>=0.24.1 humanize==4.9.0 cachetools==5.3.3 -requests==2.32.2 +requests==2.32.3 urllib3==2.2.1 jsonschema==4.22.0 -TA-Lib==0.4.28 +TA-Lib==0.4.30 technical==1.4.3 tabulate==0.9.0 pycoingecko==3.1.0 @@ -39,8 +41,8 @@ sdnotify==0.3.2 # API Server fastapi==0.111.0 -pydantic==2.7.1 -uvicorn==0.29.0 +pydantic==2.7.2 +uvicorn==0.30.1 pyjwt==2.8.0 aiofiles==23.2.1 psutil==5.9.8 @@ -55,7 +57,7 @@ python-dateutil==2.9.0.post0 pytz==2024.1 #Futures -schedule==1.2.1 +schedule==1.2.2 #WS Messages websockets==12.0 diff --git a/setup.ps1 b/setup.ps1 new file mode 100644 index 00000000000..8647bea945c --- /dev/null +++ b/setup.ps1 @@ -0,0 +1,285 @@ +Clear-Host + +$Timestamp = Get-Date -Format "yyyyMMdd_HHmmss" +$Global:LogFilePath = Join-Path $env:TEMP "script_log_$Timestamp.txt" + +$RequirementFiles = @("requirements.txt", "requirements-dev.txt", "requirements-hyperopt.txt", "requirements-freqai.txt", "requirements-freqai-rl.txt", "requirements-plot.txt") +$VenvName = ".venv" +$VenvDir = Join-Path $PSScriptRoot $VenvName + +function Write-Log { + param ( + [string]$Message, + [string]$Level = 'INFO' + ) + + if (-not (Test-Path -Path $LogFilePath)) { + New-Item -ItemType File -Path $LogFilePath -Force | Out-Null + } + + switch ($Level) { + 'INFO' { Write-Host $Message -ForegroundColor Green } + 'WARNING' { Write-Host $Message -ForegroundColor Yellow } + 'ERROR' { Write-Host $Message -ForegroundColor Red } + 'PROMPT' { Write-Host $Message -ForegroundColor Cyan } + } + + "${Level}: $Message" | Out-File $LogFilePath -Append +} + +function Get-UserSelection { + param ( + [string]$Prompt, + [string[]]$Options, + [string]$DefaultChoice = 'A', + [bool]$AllowMultipleSelections = $true + ) + + Write-Log "$Prompt`n" -Level 'PROMPT' + for ($I = 0; $I -lt $Options.Length; $I++) { + Write-Log "$([char](65 + $I)). $($Options[$I])" -Level 'PROMPT' + } + + if ($AllowMultipleSelections) { + Write-Log "`nSelect one or more options by typing the corresponding letters, separated by commas." -Level 'PROMPT' + } + else { + Write-Log "`nSelect an option by typing the corresponding letter." -Level 'PROMPT' + } + + [string]$UserInput = Read-Host + if ([string]::IsNullOrEmpty($UserInput)) { + $UserInput = $DefaultChoice + } + $UserInput = $UserInput.ToUpper() + + if ($AllowMultipleSelections) { + $Selections = $UserInput.Split(',') | ForEach-Object { $_.Trim() } + $SelectedIndices = @() + foreach ($Selection in $Selections) { + if ($Selection -match '^[A-Z]$') { + $Index = [int][char]$Selection - [int][char]'A' + if ($Index -ge 0 -and $Index -lt $Options.Length) { + $SelectedIndices += $Index + } + else { + Write-Log "Invalid input: $Selection. Please enter letters within the valid range of options." -Level 'ERROR' + return -1 + } + } + else { + Write-Log "Invalid input: $Selection. Please enter a letter between A and Z." -Level 'ERROR' + return -1 + } + } + return $SelectedIndices + } + else { + if ($UserInput -match '^[A-Z]$') { + $SelectedIndex = [int][char]$UserInput - [int][char]'A' + if ($SelectedIndex -ge 0 -and $SelectedIndex -lt $Options.Length) { + return $SelectedIndex + } + else { + Write-Log "Invalid input: $UserInput. Please enter a letter within the valid range of options." -Level 'ERROR' + return -1 + } + } + else { + Write-Log "Invalid input: $UserInput. Please enter a letter between A and Z." -Level 'ERROR' + return -1 + } + } +} + +function Exit-Script { + param ( + [int]$ExitCode, + [bool]$WaitForKeypress = $true + ) + + if ($ExitCode -ne 0) { + Write-Log "Script failed. Would you like to open the log file? (Y/N)" -Level 'PROMPT' + $openLog = Read-Host + if ($openLog -eq 'Y' -or $openLog -eq 'y') { + Start-Process notepad.exe -ArgumentList $LogFilePath + } + } + elseif ($WaitForKeypress) { + Write-Log "Press any key to exit..." + $host.UI.RawUI.ReadKey("NoEcho,IncludeKeyDown") | Out-Null + } + + return $ExitCode +} + +function Test-PythonExecutable { + param( + [string]$PythonExecutable + ) + + $DeactivateVenv = Join-Path $VenvDir "Scripts\Deactivate.bat" + if (Test-Path $DeactivateVenv) { + Write-Host "Deactivating virtual environment..." 2>&1 | Out-File $LogFilePath -Append + & $DeactivateVenv + Write-Host "Virtual environment deactivated." 2>&1 | Out-File $LogFilePath -Append + } + else { + Write-Host "Deactivation script not found: $DeactivateVenv" 2>&1 | Out-File $LogFilePath -Append + } + + $PythonCmd = Get-Command $PythonExecutable -ErrorAction SilentlyContinue + if ($PythonCmd) { + $VersionOutput = & $PythonCmd.Source --version 2>&1 + if ($LASTEXITCODE -eq 0) { + $Version = $VersionOutput | Select-String -Pattern "Python (\d+\.\d+\.\d+)" | ForEach-Object { $_.Matches.Groups[1].Value } + Write-Log "Python version $Version found using executable '$PythonExecutable'." + return $true + } + else { + Write-Log "Python executable '$PythonExecutable' not working correctly." -Level 'ERROR' + return $false + } + } + else { + Write-Log "Python executable '$PythonExecutable' not found." -Level 'ERROR' + return $false + } +} + +function Find-PythonExecutable { + $PythonExecutables = @( + "python", + "python3.12", + "python3.11", + "python3.10", + "python3.9", + "python3", + "C:\Users\$env:USERNAME\AppData\Local\Programs\Python\Python312\python.exe", + "C:\Users\$env:USERNAME\AppData\Local\Programs\Python\Python311\python.exe", + "C:\Users\$env:USERNAME\AppData\Local\Programs\Python\Python310\python.exe", + "C:\Users\$env:USERNAME\AppData\Local\Programs\Python\Python39\python.exe", + "C:\Python312\python.exe", + "C:\Python311\python.exe", + "C:\Python310\python.exe", + "C:\Python39\python.exe" + ) + + + foreach ($Executable in $PythonExecutables) { + if (Test-PythonExecutable -PythonExecutable $Executable) { + return $Executable + } + } + + return $null +} +function Main { + "Starting the operations..." | Out-File $LogFilePath -Append + "Current directory: $(Get-Location)" | Out-File $LogFilePath -Append + + # Exit on lower versions than Python 3.9 or when Python executable not found + $PythonExecutable = Find-PythonExecutable + if ($null -eq $PythonExecutable) { + Write-Log "No suitable Python executable found. Please ensure that Python 3.9 or higher is installed and available in the system PATH." -Level 'ERROR' + Exit 1 + } + + # Define the path to the Python executable in the virtual environment + $ActivateVenv = "$VenvDir\Scripts\Activate.ps1" + + # Check if the virtual environment exists, if not, create it + if (-Not (Test-Path $ActivateVenv)) { + Write-Log "Virtual environment not found. Creating virtual environment..." -Level 'ERROR' + & $PythonExecutable -m venv $VenvName 2>&1 | Out-File $LogFilePath -Append + if ($LASTEXITCODE -ne 0) { + Write-Log "Failed to create virtual environment." -Level 'ERROR' + Exit-Script -exitCode 1 + } + else { + Write-Log "Virtual environment created." + } + } + + # Activate the virtual environment and check if it was successful + Write-Log "Virtual environment found. Activating virtual environment..." + & $ActivateVenv 2>&1 | Out-File $LogFilePath -Append + # Check if virtual environment is activated + if ($env:VIRTUAL_ENV) { + Write-Log "Virtual environment is activated at: $($env:VIRTUAL_ENV)" + } + else { + Write-Log "Failed to activate virtual environment." -Level 'ERROR' + Exit-Script -exitCode 1 + } + + # Ensure pip + python -m ensurepip --default-pip 2>&1 | Out-File $LogFilePath -Append + + # Pull latest updates only if the repository state is not dirty + Write-Log "Checking if the repository is clean..." + $Status = & "git" status --porcelain + if ($Status) { + Write-Log "Changes in local git repository. Skipping git pull." + } + else { + Write-Log "Pulling latest updates..." + & "git" pull 2>&1 | Out-File $LogFilePath -Append + if ($LASTEXITCODE -ne 0) { + Write-Log "Failed to pull updates from Git." -Level 'ERROR' + Exit-Script -exitCode 1 + } + } + + if (-not (Test-Path "$VenvDir\Lib\site-packages\talib")) { + # Install TA-Lib using the virtual environment's pip + Write-Log "Installing TA-Lib using virtual environment's pip..." + python -m pip install --find-links=build_helpers\ --prefer-binary TA-Lib 2>&1 | Out-File $LogFilePath -Append + if ($LASTEXITCODE -ne 0) { + Write-Log "Failed to install TA-Lib." -Level 'ERROR' + Exit-Script -exitCode 1 + } + } + + # Present options for requirement files + $SelectedIndices = Get-UserSelection -prompt "Select which requirement files to install:" -options $RequirementFiles -defaultChoice 'A' + + # Cache the selected requirement files + $SelectedRequirementFiles = @() + $PipInstallArguments = @() + foreach ($Index in $SelectedIndices) { + $RelativePath = $RequirementFiles[$Index] + if (Test-Path $RelativePath) { + $SelectedRequirementFiles += $RelativePath + $PipInstallArguments += "-r", $RelativePath # Add each flag and path as separate elements + } + else { + Write-Log "Requirement file not found: $RelativePath" -Level 'ERROR' + Exit-Script -exitCode 1 + } + } + if ($PipInstallArguments.Count -ne 0) { + & pip install @PipInstallArguments # Use array splatting to pass arguments correctly + } + + # Install freqtrade from setup using the virtual environment's Python + Write-Log "Installing freqtrade from setup..." + pip install -e . 2>&1 | Out-File $LogFilePath -Append + if ($LASTEXITCODE -ne 0) { + Write-Log "Failed to install freqtrade." -Level 'ERROR' + Exit-Script -exitCode 1 + } + + Write-Log "Installing freqUI..." + python freqtrade install-ui 2>&1 | Out-File $LogFilePath -Append + if ($LASTEXITCODE -ne 0) { + Write-Log "Failed to install freqUI." -Level 'ERROR' + Exit-Script -exitCode 1 + } + + Write-Log "Installation/Update complete!" + Exit-Script -exitCode 0 +} + +# Call the Main function +Main diff --git a/tests/conftest.py b/tests/conftest.py index 3686a548abc..c9f923c1319 100644 --- a/tests/conftest.py +++ b/tests/conftest.py @@ -238,7 +238,6 @@ def patched_configuration_load_config_file(mocker, config) -> None: def patch_exchange( mocker, api_mock=None, id="binance", mock_markets=True, mock_supported_modes=True ) -> None: - mocker.patch(f"{EXMS}._load_async_markets", return_value={}) mocker.patch(f"{EXMS}.validate_config", MagicMock()) mocker.patch(f"{EXMS}.validate_timeframes", MagicMock()) mocker.patch(f"{EXMS}.id", PropertyMock(return_value=id)) @@ -248,6 +247,7 @@ def patch_exchange( mocker.patch("freqtrade.exchange.bybit.Bybit.cache_leverage_tiers") if mock_markets: + mocker.patch(f"{EXMS}._load_async_markets", return_value={}) if isinstance(mock_markets, bool): mock_markets = get_markets() mocker.patch(f"{EXMS}.markets", PropertyMock(return_value=mock_markets)) diff --git a/tests/exchange/test_exchange.py b/tests/exchange/test_exchange.py index 520de1c0fa4..fe47f821c3e 100644 --- a/tests/exchange/test_exchange.py +++ b/tests/exchange/test_exchange.py @@ -181,7 +181,7 @@ def test_remove_exchange_credentials(default_conf) -> None: def test_init_ccxt_kwargs(default_conf, mocker, caplog): - mocker.patch(f"{EXMS}._load_markets", MagicMock(return_value={})) + mocker.patch(f"{EXMS}.reload_markets") mocker.patch(f"{EXMS}.validate_stakecurrency") aei_mock = mocker.patch(f"{EXMS}.additional_exchange_init") @@ -518,7 +518,7 @@ def test__load_async_markets(default_conf, mocker, caplog): mocker.patch(f"{EXMS}._init_ccxt") mocker.patch(f"{EXMS}.validate_pairs") mocker.patch(f"{EXMS}.validate_timeframes") - mocker.patch(f"{EXMS}._load_markets") + mocker.patch(f"{EXMS}.reload_markets") mocker.patch(f"{EXMS}.validate_stakecurrency") mocker.patch(f"{EXMS}.validate_pricing") exchange = Exchange(default_conf) @@ -527,28 +527,26 @@ def test__load_async_markets(default_conf, mocker, caplog): assert exchange._api_async.load_markets.call_count == 1 caplog.set_level(logging.DEBUG) - exchange._api_async.load_markets = Mock(side_effect=ccxt.BaseError("deadbeef")) - exchange._load_async_markets() - - assert log_has("Could not load async markets. Reason: deadbeef", caplog) + exchange._api_async.load_markets = get_mock_coro(side_effect=ccxt.BaseError("deadbeef")) + with pytest.raises(ccxt.BaseError, match="deadbeef"): + exchange._load_async_markets() def test__load_markets(default_conf, mocker, caplog): caplog.set_level(logging.INFO) api_mock = MagicMock() - api_mock.load_markets = MagicMock(side_effect=ccxt.BaseError("SomeError")) + api_mock.load_markets = get_mock_coro(side_effect=ccxt.BaseError("SomeError")) mocker.patch(f"{EXMS}._init_ccxt", MagicMock(return_value=api_mock)) mocker.patch(f"{EXMS}.validate_pairs") mocker.patch(f"{EXMS}.validate_timeframes") - mocker.patch(f"{EXMS}._load_async_markets") mocker.patch(f"{EXMS}.validate_stakecurrency") mocker.patch(f"{EXMS}.validate_pricing") Exchange(default_conf) - assert log_has("Unable to initialize markets.", caplog) + assert log_has("Could not load markets.", caplog) expected_return = {"ETH/BTC": "available"} api_mock = MagicMock() - api_mock.load_markets = MagicMock(return_value=expected_return) + api_mock.load_markets = get_mock_coro(return_value=expected_return) mocker.patch(f"{EXMS}._init_ccxt", MagicMock(return_value=api_mock)) default_conf["exchange"]["pair_whitelist"] = ["ETH/BTC"] ex = Exchange(default_conf) @@ -563,12 +561,12 @@ def test_reload_markets(default_conf, mocker, caplog, time_machine): start_dt = dt_now() time_machine.move_to(start_dt, tick=False) api_mock = MagicMock() - api_mock.load_markets = MagicMock(return_value=initial_markets) + api_mock.load_markets = get_mock_coro(return_value=initial_markets) default_conf["exchange"]["markets_refresh_interval"] = 10 exchange = get_patched_exchange( mocker, default_conf, api_mock, id="binance", mock_markets=False ) - exchange._load_async_markets = MagicMock() + lam_spy = mocker.spy(exchange, "_load_async_markets") assert exchange._last_markets_refresh == dt_ts() assert exchange.markets == initial_markets @@ -577,42 +575,45 @@ def test_reload_markets(default_conf, mocker, caplog, time_machine): # less than 10 minutes have passed, no reload exchange.reload_markets() assert exchange.markets == initial_markets - assert exchange._load_async_markets.call_count == 0 + assert lam_spy.call_count == 0 - api_mock.load_markets = MagicMock(return_value=updated_markets) + api_mock.load_markets = get_mock_coro(return_value=updated_markets) # more than 10 minutes have passed, reload is executed time_machine.move_to(start_dt + timedelta(minutes=11), tick=False) exchange.reload_markets() assert exchange.markets == updated_markets - assert exchange._load_async_markets.call_count == 1 + assert lam_spy.call_count == 1 assert log_has("Performing scheduled market reload..", caplog) # Not called again - exchange._load_async_markets.reset_mock() + lam_spy.reset_mock() exchange.reload_markets() - assert exchange._load_async_markets.call_count == 0 + assert lam_spy.call_count == 0 def test_reload_markets_exception(default_conf, mocker, caplog): caplog.set_level(logging.DEBUG) api_mock = MagicMock() - api_mock.load_markets = MagicMock(side_effect=ccxt.NetworkError("LoadError")) + api_mock.load_markets = get_mock_coro(side_effect=ccxt.NetworkError("LoadError")) default_conf["exchange"]["markets_refresh_interval"] = 10 - exchange = get_patched_exchange(mocker, default_conf, api_mock, id="binance") + exchange = get_patched_exchange( + mocker, default_conf, api_mock, id="binance", mock_markets=False + ) + exchange._last_markets_refresh = 2 # less than 10 minutes have passed, no reload exchange.reload_markets() - assert exchange._last_markets_refresh == 0 - assert log_has_re(r"Could not reload markets.*", caplog) + assert exchange._last_markets_refresh == 2 + assert log_has_re(r"Could not load markets\..*", caplog) @pytest.mark.parametrize("stake_currency", ["ETH", "BTC", "USDT"]) def test_validate_stakecurrency(default_conf, stake_currency, mocker, caplog): default_conf["stake_currency"] = stake_currency api_mock = MagicMock() - type(api_mock).load_markets = MagicMock( + type(api_mock).load_markets = get_mock_coro( return_value={ "ETH/BTC": {"quote": "BTC"}, "LTC/BTC": {"quote": "BTC"}, @@ -623,7 +624,6 @@ def test_validate_stakecurrency(default_conf, stake_currency, mocker, caplog): mocker.patch(f"{EXMS}._init_ccxt", MagicMock(return_value=api_mock)) mocker.patch(f"{EXMS}.validate_pairs") mocker.patch(f"{EXMS}.validate_timeframes") - mocker.patch(f"{EXMS}._load_async_markets") mocker.patch(f"{EXMS}.validate_pricing") Exchange(default_conf) @@ -631,7 +631,7 @@ def test_validate_stakecurrency(default_conf, stake_currency, mocker, caplog): def test_validate_stakecurrency_error(default_conf, mocker, caplog): default_conf["stake_currency"] = "XRP" api_mock = MagicMock() - type(api_mock).load_markets = MagicMock( + type(api_mock).load_markets = get_mock_coro( return_value={ "ETH/BTC": {"quote": "BTC"}, "LTC/BTC": {"quote": "BTC"}, @@ -642,14 +642,13 @@ def test_validate_stakecurrency_error(default_conf, mocker, caplog): mocker.patch(f"{EXMS}._init_ccxt", MagicMock(return_value=api_mock)) mocker.patch(f"{EXMS}.validate_pairs") mocker.patch(f"{EXMS}.validate_timeframes") - mocker.patch(f"{EXMS}._load_async_markets") with pytest.raises( ConfigurationError, match=r"XRP is not available as stake on .*Available currencies are: BTC, ETH, USDT", ): Exchange(default_conf) - type(api_mock).load_markets = MagicMock(side_effect=ccxt.NetworkError("No connection.")) + type(api_mock).load_markets = get_mock_coro(side_effect=ccxt.NetworkError("No connection.")) mocker.patch(f"{EXMS}._init_ccxt", MagicMock(return_value=api_mock)) with pytest.raises( @@ -694,24 +693,26 @@ def test_get_pair_base_currency(default_conf, mocker, pair, expected): assert ex.get_pair_base_currency(pair) == expected -def test_validate_pairs(default_conf, mocker): # test exchange.validate_pairs directly +def test_validate_pairs(default_conf, mocker): api_mock = MagicMock() - type(api_mock).load_markets = MagicMock( + id_mock = PropertyMock(return_value="test_exchange") + type(api_mock).id = id_mock + + mocker.patch(f"{EXMS}._init_ccxt", MagicMock(return_value=api_mock)) + mocker.patch(f"{EXMS}.validate_timeframes") + mocker.patch( + f"{EXMS}._load_async_markets", return_value={ "ETH/BTC": {"quote": "BTC"}, "LTC/BTC": {"quote": "BTC"}, "XRP/BTC": {"quote": "BTC"}, "NEO/BTC": {"quote": "BTC"}, - } + }, ) - id_mock = PropertyMock(return_value="test_exchange") - type(api_mock).id = id_mock - - mocker.patch(f"{EXMS}._init_ccxt", MagicMock(return_value=api_mock)) - mocker.patch(f"{EXMS}.validate_timeframes") - mocker.patch(f"{EXMS}._load_async_markets") mocker.patch(f"{EXMS}.validate_stakecurrency") mocker.patch(f"{EXMS}.validate_pricing") + # test exchange.validate_pairs directly + # No assert - but this should not fail (!) Exchange(default_conf) @@ -751,7 +752,7 @@ def test_validate_pairs_exception(default_conf, mocker, caplog): def test_validate_pairs_restricted(default_conf, mocker, caplog): api_mock = MagicMock() - type(api_mock).load_markets = MagicMock( + type(api_mock).load_markets = get_mock_coro( return_value={ "ETH/BTC": {"quote": "BTC"}, "LTC/BTC": {"quote": "BTC"}, @@ -761,7 +762,6 @@ def test_validate_pairs_restricted(default_conf, mocker, caplog): ) mocker.patch(f"{EXMS}._init_ccxt", MagicMock(return_value=api_mock)) mocker.patch(f"{EXMS}.validate_timeframes") - mocker.patch(f"{EXMS}._load_async_markets") mocker.patch(f"{EXMS}.validate_pricing") mocker.patch(f"{EXMS}.validate_stakecurrency") @@ -774,9 +774,9 @@ def test_validate_pairs_restricted(default_conf, mocker, caplog): ) -def test_validate_pairs_stakecompatibility(default_conf, mocker, caplog): +def test_validate_pairs_stakecompatibility(default_conf, mocker): api_mock = MagicMock() - type(api_mock).load_markets = MagicMock( + type(api_mock).load_markets = get_mock_coro( return_value={ "ETH/BTC": {"quote": "BTC"}, "LTC/BTC": {"quote": "BTC"}, @@ -787,17 +787,16 @@ def test_validate_pairs_stakecompatibility(default_conf, mocker, caplog): ) mocker.patch(f"{EXMS}._init_ccxt", MagicMock(return_value=api_mock)) mocker.patch(f"{EXMS}.validate_timeframes") - mocker.patch(f"{EXMS}._load_async_markets") mocker.patch(f"{EXMS}.validate_stakecurrency") mocker.patch(f"{EXMS}.validate_pricing") Exchange(default_conf) -def test_validate_pairs_stakecompatibility_downloaddata(default_conf, mocker, caplog): +def test_validate_pairs_stakecompatibility_downloaddata(default_conf, mocker): api_mock = MagicMock() default_conf["stake_currency"] = "" - type(api_mock).load_markets = MagicMock( + type(api_mock).load_markets = get_mock_coro( return_value={ "ETH/BTC": {"quote": "BTC"}, "LTC/BTC": {"quote": "BTC"}, @@ -808,7 +807,6 @@ def test_validate_pairs_stakecompatibility_downloaddata(default_conf, mocker, ca ) mocker.patch(f"{EXMS}._init_ccxt", MagicMock(return_value=api_mock)) mocker.patch(f"{EXMS}.validate_timeframes") - mocker.patch(f"{EXMS}._load_async_markets") mocker.patch(f"{EXMS}.validate_stakecurrency") mocker.patch(f"{EXMS}.validate_pricing") @@ -816,10 +814,10 @@ def test_validate_pairs_stakecompatibility_downloaddata(default_conf, mocker, ca assert type(api_mock).load_markets.call_count == 1 -def test_validate_pairs_stakecompatibility_fail(default_conf, mocker, caplog): +def test_validate_pairs_stakecompatibility_fail(default_conf, mocker): default_conf["exchange"]["pair_whitelist"].append("HELLO-WORLD") api_mock = MagicMock() - type(api_mock).load_markets = MagicMock( + type(api_mock).load_markets = get_mock_coro( return_value={ "ETH/BTC": {"quote": "BTC"}, "LTC/BTC": {"quote": "BTC"}, @@ -830,7 +828,6 @@ def test_validate_pairs_stakecompatibility_fail(default_conf, mocker, caplog): ) mocker.patch(f"{EXMS}._init_ccxt", MagicMock(return_value=api_mock)) mocker.patch(f"{EXMS}.validate_timeframes") - mocker.patch(f"{EXMS}._load_async_markets") mocker.patch(f"{EXMS}.validate_stakecurrency") with pytest.raises(OperationalException, match=r"Stake-currency 'BTC' not compatible with.*"): @@ -847,7 +844,7 @@ def test_validate_timeframes(default_conf, mocker, timeframe): type(api_mock).timeframes = timeframes mocker.patch(f"{EXMS}._init_ccxt", MagicMock(return_value=api_mock)) - mocker.patch(f"{EXMS}._load_markets", MagicMock(return_value={})) + mocker.patch(f"{EXMS}.reload_markets") mocker.patch(f"{EXMS}.validate_pairs") mocker.patch(f"{EXMS}.validate_stakecurrency") mocker.patch(f"{EXMS}.validate_pricing") @@ -865,7 +862,7 @@ def test_validate_timeframes_failed(default_conf, mocker): type(api_mock).timeframes = timeframes mocker.patch(f"{EXMS}._init_ccxt", MagicMock(return_value=api_mock)) - mocker.patch(f"{EXMS}._load_markets", MagicMock(return_value={})) + mocker.patch(f"{EXMS}.reload_markets") mocker.patch(f"{EXMS}.validate_pairs") mocker.patch(f"{EXMS}.validate_stakecurrency") mocker.patch(f"{EXMS}.validate_pricing") @@ -895,7 +892,7 @@ def test_validate_timeframes_emulated_ohlcv_1(default_conf, mocker): del api_mock.timeframes mocker.patch(f"{EXMS}._init_ccxt", MagicMock(return_value=api_mock)) - mocker.patch(f"{EXMS}._load_markets", MagicMock(return_value={})) + mocker.patch(f"{EXMS}.reload_markets") mocker.patch(f"{EXMS}.validate_pairs") mocker.patch(f"{EXMS}.validate_stakecurrency") with pytest.raises( @@ -917,7 +914,7 @@ def test_validate_timeframes_emulated_ohlcvi_2(default_conf, mocker): del api_mock.timeframes mocker.patch(f"{EXMS}._init_ccxt", MagicMock(return_value=api_mock)) - mocker.patch(f"{EXMS}._load_markets", MagicMock(return_value={"timeframes": None})) + mocker.patch(f"{EXMS}.reload_markets") mocker.patch(f"{EXMS}.validate_pairs", MagicMock()) mocker.patch(f"{EXMS}.validate_stakecurrency") with pytest.raises( @@ -939,7 +936,7 @@ def test_validate_timeframes_not_in_config(default_conf, mocker): type(api_mock).timeframes = timeframes mocker.patch(f"{EXMS}._init_ccxt", MagicMock(return_value=api_mock)) - mocker.patch(f"{EXMS}._load_markets", MagicMock(return_value={})) + mocker.patch(f"{EXMS}.reload_markets") mocker.patch(f"{EXMS}.validate_pairs") mocker.patch(f"{EXMS}.validate_stakecurrency") mocker.patch(f"{EXMS}.validate_pricing") @@ -955,7 +952,7 @@ def test_validate_pricing(default_conf, mocker): } type(api_mock).has = PropertyMock(return_value=has) mocker.patch(f"{EXMS}._init_ccxt", MagicMock(return_value=api_mock)) - mocker.patch(f"{EXMS}._load_markets", MagicMock(return_value={})) + mocker.patch(f"{EXMS}.reload_markets") mocker.patch(f"{EXMS}.validate_trading_mode_and_margin_mode") mocker.patch(f"{EXMS}.validate_pairs") mocker.patch(f"{EXMS}.validate_timeframes") @@ -991,7 +988,7 @@ def test_validate_ordertypes(default_conf, mocker): type(api_mock).has = PropertyMock(return_value={"createMarketOrder": True}) mocker.patch(f"{EXMS}._init_ccxt", MagicMock(return_value=api_mock)) - mocker.patch(f"{EXMS}._load_markets", MagicMock(return_value={})) + mocker.patch(f"{EXMS}.reload_markets") mocker.patch(f"{EXMS}.validate_pairs") mocker.patch(f"{EXMS}.validate_timeframes") mocker.patch(f"{EXMS}.validate_stakecurrency") @@ -1050,7 +1047,7 @@ def test_validate_ordertypes_stop_advanced(default_conf, mocker, exchange_name, default_conf["margin_mode"] = MarginMode.ISOLATED type(api_mock).has = PropertyMock(return_value={"createMarketOrder": True}) mocker.patch(f"{EXMS}._init_ccxt", MagicMock(return_value=api_mock)) - mocker.patch(f"{EXMS}._load_markets", MagicMock(return_value={})) + mocker.patch(f"{EXMS}.reload_markets") mocker.patch(f"{EXMS}.validate_pairs") mocker.patch(f"{EXMS}.validate_timeframes") mocker.patch(f"{EXMS}.validate_stakecurrency") @@ -1075,7 +1072,7 @@ def test_validate_ordertypes_stop_advanced(default_conf, mocker, exchange_name, def test_validate_order_types_not_in_config(default_conf, mocker): api_mock = MagicMock() mocker.patch(f"{EXMS}._init_ccxt", MagicMock(return_value=api_mock)) - mocker.patch(f"{EXMS}._load_markets", MagicMock(return_value={})) + mocker.patch(f"{EXMS}.reload_markets") mocker.patch(f"{EXMS}.validate_pairs") mocker.patch(f"{EXMS}.validate_timeframes") mocker.patch(f"{EXMS}.validate_pricing") @@ -1947,7 +1944,9 @@ def test_fetch_trading_fees(default_conf, mocker): assert api_mock.fetch_trading_fees.call_count == 1 api_mock.fetch_trading_fees.reset_mock() - + # Reload-markets calls fetch_trading_fees, too - so the explicit calls in the below + # exception test would be called twice. + mocker.patch(f"{EXMS}.reload_markets") ccxt_exceptionhandlers( mocker, default_conf, api_mock, exchange_name, "fetch_trading_fees", "fetch_trading_fees" ) diff --git a/tests/exchange_online/conftest.py b/tests/exchange_online/conftest.py index ee1e8a74108..8820ce3e7ae 100644 --- a/tests/exchange_online/conftest.py +++ b/tests/exchange_online/conftest.py @@ -45,7 +45,25 @@ "workingTime": 1674493798550, "fills": [], "selfTradePreventionMode": "NONE", - } + }, + { + "symbol": "SOLUSDT", + "orderId": 3551312894, + "orderListId": -1, + "clientOrderId": "x-R4DD3S8297c73a11ccb9dc8f2811ba", + "transactTime": 1674493798550, + "price": "15.50000000", + "origQty": "1.10000000", + "executedQty": "1.10000000", + "cummulativeQuoteQty": "17.05", + "status": "FILLED", + "timeInForce": "GTC", + "type": "LIMIT", + "side": "BUY", + "workingTime": 1674493798550, + "fills": [], + "selfTradePreventionMode": "NONE", + }, ], }, "binanceus": { @@ -288,6 +306,36 @@ "hasQuoteVolume": True, "timeframe": "1h", "futures": False, + "sample_order": [ + { + "symbol": "SOL-USDT", + "orderId": "1762393630149869568", + "transactTime": "1674493798550", + "price": "15.5", + "stopPrice": "0", + "origQty": "1.1", + "executedQty": "1.1", + "cummulativeQuoteQty": "17.05", + "status": "FILLED", + "type": "LIMIT", + "side": "BUY", + "clientOrderID": "", + }, + { + "symbol": "SOL-USDT", + "orderId": "1762393630149869568", + "transactTime": "1674493798550", + "price": "15.5", + "stopPrice": "0", + "origQty": "1.1", + "executedQty": "1.1", + "cummulativeQuoteQty": "17.05", + "status": "FILLED", + "type": "MARKET", + "side": "BUY", + "clientOrderID": "", + }, + ], }, } diff --git a/tests/exchange_online/test_ccxt_compat.py b/tests/exchange_online/test_ccxt_compat.py index 6eb9f002311..49fbfc60da6 100644 --- a/tests/exchange_online/test_ccxt_compat.py +++ b/tests/exchange_online/test_ccxt_compat.py @@ -76,7 +76,8 @@ def test_ccxt_order_parse(self, exchange: EXCHANGE_FIXTURE_TYPE): assert isinstance(po["timestamp"], int) assert isinstance(po["price"], float) assert po["price"] == 15.5 - if po["average"] is not None: + if po["status"] == "closed": + # Filled orders should have average assigned. assert isinstance(po["average"], float) assert po["average"] == 15.5 assert po["symbol"] == pair diff --git a/tests/freqai/conftest.py b/tests/freqai/conftest.py index fce01b9ee71..887dfe3a48a 100644 --- a/tests/freqai/conftest.py +++ b/tests/freqai/conftest.py @@ -50,6 +50,7 @@ def freqai_conf(default_conf, tmp_path): freqaiconf.update( { "datadir": Path(default_conf["datadir"]), + "runmode": "backtest", "strategy": "freqai_test_strat", "user_data_dir": tmp_path, "strategy-path": "freqtrade/tests/strategy/strats", diff --git a/tests/freqtradebot/test_freqtradebot.py b/tests/freqtradebot/test_freqtradebot.py index 826e7ebf490..db2e96b37e5 100644 --- a/tests/freqtradebot/test_freqtradebot.py +++ b/tests/freqtradebot/test_freqtradebot.py @@ -699,18 +699,20 @@ def test_process_trade_creation( def test_process_exchange_failures(default_conf_usdt, ticker_usdt, mocker) -> None: + # TODO: Move this test to test_worker patch_RPCManager(mocker) patch_exchange(mocker) mocker.patch.multiple( EXMS, fetch_ticker=ticker_usdt, - reload_markets=MagicMock(side_effect=TemporaryError), + reload_markets=MagicMock(), create_order=MagicMock(side_effect=TemporaryError), ) sleep_mock = mocker.patch("time.sleep") worker = Worker(args=None, config=default_conf_usdt) patch_get_signal(worker.freqtrade) + mocker.patch(f"{EXMS}.reload_markets", MagicMock(side_effect=TemporaryError)) worker._process_running() assert sleep_mock.called is True @@ -1146,6 +1148,36 @@ def test_execute_entry_confirm_error(mocker, default_conf_usdt, fee, limit_order assert not freqtrade.execute_entry(pair, stake_amount) +@pytest.mark.parametrize("is_short", [False, True]) +def test_execute_entry_fully_canceled_on_create( + mocker, default_conf_usdt, fee, limit_order_open, is_short +) -> None: + freqtrade = get_patched_freqtradebot(mocker, default_conf_usdt) + + mock_hce = mocker.spy(freqtrade, "handle_cancel_enter") + order = limit_order_open[entry_side(is_short)] + pair = "ETH/USDT" + order["symbol"] = pair + order["status"] = "canceled" + order["filled"] = 0.0 + + mocker.patch.multiple( + EXMS, + fetch_ticker=MagicMock(return_value={"bid": 1.9, "ask": 2.2, "last": 1.9}), + create_order=MagicMock(return_value=order), + get_rate=MagicMock(return_value=0.11), + get_min_pair_stake_amount=MagicMock(return_value=1), + get_fee=fee, + ) + stake_amount = 2 + + assert freqtrade.execute_entry(pair, stake_amount) + assert mock_hce.call_count == 1 + # an order that immediately cancels completely should delete the order. + trades = Trade.get_trades().all() + assert len(trades) == 0 + + @pytest.mark.parametrize("is_short", [False, True]) def test_execute_entry_min_leverage(mocker, default_conf_usdt, fee, limit_order, is_short) -> None: default_conf_usdt["trading_mode"] = "futures" @@ -4978,6 +5010,47 @@ def test_handle_onexchange_order_exit(mocker, default_conf_usdt, limit_order, is assert trade.amount == 5.0 +@pytest.mark.usefixtures("init_persistence") +@pytest.mark.parametrize("is_short", [False, True]) +def test_handle_onexchange_order_fully_canceled_enter( + mocker, default_conf_usdt, limit_order, is_short, caplog +): + default_conf_usdt["dry_run"] = False + freqtrade = get_patched_freqtradebot(mocker, default_conf_usdt) + + entry_order = limit_order[entry_side(is_short)] + entry_order["status"] = "canceled" + entry_order["filled"] = 0.0 + mock_fo = mocker.patch( + f"{EXMS}.fetch_orders", + return_value=[ + entry_order, + ], + ) + mocker.patch(f"{EXMS}.get_rate", return_value=entry_order["price"]) + + trade = Trade( + pair="ETH/USDT", + fee_open=0.001, + fee_close=0.001, + open_rate=entry_order["price"], + open_date=dt_now(), + stake_amount=entry_order["cost"], + amount=entry_order["amount"], + exchange="binance", + is_short=is_short, + leverage=1, + ) + + trade.orders.append(Order.parse_from_ccxt_object(entry_order, "ADA/USDT", entry_side(is_short))) + Trade.session.add(trade) + assert freqtrade.handle_onexchange_order(trade) is True + assert log_has_re(r"Trade only had fully canceled entry orders\. .*", caplog) + assert mock_fo.call_count == 1 + trades = Trade.get_trades().all() + assert len(trades) == 0 + + def test_get_valid_price(mocker, default_conf_usdt) -> None: patch_RPCManager(mocker) patch_exchange(mocker) diff --git a/tests/optimize/test_hyperopt.py b/tests/optimize/test_hyperopt.py index 4d2f9ae36b1..d2ff5b8d3e0 100644 --- a/tests/optimize/test_hyperopt.py +++ b/tests/optimize/test_hyperopt.py @@ -1063,7 +1063,7 @@ def test_in_strategy_auto_hyperopt(mocker, hyperopt_conf, tmp_path, fee) -> None def test_in_strategy_auto_hyperopt_with_parallel(mocker, hyperopt_conf, tmp_path, fee) -> None: mocker.patch(f"{EXMS}.validate_config", MagicMock()) mocker.patch(f"{EXMS}.get_fee", fee) - mocker.patch(f"{EXMS}._load_markets") + mocker.patch(f"{EXMS}.reload_markets") mocker.patch(f"{EXMS}.markets", PropertyMock(return_value=get_markets())) (tmp_path / "hyperopt_results").mkdir(parents=True) # Dummy-reduce points to ensure scikit-learn is forced to generate new values diff --git a/tests/persistence/test_persistence.py b/tests/persistence/test_persistence.py index a307614863f..0545ac8613f 100644 --- a/tests/persistence/test_persistence.py +++ b/tests/persistence/test_persistence.py @@ -1961,9 +1961,25 @@ def test_get_canceled_exit_order_count(fee, is_short): trade = Trade.get_trades([Trade.pair == "ETC/BTC"]).first() # No canceled order. assert trade.get_canceled_exit_order_count() == 0 + # Property returns the same result + assert trade.canceled_exit_order_count == 0 trade.orders[-1].status = "canceled" assert trade.get_canceled_exit_order_count() == 1 + assert trade.canceled_exit_order_count == 1 + + +@pytest.mark.usefixtures("init_persistence") +@pytest.mark.parametrize("is_short", [True, False]) +def test_fully_canceled_entry_order_count(fee, is_short): + create_mock_trades(fee, is_short=is_short) + trade = Trade.get_trades([Trade.pair == "ETC/BTC"]).first() + # No canceled order. + assert trade.fully_canceled_entry_order_count == 0 + + trade.orders[0].status = "canceled" + trade.orders[0].filled = 0 + assert trade.fully_canceled_entry_order_count == 1 @pytest.mark.usefixtures("init_persistence") diff --git a/tests/plugins/test_pairlist.py b/tests/plugins/test_pairlist.py index b1fed192b7d..f6c58a1e70d 100644 --- a/tests/plugins/test_pairlist.py +++ b/tests/plugins/test_pairlist.py @@ -773,6 +773,7 @@ def test_VolumePairList_whitelist_gen( whitelist_result, caplog, ) -> None: + whitelist_conf["runmode"] = "backtest" whitelist_conf["pairlists"] = pairlists whitelist_conf["stake_currency"] = base_currency @@ -1270,6 +1271,7 @@ def test_ShuffleFilter_init(mocker, whitelist_conf, caplog) -> None: {"method": "StaticPairList"}, {"method": "ShuffleFilter", "seed": 43}, ] + whitelist_conf["runmode"] = "backtest" exchange = get_patched_exchange(mocker, whitelist_conf) plm = PairListManager(exchange, whitelist_conf) diff --git a/tests/setup.Tests.ps1 b/tests/setup.Tests.ps1 new file mode 100644 index 00000000000..e58a4729d7a --- /dev/null +++ b/tests/setup.Tests.ps1 @@ -0,0 +1,177 @@ + +Describe "Setup and Tests" { + BeforeAll { + # Setup variables + $SetupScriptPath = Join-Path $PSScriptRoot "..\setup.ps1" + $Global:LogFilePath = Join-Path $env:TEMP "script_log.txt" + + # Check if the setup script exists + if (-Not (Test-Path -Path $SetupScriptPath)) { + Write-Host "Error: setup.ps1 script not found at path: $SetupScriptPath" + exit 1 + } + + # Mock main to prevent it from running + Mock Main {} + + . $SetupScriptPath + } + + Context "Write-Log Tests" -Tag "Unit" { + It "should write INFO level log" { + if (Test-Path $Global:LogFilePath){ + Remove-Item $Global:LogFilePath -ErrorAction SilentlyContinue + } + + Write-Log -Message "Test Info Message" -Level "INFO" + $Global:LogFilePath | Should -Exist + + $LogContent = Get-Content $Global:LogFilePath + $LogContent | Should -Contain "INFO: Test Info Message" + } + + It "should write ERROR level log" { + if (Test-Path $Global:LogFilePath){ + Remove-Item $Global:LogFilePath -ErrorAction SilentlyContinue + } + + Write-Log -Message "Test Error Message" -Level "ERROR" + $Global:LogFilePath | Should -Exist + + $LogContent = Get-Content $Global:LogFilePath + $LogContent | Should -Contain "ERROR: Test Error Message" + } + } + + Describe "Get-UserSelection Tests" { + Context "Valid input" { + It "Should return the correct index for a valid single selection" { + $Options = @("Option1", "Option2", "Option3") + Mock Read-Host { return "B" } + $Result = Get-UserSelection -prompt "Select an option" -options $Options + $Result | Should -Be 1 + } + + It "Should return the correct index for a valid single selection" { + $Options = @("Option1", "Option2", "Option3") + Mock Read-Host { return "b" } + $Result = Get-UserSelection -prompt "Select an option" -options $Options + $Result | Should -Be 1 + } + + It "Should return the default choice when no input is provided" { + $Options = @("Option1", "Option2", "Option3") + Mock Read-Host { return "" } + $Result = Get-UserSelection -prompt "Select an option" -options $Options -defaultChoice "C" + $Result | Should -Be 2 + } + } + + Context "Invalid input" { + It "Should return -1 for an invalid letter selection" { + $Options = @("Option1", "Option2", "Option3") + Mock Read-Host { return "X" } + $Result = Get-UserSelection -prompt "Select an option" -options $Options + $Result | Should -Be -1 + } + + It "Should return -1 for a selection outside the valid range" { + $Options = @("Option1", "Option2", "Option3") + Mock Read-Host { return "D" } + $Result = Get-UserSelection -prompt "Select an option" -options $Options + $Result | Should -Be -1 + } + + It "Should return -1 for a non-letter input" { + $Options = @("Option1", "Option2", "Option3") + Mock Read-Host { return "1" } + $Result = Get-UserSelection -prompt "Select an option" -options $Options + $Result | Should -Be -1 + } + + It "Should return -1 for mixed valid and invalid input" { + Mock Read-Host { return "A,X,B,Y,C,Z" } + $Options = @("Option1", "Option2", "Option3") + $Indices = Get-UserSelection -prompt "Select options" -options $Options -defaultChoice "A" + $Indices | Should -Be -1 + } + } + + Context "Multiple selections" { + It "Should handle valid input correctly" { + Mock Read-Host { return "A, B, C" } + $Options = @("Option1", "Option2", "Option3") + $Indices = Get-UserSelection -prompt "Select options" -options $Options -defaultChoice "A" + $Indices | Should -Be @(0, 1, 2) + } + + It "Should handle valid input without whitespace correctly" { + Mock Read-Host { return "A,B,C" } + $Options = @("Option1", "Option2", "Option3") + $Indices = Get-UserSelection -prompt "Select options" -options $Options -defaultChoice "A" + $Indices | Should -Be @(0, 1, 2) + } + + It "Should return indices for selected options" { + Mock Read-Host { return "a,b" } + $Options = @("Option1", "Option2", "Option3") + $Indices = Get-UserSelection -prompt "Select options" -options $Options + $Indices | Should -Be @(0, 1) + } + + It "Should return default choice if no input" { + Mock Read-Host { return "" } + $Options = @("Option1", "Option2", "Option3") + $Indices = Get-UserSelection -prompt "Select options" -options $Options -defaultChoice "C" + $Indices | Should -Be @(2) + } + + It "Should handle invalid input gracefully" { + Mock Read-Host { return "x,y,z" } + $Options = @("Option1", "Option2", "Option3") + $Indices = Get-UserSelection -prompt "Select options" -options $Options -defaultChoice "A" + $Indices | Should -Be -1 + } + + It "Should handle input without whitespace" { + Mock Read-Host { return "a,b,c" } + $Options = @("Option1", "Option2", "Option3") + $Indices = Get-UserSelection -prompt "Select options" -options $Options + $Indices | Should -Be @(0, 1, 2) + } + } + } + + Describe "Exit-Script Tests" -Tag "Unit" { + BeforeEach { + Mock Write-Log {} + Mock Start-Process {} + Mock Read-Host { return "Y" } + } + + It "should exit with the given exit code without waiting for key press" { + $ExitCode = Exit-Script -ExitCode 0 -isSubShell $true -waitForKeypress $false + $ExitCode | Should -Be 0 + } + + It "should prompt to open log file on error" { + Exit-Script -ExitCode 1 -isSubShell $true -waitForKeypress $false + Assert-MockCalled Read-Host -Exactly 1 + Assert-MockCalled Start-Process -Exactly 1 + } + } + + Context 'Find-PythonExecutable' { + It 'Returns the first valid Python executable' { + Mock Test-PythonExecutable { $true } -ParameterFilter { $PythonExecutable -eq 'python' } + $Result = Find-PythonExecutable + $Result | Should -Be 'python' + } + + It 'Returns null if no valid Python executable is found' { + Mock Test-PythonExecutable { $false } + $Result = Find-PythonExecutable + $Result | Should -Be $null + } + } +}