diff --git a/.github/workflows/binance-lev-tier-update.yml b/.github/workflows/binance-lev-tier-update.yml index f06251cca..2e0a3d3b2 100644 --- a/.github/workflows/binance-lev-tier-update.yml +++ b/.github/workflows/binance-lev-tier-update.yml @@ -19,7 +19,7 @@ jobs: - uses: actions/setup-python@v5 with: - python-version: "3.11" + python-version: "3.12" - name: Install ccxt run: pip install ccxt diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml index 4d5ee5c5f..fc8dbecbe 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) @@ -334,7 +345,7 @@ jobs: - name: Set up Python uses: actions/setup-python@v5 with: - python-version: "3.10" + python-version: "3.12" - name: pre-commit dependencies run: | @@ -348,7 +359,7 @@ jobs: - uses: actions/setup-python@v5 with: - python-version: "3.10" + python-version: "3.12" - uses: pre-commit/action@v3.0.1 docs-check: @@ -363,7 +374,7 @@ jobs: - name: Set up Python uses: actions/setup-python@v5 with: - python-version: "3.11" + python-version: "3.12" - name: Documentation build run: | @@ -389,7 +400,7 @@ jobs: - name: Set up Python uses: actions/setup-python@v5 with: - python-version: "3.11" + python-version: "3.12" - name: Cache_dependencies uses: actions/cache@v4 @@ -471,7 +482,7 @@ jobs: - name: Set up Python uses: actions/setup-python@v5 with: - python-version: "3.11" + python-version: "3.12" - name: Build distribution run: | @@ -542,7 +553,7 @@ jobs: - name: Set up Python uses: actions/setup-python@v5 with: - python-version: "3.11" + python-version: "3.12" - name: Extract branch name id: extract-branch @@ -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 69f5dbb4e..d30fdd1bf 100644 --- a/.github/workflows/pre-commit-update.yml +++ b/.github/workflows/pre-commit-update.yml @@ -17,7 +17,7 @@ jobs: - uses: actions/setup-python@v5 with: - python-version: "3.11" + python-version: "3.12" - name: Install pre-commit @@ -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 1fdec721b..992e9b373 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 c6e54b112..d7ab7c05c 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 cb7fdf907..000000000 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 f88bc8002..000000000 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 4cb902191..000000000 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 81ccc4818..000000000 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 596b5923d..000000000 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 64a61ff0d..000000000 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 000000000..68d6df6b3 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 000000000..941a2aa9d 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 000000000..702f51559 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 000000000..2a8253e39 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 000000000..a5cab7919 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 000000000..a03c3ca8c Binary files /dev/null and b/build_helpers/TA_Lib-0.4.30-cp39-cp39-win_amd64.whl differ diff --git a/build_helpers/pyarrow-16.0.0-cp311-cp311-linux_armv7l.whl b/build_helpers/pyarrow-16.1.0-cp311-cp311-linux_armv7l.whl similarity index 66% rename from build_helpers/pyarrow-16.0.0-cp311-cp311-linux_armv7l.whl rename to build_helpers/pyarrow-16.1.0-cp311-cp311-linux_armv7l.whl index f8022ffbf..345f2dd63 100644 Binary files a/build_helpers/pyarrow-16.0.0-cp311-cp311-linux_armv7l.whl and b/build_helpers/pyarrow-16.1.0-cp311-cp311-linux_armv7l.whl differ diff --git a/build_helpers/pyarrow-16.0.0-cp39-cp39-linux_armv7l.whl b/build_helpers/pyarrow-16.1.0-cp39-cp39-linux_armv7l.whl similarity index 66% rename from build_helpers/pyarrow-16.0.0-cp39-cp39-linux_armv7l.whl rename to build_helpers/pyarrow-16.1.0-cp39-cp39-linux_armv7l.whl index c1cbf19de..ffbe09bf4 100644 Binary files a/build_helpers/pyarrow-16.0.0-cp39-cp39-linux_armv7l.whl and b/build_helpers/pyarrow-16.1.0-cp39-cp39-linux_armv7l.whl differ diff --git a/docker/Dockerfile.armhf b/docker/Dockerfile.armhf index 1165f305c..688254122 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/configuration.md b/docs/configuration.md index 6e89348e4..e2501cf48 100644 --- a/docs/configuration.md +++ b/docs/configuration.md @@ -568,7 +568,14 @@ The possible values are: `GTC` (default), `FOK` or `IOC`. This is ongoing work. For now, it is supported only for binance, gate and kucoin. Please don't change the default value unless you know what you are doing and have researched the impact of using different values for your particular exchange. -### What values can be used for fiat_display_currency? +### Fiat conversion + +Freqtrade uses the Coingecko API to convert the coin value to it's corresponding fiat value for the Telegram reports. +The FIAT currency can be set in the configuration file as `fiat_display_currency`. + +Removing `fiat_display_currency` completely from the configuration will skip initializing coingecko, and will not show any FIAT currency conversion. This has no importance for the correct functioning of the bot. + +#### What values can be used for fiat_display_currency? The `fiat_display_currency` configuration parameter sets the base currency to use for the conversion from coin to fiat in the bot Telegram reports. @@ -587,7 +594,25 @@ The valid values are: "BTC", "ETH", "XRP", "LTC", "BCH", "BNB" ``` -Removing `fiat_display_currency` completely from the configuration will skip initializing coingecko, and will not show any FIAT currency conversion. This has no importance for the correct functioning of the bot. +#### Coingecko Rate limit problems + +On some IP ranges, coingecko is heavily rate-limiting. +In such cases, you may want to add your coingecko API key to the configuration. + +``` json +{ + "fiat_display_currency": "USD", + "coingecko": { + "api_key": "your-api", + "is_demo": true + } +} +``` + +Freqtrade supports both Demo and Pro coingecko API keys. + +The Coingecko API key is NOT required for the bot to function correctly. +It is only used for the conversion of coin to fiat in the Telegram reports, which usually also work without API key. ## Using Dry-run mode diff --git a/docs/data-download.md b/docs/data-download.md index 890a89b60..2a51edb0b 100644 --- a/docs/data-download.md +++ b/docs/data-download.md @@ -24,10 +24,10 @@ usage: freqtrade download-data [-h] [-v] [--logfile FILE] [-V] [-c PATH] [--days INT] [--new-pairs-days INT] [--include-inactive-pairs] [--timerange TIMERANGE] [--dl-trades] - [--exchange EXCHANGE] + [--convert] [--exchange EXCHANGE] [-t TIMEFRAMES [TIMEFRAMES ...]] [--erase] [--data-format-ohlcv {json,jsongz,hdf5,feather,parquet}] - [--data-format-trades {json,jsongz,hdf5,feather}] + [--data-format-trades {json,jsongz,hdf5,feather,parquet}] [--trading-mode {spot,margin,futures}] [--prepend] @@ -48,6 +48,11 @@ options: --dl-trades Download trades instead of OHLCV data. The bot will resample trades to the desired timeframe as specified as --timeframes/-t. + --convert Convert downloaded trades to OHLCV data. Only + applicable in combination with `--dl-trades`. Will be + automatic for exchanges which don't have historic + OHLCV (e.g. Kraken). If not provided, use `trades-to- + ohlcv` to convert trades data to OHLCV data. --exchange EXCHANGE Exchange name. Only valid if no config is provided. -t TIMEFRAMES [TIMEFRAMES ...], --timeframes TIMEFRAMES [TIMEFRAMES ...] Specify which tickers to download. Space-separated @@ -57,7 +62,7 @@ options: --data-format-ohlcv {json,jsongz,hdf5,feather,parquet} Storage format for downloaded candle (OHLCV) data. (default: `feather`). - --data-format-trades {json,jsongz,hdf5,feather} + --data-format-trades {json,jsongz,hdf5,feather,parquet} Storage format for downloaded trades data. (default: `feather`). --trading-mode {spot,margin,futures}, --tradingmode {spot,margin,futures} @@ -471,15 +476,20 @@ ETH/USDT 5m, 15m, 30m, 1h, 2h, 4h ## Trades (tick) data -By default, `download-data` sub-command downloads Candles (OHLCV) data. Some exchanges also provide historic trade-data via their API. +By default, `download-data` sub-command downloads Candles (OHLCV) data. Most exchanges also provide historic trade-data via their API. This data can be useful if you need many different timeframes, since it is only downloaded once, and then resampled locally to the desired timeframes. -Since this data is large by default, the files use the feather fileformat by default. They are stored in your data-directory with the naming convention of `-trades.feather` (`ETH_BTC-trades.feather`). Incremental mode is also supported, as for historic OHLCV data, so downloading the data once per week with `--days 8` will create an incremental data-repository. +Since this data is large by default, the files use the feather file format by default. They are stored in your data-directory with the naming convention of `-trades.feather` (`ETH_BTC-trades.feather`). Incremental mode is also supported, as for historic OHLCV data, so downloading the data once per week with `--days 8` will create an incremental data-repository. -To use this mode, simply add `--dl-trades` to your call. This will swap the download method to download trades, and resamples the data locally. +To use this mode, simply add `--dl-trades` to your call. This will swap the download method to download trades. +If `--convert` is also provided, the resample step will happen automatically and overwrite eventually existing OHLCV data for the given pair/timeframe combinations. -!!! Warning "do not use" - You should not use this unless you're a kraken user. Most other exchanges provide OHLCV data with sufficient history. +!!! Warning "Do not use" + You should not use this unless you're a kraken user (Kraken does not provide historic OHLCV data). + Most other exchanges provide OHLCV data with sufficient history, so downloading multiple timeframes through that method will still proof to be a lot faster than downloading trades data. + +!!! Note "Kraken user" + Kraken users should read [this](exchanges.md#historic-kraken-data) before starting to download data. Example call: @@ -490,12 +500,6 @@ freqtrade download-data --exchange kraken --pairs XRP/EUR ETH/EUR --days 20 --dl !!! Note While this method uses async calls, it will be slow, since it requires the result of the previous call to generate the next request to the exchange. -!!! Warning - The historic trades are not available during Freqtrade dry-run and live trade modes because all exchanges tested provide this data with a delay of few 100 candles, so it's not suitable for real-time trading. - -!!! Note "Kraken user" - Kraken users should read [this](exchanges.md#historic-kraken-data) before starting to download data. - ## Next step -Great, you now have backtest data downloaded, so you can now start [backtesting](backtesting.md) your strategy. +Great, you now have some data downloaded, so you can now start [backtesting](backtesting.md) your strategy. diff --git a/docs/exchanges.md b/docs/exchanges.md index d4437cfff..f3550e97e 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 f603baca3..d25051291 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 26debe993..55835f555 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/installation.md b/docs/installation.md index 189f45cfa..f86043fb3 100644 --- a/docs/installation.md +++ b/docs/installation.md @@ -286,7 +286,7 @@ cd freqtrade #### Freqtrade install: Conda Environment ```bash -conda create --name freqtrade python=3.11 +conda create --name freqtrade python=3.12 ``` !!! Note "Creating Conda Environment" diff --git a/docs/requirements-docs.txt b/docs/requirements-docs.txt index a333447c2..c290fb852 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.22 +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 51573b77f..2b55c2563 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 a1095b465..e0353d4da 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 cd9007d98..5e28d98fb 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,17 +38,11 @@ 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). -As compiling from source on windows has heavy dependencies (requires a partial visual studio installation), Freqtrade provides these dependencies (in the binary wheel format) for the latest 3 Python versions (3.9, 3.10 and 3.11) and for 64bit Windows. +As compiling from source on windows has heavy dependencies (requires a partial visual studio installation), Freqtrade provides these dependencies (in the binary wheel format) for the latest 3 Python versions (3.9, 3.10, 3.11 and 3.12) and for 64bit Windows. These Wheels are also used by CI running on windows, and are therefore tested together with freqtrade. Other versions must be downloaded from the above link. diff --git a/freqtrade/__init__.py b/freqtrade/__init__.py index d1cef043d..932f9b701 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/arguments.py b/freqtrade/commands/arguments.py index 98281e9f0..c527a80d6 100755 --- a/freqtrade/commands/arguments.py +++ b/freqtrade/commands/arguments.py @@ -142,6 +142,7 @@ ARGS_DOWNLOAD_DATA = [ "include_inactive", "timerange", "download_trades", + "convert_trades", "exchange", "timeframes", "erase", diff --git a/freqtrade/commands/build_config_commands.py b/freqtrade/commands/build_config_commands.py index 82ff76061..1e771a372 100644 --- a/freqtrade/commands/build_config_commands.py +++ b/freqtrade/commands/build_config_commands.py @@ -100,7 +100,10 @@ def ask_user_config() -> Dict[str, Any]: { "type": "text", "name": "fiat_display_currency", - "message": "Please insert your display Currency (for reporting):", + "message": ( + "Please insert your display Currency for reporting " + "(leave empty to disable FIAT conversion):" + ), "default": "USD", }, { @@ -110,6 +113,7 @@ def ask_user_config() -> Dict[str, Any]: "choices": [ "binance", "binanceus", + "bingx", "gate", "htx", "kraken", @@ -125,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/commands/cli_options.py b/freqtrade/commands/cli_options.py index b7aaf7812..b9236a0ab 100755 --- a/freqtrade/commands/cli_options.py +++ b/freqtrade/commands/cli_options.py @@ -450,6 +450,14 @@ AVAILABLE_CLI_OPTIONS = { "desired timeframe as specified as --timeframes/-t.", action="store_true", ), + "convert_trades": Arg( + "--convert", + help="Convert downloaded trades to OHLCV data. Only applicable in combination with " + "`--dl-trades`. " + "Will be automatic for exchanges which don't have historic OHLCV (e.g. Kraken). " + "If not provided, use `trades-to-ohlcv` to convert trades data to OHLCV data.", + action="store_true", + ), "format_from_trades": Arg( "--format-from", help="Source format for data conversion.", diff --git a/freqtrade/configuration/configuration.py b/freqtrade/configuration/configuration.py index 99ec44525..cc8b5407e 100644 --- a/freqtrade/configuration/configuration.py +++ b/freqtrade/configuration/configuration.py @@ -370,6 +370,7 @@ class Configuration: ("days", "Detected --days: {}"), ("include_inactive", "Detected --include-inactive-pairs: {}"), ("download_trades", "Detected --dl-trades: {}"), + ("convert_trades", "Detected --convert: {} - Converting Trade data to OHCV {}"), ("dataformat_ohlcv", 'Using "{}" to store OHLCV data.'), ("dataformat_trades", 'Using "{}" to store trades data.'), ("show_timerange", "Detected --show-timerange"), diff --git a/freqtrade/constants.py b/freqtrade/constants.py index 8423d04f4..f56e14503 100644 --- a/freqtrade/constants.py +++ b/freqtrade/constants.py @@ -157,6 +157,7 @@ SUPPORTED_FIAT = [ "LTC", "BCH", "BNB", + "", # Allow empty field in config. ] MINIMAL_CONFIG = { @@ -323,6 +324,14 @@ CONF_SCHEMA = { }, "required": REQUIRED_ORDERTIF, }, + "coingecko": { + "type": "object", + "properties": { + "is_demo": {"type": "boolean", "default": True}, + "api_key": {"type": "string"}, + }, + "required": ["is_demo", "api_key"], + }, "exchange": {"$ref": "#/definitions/exchange"}, "edge": {"$ref": "#/definitions/edge"}, "freqai": {"$ref": "#/definitions/freqai"}, diff --git a/freqtrade/data/history/history_utils.py b/freqtrade/data/history/history_utils.py index 575bf1343..d83b6eef8 100644 --- a/freqtrade/data/history/history_utils.py +++ b/freqtrade/data/history/history_utils.py @@ -629,17 +629,20 @@ def download_data_main(config: Config) -> None: trading_mode=config.get("trading_mode", TradingMode.SPOT), ) - # Convert downloaded trade data to different timeframes - convert_trades_to_ohlcv( - pairs=expanded_pairs, - timeframes=config["timeframes"], - datadir=config["datadir"], - timerange=timerange, - erase=bool(config.get("erase")), - data_format_ohlcv=config["dataformat_ohlcv"], - data_format_trades=config["dataformat_trades"], - candle_type=config.get("candle_type_def", CandleType.SPOT), - ) + if config.get("convert_trades") or not exchange.get_option("ohlcv_has_history", True): + # Convert downloaded trade data to different timeframes + # Only auto-convert for exchanges without historic klines + + convert_trades_to_ohlcv( + pairs=expanded_pairs, + timeframes=config["timeframes"], + datadir=config["datadir"], + timerange=timerange, + erase=bool(config.get("erase")), + data_format_ohlcv=config["dataformat_ohlcv"], + data_format_trades=config["dataformat_trades"], + candle_type=config.get("candle_type_def", CandleType.SPOT), + ) else: if not exchange.get_option("ohlcv_has_history", True): raise OperationalException( diff --git a/freqtrade/data/metrics.py b/freqtrade/data/metrics.py index 0bee68326..2e4673fb5 100644 --- a/freqtrade/data/metrics.py +++ b/freqtrade/data/metrics.py @@ -1,5 +1,6 @@ import logging import math +from dataclasses import dataclass from datetime import datetime from typing import Dict, Tuple @@ -160,6 +161,16 @@ def calculate_underwater( return max_drawdown_df +@dataclass() +class DrawDownResult: + drawdown_abs: float = 0.0 + high_date: pd.Timestamp = None + low_date: pd.Timestamp = None + high_value: float = 0.0 + low_value: float = 0.0 + relative_account_drawdown: float = 0.0 + + def calculate_max_drawdown( trades: pd.DataFrame, *, @@ -167,14 +178,14 @@ def calculate_max_drawdown( value_col: str = "profit_abs", starting_balance: float = 0, relative: bool = False, -) -> Tuple[float, pd.Timestamp, pd.Timestamp, float, float, float]: +) -> DrawDownResult: """ Calculate max drawdown and the corresponding close dates :param trades: DataFrame containing trades (requires columns close_date and profit_ratio) :param date_col: Column in DataFrame to use for dates (defaults to 'close_date') :param value_col: Column in DataFrame to use for values (defaults to 'profit_abs') :param starting_balance: Portfolio starting balance - properly calculate relative drawdown. - :return: Tuple (float, highdate, lowdate, highvalue, lowvalue, relative_drawdown) + :return: DrawDownResult object with absolute max drawdown, high and low time and high and low value, and the relative account drawdown :raise: ValueError if trade-dataframe was found empty. @@ -201,13 +212,13 @@ def calculate_max_drawdown( low_val = max_drawdown_df.loc[idxmin, "cumulative"] max_drawdown_rel = max_drawdown_df.loc[idxmin, "drawdown_relative"] - return ( - abs(max_drawdown_df.loc[idxmin, "drawdown"]), - high_date, - low_date, - high_val, - low_val, - max_drawdown_rel, + return DrawDownResult( + drawdown_abs=abs(max_drawdown_df.loc[idxmin, "drawdown"]), + high_date=high_date, + low_date=low_date, + high_value=high_val, + low_value=low_val, + relative_account_drawdown=max_drawdown_rel, ) @@ -350,9 +361,10 @@ def calculate_calmar( # calculate max drawdown try: - _, _, _, _, _, max_drawdown = calculate_max_drawdown( + drawdown = calculate_max_drawdown( trades, value_col="profit_abs", starting_balance=starting_balance ) + max_drawdown = drawdown.relative_account_drawdown except ValueError: max_drawdown = 0 diff --git a/freqtrade/enums/runmode.py b/freqtrade/enums/runmode.py index d5c2cf652..a24dd6e2c 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.py b/freqtrade/exchange/binance.py index 18fb23ebd..07c4f9286 100644 --- a/freqtrade/exchange/binance.py +++ b/freqtrade/exchange/binance.py @@ -201,7 +201,6 @@ class Binance(Exchange): "Freqtrade only supports isolated futures for leverage trading" ) - @retrier def load_leverage_tiers(self) -> Dict[str, List[Dict]]: if self.trading_mode == TradingMode.FUTURES: if self._config["dry_run"]: @@ -209,16 +208,6 @@ class Binance(Exchange): with leverage_tiers_path.open() as json_file: return json_load(json_file) else: - try: - return self._api.fetch_leverage_tiers() - except ccxt.DDoSProtection as e: - raise DDosProtection(e) from e - except (ccxt.OperationFailed, ccxt.ExchangeError) as e: - raise TemporaryError( - f"Could not fetch leverage amounts due to" - f"{e.__class__.__name__}. Message: {e}" - ) from e - except ccxt.BaseError as e: - raise OperationalException(e) from e + return self.get_leverage_tiers() else: return {} diff --git a/freqtrade/exchange/binance_leverage_tiers.json b/freqtrade/exchange/binance_leverage_tiers.json index f6ad6bc47..71744bf36 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, @@ -6051,6 +5855,120 @@ } } ], + "BB/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.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": 200000.0, + "maintenanceMarginRate": 0.1, + "maxLeverage": 5.0, + "info": { + "bracket": "4", + "initialLeverage": "5", + "notionalCap": "200000", + "notionalFloor": "100000", + "maintMarginRatio": "0.1", + "cum": "5675.0" + } + }, + { + "tier": 5.0, + "currency": "USDT", + "minNotional": 200000.0, + "maxNotional": 500000.0, + "maintenanceMarginRate": 0.125, + "maxLeverage": 4.0, + "info": { + "bracket": "5", + "initialLeverage": "4", + "notionalCap": "500000", + "notionalFloor": "200000", + "maintMarginRatio": "0.125", + "cum": "10675.0" + } + }, + { + "tier": 6.0, + "currency": "USDT", + "minNotional": 500000.0, + "maxNotional": 1000000.0, + "maintenanceMarginRate": 0.25, + "maxLeverage": 2.0, + "info": { + "bracket": "6", + "initialLeverage": "2", + "notionalCap": "1000000", + "notionalFloor": "500000", + "maintMarginRatio": "0.25", + "cum": "73175.0" + } + }, + { + "tier": 7.0, + "currency": "USDT", + "minNotional": 1000000.0, + "maxNotional": 2000000.0, + "maintenanceMarginRate": 0.5, + "maxLeverage": 1.0, + "info": { + "bracket": "7", + "initialLeverage": "1", + "notionalCap": "2000000", + "notionalFloor": "1000000", + "maintMarginRatio": "0.5", + "cum": "323175.0" + } + } + ], "BCH/USDC:USDC": [ { "tier": 1.0, @@ -6815,104 +6733,6 @@ } } ], - "BLUEBIRD/USDT:USDT": [ - { - "tier": 1.0, - "currency": "USDT", - "minNotional": 0.0, - "maxNotional": 5000.0, - "maintenanceMarginRate": 0.01, - "maxLeverage": 11.0, - "info": { - "bracket": "1", - "initialLeverage": "11", - "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": 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, @@ -8348,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" @@ -8363,129 +8183,161 @@ { "tier": 3.0, "currency": "USDT", - "minNotional": 500000.0, - "maxNotional": 10000000.0, - "maintenanceMarginRate": 0.01, - "maxLeverage": 50.0, + "minNotional": 600000.0, + "maxNotional": 3000000.0, + "maintenanceMarginRate": 0.0065, + "maxLeverage": 75.0, "info": { "bracket": "3", - "initialLeverage": "50", - "notionalCap": "10000000", - "notionalFloor": "500000", - "maintMarginRatio": "0.01", - "cum": "2550.0" + "initialLeverage": "75", + "notionalCap": "3000000", + "notionalFloor": "600000", + "maintMarginRatio": "0.0065", + "cum": "950.0" } }, { "tier": 4.0, "currency": "USDT", - "minNotional": 10000000.0, - "maxNotional": 80000000.0, - "maintenanceMarginRate": 0.025, - "maxLeverage": 20.0, + "minNotional": 3000000.0, + "maxNotional": 12000000.0, + "maintenanceMarginRate": 0.01, + "maxLeverage": 50.0, "info": { "bracket": "4", - "initialLeverage": "20", - "notionalCap": "80000000", - "notionalFloor": "10000000", - "maintMarginRatio": "0.025", - "cum": "152550.0" + "initialLeverage": "50", + "notionalCap": "12000000", + "notionalFloor": "3000000", + "maintMarginRatio": "0.01", + "cum": "11450.0" } }, { "tier": 5.0, "currency": "USDT", - "minNotional": 80000000.0, - "maxNotional": 150000000.0, - "maintenanceMarginRate": 0.05, - "maxLeverage": 10.0, + "minNotional": 12000000.0, + "maxNotional": 70000000.0, + "maintenanceMarginRate": 0.02, + "maxLeverage": 25.0, "info": { "bracket": "5", - "initialLeverage": "10", - "notionalCap": "150000000", - "notionalFloor": "80000000", - "maintMarginRatio": "0.05", - "cum": "2152550.0" + "initialLeverage": "25", + "notionalCap": "70000000", + "notionalFloor": "12000000", + "maintMarginRatio": "0.02", + "cum": "131450.0" } }, { "tier": 6.0, "currency": "USDT", - "minNotional": 150000000.0, - "maxNotional": 300000000.0, - "maintenanceMarginRate": 0.1, - "maxLeverage": 5.0, + "minNotional": 70000000.0, + "maxNotional": 100000000.0, + "maintenanceMarginRate": 0.025, + "maxLeverage": 20.0, "info": { "bracket": "6", - "initialLeverage": "5", - "notionalCap": "300000000", - "notionalFloor": "150000000", - "maintMarginRatio": "0.1", - "cum": "9652550.0" + "initialLeverage": "20", + "notionalCap": "100000000", + "notionalFloor": "70000000", + "maintMarginRatio": "0.025", + "cum": "481450.0" } }, { "tier": 7.0, "currency": "USDT", - "minNotional": 300000000.0, - "maxNotional": 450000000.0, - "maintenanceMarginRate": 0.125, - "maxLeverage": 4.0, + "minNotional": 100000000.0, + "maxNotional": 230000000.0, + "maintenanceMarginRate": 0.05, + "maxLeverage": 10.0, "info": { "bracket": "7", - "initialLeverage": "4", - "notionalCap": "450000000", - "notionalFloor": "300000000", - "maintMarginRatio": "0.125", - "cum": "17152550.0" + "initialLeverage": "10", + "notionalCap": "230000000", + "notionalFloor": "100000000", + "maintMarginRatio": "0.05", + "cum": "2981450.0" } }, { "tier": 8.0, "currency": "USDT", - "minNotional": 450000000.0, - "maxNotional": 600000000.0, - "maintenanceMarginRate": 0.15, - "maxLeverage": 3.0, + "minNotional": 230000000.0, + "maxNotional": 480000000.0, + "maintenanceMarginRate": 0.1, + "maxLeverage": 5.0, "info": { "bracket": "8", - "initialLeverage": "3", - "notionalCap": "600000000", - "notionalFloor": "450000000", - "maintMarginRatio": "0.15", - "cum": "28402550.0" + "initialLeverage": "5", + "notionalCap": "480000000", + "notionalFloor": "230000000", + "maintMarginRatio": "0.1", + "cum": "14481450.0" } }, { "tier": 9.0, "currency": "USDT", - "minNotional": 600000000.0, - "maxNotional": 800000000.0, - "maintenanceMarginRate": 0.25, - "maxLeverage": 2.0, + "minNotional": 480000000.0, + "maxNotional": 600000000.0, + "maintenanceMarginRate": 0.125, + "maxLeverage": 4.0, "info": { "bracket": "9", - "initialLeverage": "2", - "notionalCap": "800000000", - "notionalFloor": "600000000", - "maintMarginRatio": "0.25", - "cum": "88402550.0" + "initialLeverage": "4", + "notionalCap": "600000000", + "notionalFloor": "480000000", + "maintMarginRatio": "0.125", + "cum": "26481450.0" } }, { "tier": 10.0, "currency": "USDT", + "minNotional": 600000000.0, + "maxNotional": 800000000.0, + "maintenanceMarginRate": 0.15, + "maxLeverage": 3.0, + "info": { + "bracket": "10", + "initialLeverage": "3", + "notionalCap": "800000000", + "notionalFloor": "600000000", + "maintMarginRatio": "0.15", + "cum": "41481450.0" + } + }, + { + "tier": 11.0, + "currency": "USDT", "minNotional": 800000000.0, - "maxNotional": 1000000000.0, + "maxNotional": 1200000000.0, + "maintenanceMarginRate": 0.25, + "maxLeverage": 2.0, + "info": { + "bracket": "11", + "initialLeverage": "2", + "notionalCap": "1200000000", + "notionalFloor": "800000000", + "maintMarginRatio": "0.25", + "cum": "121481450.0" + } + }, + { + "tier": 12.0, + "currency": "USDT", + "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" } } ], @@ -8945,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, @@ -9939,104 +9693,6 @@ } } ], - "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, - "maintenanceMarginRate": 0.05, - "maxLeverage": 6.0, - "info": { - "bracket": "3", - "initialLeverage": "6", - "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" - } - } - ], "COMBO/USDT:USDT": [ { "tier": 1.0, @@ -13848,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" @@ -13863,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" } } ], @@ -15257,104 +14929,6 @@ } } ], - "FOOTBALL/USDT:USDT": [ - { - "tier": 1.0, - "currency": "USDT", - "minNotional": 0.0, - "maxNotional": 5000.0, - "maintenanceMarginRate": 0.02, - "maxLeverage": 11.0, - "info": { - "bracket": "1", - "initialLeverage": "11", - "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" - } - } - ], "FRONT/USDT:USDT": [ { "tier": 1.0, @@ -17245,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, @@ -22535,120 +21995,6 @@ } } ], - "MBL/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": 200000.0, - "maintenanceMarginRate": 0.1, - "maxLeverage": 5.0, - "info": { - "bracket": "4", - "initialLeverage": "5", - "notionalCap": "200000", - "notionalFloor": "100000", - "maintMarginRatio": "0.1", - "cum": "5675.0" - } - }, - { - "tier": 5.0, - "currency": "USDT", - "minNotional": 200000.0, - "maxNotional": 500000.0, - "maintenanceMarginRate": 0.125, - "maxLeverage": 4.0, - "info": { - "bracket": "5", - "initialLeverage": "4", - "notionalCap": "500000", - "notionalFloor": "200000", - "maintMarginRatio": "0.125", - "cum": "10675.0" - } - }, - { - "tier": 6.0, - "currency": "USDT", - "minNotional": 500000.0, - "maxNotional": 1000000.0, - "maintenanceMarginRate": 0.25, - "maxLeverage": 2.0, - "info": { - "bracket": "6", - "initialLeverage": "2", - "notionalCap": "1000000", - "notionalFloor": "500000", - "maintMarginRatio": "0.25", - "cum": "73175.0" - } - }, - { - "tier": 7.0, - "currency": "USDT", - "minNotional": 1000000.0, - "maxNotional": 2000000.0, - "maintenanceMarginRate": 0.5, - "maxLeverage": 1.0, - "info": { - "bracket": "7", - "initialLeverage": "1", - "notionalCap": "2000000", - "notionalFloor": "1000000", - "maintMarginRatio": "0.5", - "cum": "323175.0" - } - } - ], "MDT/USDT:USDT": [ { "tier": 1.0, @@ -24455,6 +23801,120 @@ } } ], + "NOT/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.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": 200000.0, + "maintenanceMarginRate": 0.1, + "maxLeverage": 5.0, + "info": { + "bracket": "4", + "initialLeverage": "5", + "notionalCap": "200000", + "notionalFloor": "100000", + "maintMarginRatio": "0.1", + "cum": "5675.0" + } + }, + { + "tier": 5.0, + "currency": "USDT", + "minNotional": 200000.0, + "maxNotional": 500000.0, + "maintenanceMarginRate": 0.125, + "maxLeverage": 4.0, + "info": { + "bracket": "5", + "initialLeverage": "4", + "notionalCap": "500000", + "notionalFloor": "200000", + "maintMarginRatio": "0.125", + "cum": "10675.0" + } + }, + { + "tier": 6.0, + "currency": "USDT", + "minNotional": 500000.0, + "maxNotional": 1000000.0, + "maintenanceMarginRate": 0.25, + "maxLeverage": 2.0, + "info": { + "bracket": "6", + "initialLeverage": "2", + "notionalCap": "1000000", + "notionalFloor": "500000", + "maintMarginRatio": "0.25", + "cum": "73175.0" + } + }, + { + "tier": 7.0, + "currency": "USDT", + "minNotional": 1000000.0, + "maxNotional": 2000000.0, + "maintenanceMarginRate": 0.5, + "maxLeverage": 1.0, + "info": { + "bracket": "7", + "initialLeverage": "1", + "notionalCap": "2000000", + "notionalFloor": "1000000", + "maintMarginRatio": "0.5", + "cum": "323175.0" + } + } + ], "NTRN/USDT:USDT": [ { "tier": 1.0, @@ -30607,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, @@ -33261,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 7ee08273c..2d81643a1 100644 --- a/freqtrade/exchange/bingx.py +++ b/freqtrade/exchange/bingx.py @@ -17,4 +17,7 @@ class Bingx(Exchange): _ft_has: Dict = { "ohlcv_candle_limit": 1000, + "stoploss_on_exchange": True, + "stoploss_order_types": {"limit": "limit", "market": "market"}, + "order_time_in_force": ["GTC", "IOC", "PO"], } diff --git a/freqtrade/exchange/bybit.py b/freqtrade/exchange/bybit.py index 1f810cf1f..c8b05d1de 100644 --- a/freqtrade/exchange/bybit.py +++ b/freqtrade/exchange/bybit.py @@ -250,38 +250,19 @@ class Bybit(Exchange): @retrier def get_leverage_tiers(self) -> Dict[str, List[Dict]]: """ - Temporary workaround for https://github.com/freqtrade/freqtrade/issues/10196 - should be removed or updated once https://github.com/ccxt/ccxt/issues/22448 is fixed. + Cache leverage tiers for 1 day, since they are not expected to change often, and + bybit requires pagination to fetch all tiers. """ # Load cached tiers - tiers_cached = self.load_cached_leverage_tiers(self._config["stake_currency"]) + tiers_cached = self.load_cached_leverage_tiers( + self._config["stake_currency"], timedelta(days=1) + ) if tiers_cached: - tiers = tiers_cached - return tiers + return tiers_cached # Fetch tiers from exchange - - symbols = self._api.market_symbols([]) - - def parse_resp(response): - result = self._api.safe_dict(response, "result", {}) - data = self._api.safe_list(result, "list", []) - return self._api.parse_leverage_tiers(data, symbols, "symbol") - - params = { - "category": "linear", - } - tiers = {} - # 20 pairs ... should be sufficient assuming 30 pairs per page - # Aimed to avoid a potential infinite loop - for _ in range(20): - # Fetch from private endpoint - response = self._api.publicGetV5MarketRiskLimit(params) - tiers = tiers | parse_resp(response) - if (cursor := response["result"]["nextPageCursor"]) == "": - break - params.update({"cursor": cursor}) + tiers = super().get_leverage_tiers() self.cache_leverage_tiers(tiers, self._config["stake_currency"]) return tiers diff --git a/freqtrade/exchange/common.py b/freqtrade/exchange/common.py index 5035d0dd8..99f891836 100644 --- a/freqtrade/exchange/common.py +++ b/freqtrade/exchange/common.py @@ -53,6 +53,7 @@ MAP_EXCHANGE_CHILDCLASS = { SUPPORTED_EXCHANGES = [ "binance", + "bingx", "bitmart", "gate", "htx", diff --git a/freqtrade/exchange/exchange.py b/freqtrade/exchange/exchange.py index b7b304432..0b6e6f5f1 100644 --- a/freqtrade/exchange/exchange.py +++ b/freqtrade/exchange/exchange.py @@ -159,7 +159,7 @@ class Exchange: :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]] = {} @@ -246,7 +246,7 @@ class Exchange: 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( @@ -367,7 +367,7 @@ class Exchange: """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 @@ -552,30 +552,26 @@ class Exchange: 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={})) + markets = 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 + 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 - 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() + 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 - except ccxt.BaseError: - logger.exception("Unable to initialize markets.") - - 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 @@ -584,13 +580,18 @@ class Exchange: 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: """ @@ -3040,7 +3041,16 @@ class Exchange: } file_dump_json(filename, data) - def load_cached_leverage_tiers(self, stake_currency: str) -> Optional[Dict[str, List[Dict]]]: + def load_cached_leverage_tiers( + self, stake_currency: str, cache_time: Optional[timedelta] = None + ) -> Optional[Dict[str, List[Dict]]]: + """ + Load cached leverage tiers from disk + :param cache_time: The maximum age of the cache before it is considered outdated + """ + if not cache_time: + # Default to 4 weeks + cache_time = timedelta(weeks=4) filename = self._config["datadir"] / "futures" / f"leverage_tiers_{stake_currency}.json" if filename.is_file(): try: @@ -3048,7 +3058,7 @@ class Exchange: updated = tiers.get("updated") if updated: updated_dt = parser.parse(updated) - if updated_dt < datetime.now(timezone.utc) - timedelta(weeks=4): + if updated_dt < datetime.now(timezone.utc) - cache_time: logger.info("Cached leverage tiers are outdated. Will update.") return None return tiers["data"] diff --git a/freqtrade/exchange/htx.py b/freqtrade/exchange/htx.py index 58eb919bc..f939534e9 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 b17fffe0a..d43f569d8 100644 --- a/freqtrade/freqai/data_kitchen.py +++ b/freqtrade/freqai/data_kitchen.py @@ -960,7 +960,7 @@ class FreqaiDataKitchen: """ 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/freqai/prediction_models/CatboostClassifier.py b/freqtrade/freqai/prediction_models/CatboostClassifier.py index 9dd1a4107..176139770 100644 --- a/freqtrade/freqai/prediction_models/CatboostClassifier.py +++ b/freqtrade/freqai/prediction_models/CatboostClassifier.py @@ -1,5 +1,4 @@ import logging -import sys from pathlib import Path from typing import Any, Dict @@ -57,8 +56,6 @@ class CatboostClassifier(BaseClassifierModel): X=train_data, eval_set=test_data, init_model=init_model, - log_cout=sys.stdout, - log_cerr=sys.stderr, ) return cbr diff --git a/freqtrade/freqai/prediction_models/CatboostClassifierMultiTarget.py b/freqtrade/freqai/prediction_models/CatboostClassifierMultiTarget.py index de93a4751..02cb91f5a 100644 --- a/freqtrade/freqai/prediction_models/CatboostClassifierMultiTarget.py +++ b/freqtrade/freqai/prediction_models/CatboostClassifierMultiTarget.py @@ -1,5 +1,4 @@ import logging -import sys from pathlib import Path from typing import Any, Dict @@ -68,8 +67,6 @@ class CatboostClassifierMultiTarget(BaseClassifierModel): { "eval_set": eval_sets[i], "init_model": init_models[i], - "log_cout": sys.stdout, - "log_cerr": sys.stderr, } ) diff --git a/freqtrade/freqai/prediction_models/CatboostRegressor.py b/freqtrade/freqai/prediction_models/CatboostRegressor.py index f0bb5e999..5401a808b 100644 --- a/freqtrade/freqai/prediction_models/CatboostRegressor.py +++ b/freqtrade/freqai/prediction_models/CatboostRegressor.py @@ -1,5 +1,4 @@ import logging -import sys from pathlib import Path from typing import Any, Dict @@ -56,8 +55,6 @@ class CatboostRegressor(BaseRegressionModel): X=train_data, eval_set=test_data, init_model=init_model, - log_cout=sys.stdout, - log_cerr=sys.stderr, ) return model diff --git a/freqtrade/freqai/prediction_models/CatboostRegressorMultiTarget.py b/freqtrade/freqai/prediction_models/CatboostRegressorMultiTarget.py index 1300fbfe2..c2a5344e3 100644 --- a/freqtrade/freqai/prediction_models/CatboostRegressorMultiTarget.py +++ b/freqtrade/freqai/prediction_models/CatboostRegressorMultiTarget.py @@ -1,5 +1,4 @@ import logging -import sys from pathlib import Path from typing import Any, Dict @@ -67,8 +66,6 @@ class CatboostRegressorMultiTarget(BaseRegressionModel): { "eval_set": eval_sets[i], "init_model": init_models[i], - "log_cout": sys.stdout, - "log_cerr": sys.stderr, } ) diff --git a/freqtrade/freqtradebot.py b/freqtrade/freqtradebot.py index 401de5147..8d53097a5 100644 --- a/freqtrade/freqtradebot.py +++ b/freqtrade/freqtradebot.py @@ -492,10 +492,11 @@ class FreqtradeBot(LoggingMixin): 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( @@ -541,6 +542,19 @@ class FreqtradeBot(LoggingMixin): 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}, " @@ -566,6 +580,7 @@ class FreqtradeBot(LoggingMixin): 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 @@ -1007,7 +1022,13 @@ class FreqtradeBot(LoggingMixin): # 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 @@ -1229,7 +1250,9 @@ class FreqtradeBot(LoggingMixin): 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/optimize/hyperopt_loss/hyperopt_loss_max_drawdown.py b/freqtrade/optimize/hyperopt_loss/hyperopt_loss_max_drawdown.py index 4d6a7efac..ce1b29cf5 100644 --- a/freqtrade/optimize/hyperopt_loss/hyperopt_loss_max_drawdown.py +++ b/freqtrade/optimize/hyperopt_loss/hyperopt_loss_max_drawdown.py @@ -42,4 +42,4 @@ class MaxDrawDownHyperOptLoss(IHyperOptLoss): except ValueError: # No losing trade, therefore no drawdown. return -total_profit - return -total_profit / max_drawdown[0] + return -total_profit / max_drawdown.drawdown_abs diff --git a/freqtrade/optimize/hyperopt_loss/hyperopt_loss_profit_drawdown.py b/freqtrade/optimize/hyperopt_loss/hyperopt_loss_profit_drawdown.py index 99d28de0d..61e2a6d32 100644 --- a/freqtrade/optimize/hyperopt_loss/hyperopt_loss_profit_drawdown.py +++ b/freqtrade/optimize/hyperopt_loss/hyperopt_loss_profit_drawdown.py @@ -10,22 +10,28 @@ individual needs. from pandas import DataFrame +from freqtrade.constants import Config from freqtrade.data.metrics import calculate_max_drawdown from freqtrade.optimize.hyperopt import IHyperOptLoss -# higher numbers penalize drawdowns more severely +# smaller numbers penalize drawdowns more severely DRAWDOWN_MULT = 0.075 class ProfitDrawDownHyperOptLoss(IHyperOptLoss): @staticmethod - def hyperopt_loss_function(results: DataFrame, trade_count: int, *args, **kwargs) -> float: + def hyperopt_loss_function(results: DataFrame, config: Config, *args, **kwargs) -> float: total_profit = results["profit_abs"].sum() try: - max_drawdown_abs = calculate_max_drawdown(results, value_col="profit_abs")[5] + drawdown = calculate_max_drawdown( + results, starting_balance=config["dry_run_wallet"], value_col="profit_abs" + ) + relative_account_drawdown = drawdown.relative_account_drawdown except ValueError: - max_drawdown_abs = 0 + relative_account_drawdown = 0 - return -1 * (total_profit * (1 - max_drawdown_abs * DRAWDOWN_MULT)) + return -1 * ( + total_profit - (relative_account_drawdown * total_profit) * (1 - DRAWDOWN_MULT) + ) diff --git a/freqtrade/optimize/hyperopt_tools.py b/freqtrade/optimize/hyperopt_tools.py index 58d01af47..50c55c43d 100644 --- a/freqtrade/optimize/hyperopt_tools.py +++ b/freqtrade/optimize/hyperopt_tools.py @@ -358,14 +358,15 @@ class HyperoptTools: ) @staticmethod - def prepare_trials_columns(trials: pd.DataFrame, has_drawdown: bool) -> pd.DataFrame: + def prepare_trials_columns(trials: pd.DataFrame) -> pd.DataFrame: trials["Best"] = "" if "results_metrics.winsdrawslosses" not in trials.columns: # Ensure compatibility with older versions of hyperopt results trials["results_metrics.winsdrawslosses"] = "N/A" - if not has_drawdown: + has_account_drawdown = "results_metrics.max_drawdown_account" in trials.columns + if not has_account_drawdown: # Ensure compatibility with older versions of hyperopt results trials["results_metrics.max_drawdown_account"] = None if "is_random" not in trials.columns: @@ -389,7 +390,6 @@ class HyperoptTools: "results_metrics.profit_total_abs", "results_metrics.profit_total", "results_metrics.holding_avg", - "results_metrics.max_drawdown", "results_metrics.max_drawdown_account", "results_metrics.max_drawdown_abs", "loss", @@ -408,7 +408,6 @@ class HyperoptTools: "Total profit", "Profit", "Avg duration", - "max_drawdown", "max_drawdown_account", "max_drawdown_abs", "Objective", @@ -437,9 +436,7 @@ class HyperoptTools: tabulate.PRESERVE_WHITESPACE = True trials = json_normalize(results, max_level=1) - has_account_drawdown = "results_metrics.max_drawdown_account" in trials.columns - - trials = HyperoptTools.prepare_trials_columns(trials, has_account_drawdown) + trials = HyperoptTools.prepare_trials_columns(trials) trials["is_profit"] = False trials.loc[trials["is_initial_point"] | trials["is_random"], "Best"] = "* " @@ -471,23 +468,19 @@ class HyperoptTools: stake_currency = config["stake_currency"] - trials[f"Max Drawdown{' (Acct)' if has_account_drawdown else ''}"] = trials.apply( + trials["Max Drawdown (Acct)"] = trials.apply( lambda x: ( "{} {}".format( fmt_coin(x["max_drawdown_abs"], stake_currency, keep_trailing_zeros=True), - ( - f"({x['max_drawdown_account']:,.2%})" - if has_account_drawdown - else f"({x['max_drawdown']:,.2%})" - ).rjust(10, " "), + (f"({x['max_drawdown_account']:,.2%})").rjust(10, " "), ).rjust(25 + len(stake_currency)) - if x["max_drawdown"] != 0.0 or x["max_drawdown_account"] != 0.0 + if x["max_drawdown_account"] != 0.0 else "--".rjust(25 + len(stake_currency)) ), axis=1, ) - trials = trials.drop(columns=["max_drawdown_abs", "max_drawdown", "max_drawdown_account"]) + trials = trials.drop(columns=["max_drawdown_abs", "max_drawdown_account"]) trials["Profit"] = trials.apply( lambda x: ( diff --git a/freqtrade/optimize/optimize_reports/optimize_reports.py b/freqtrade/optimize/optimize_reports/optimize_reports.py index 1fcf7b271..2ca467eb6 100644 --- a/freqtrade/optimize/optimize_reports/optimize_reports.py +++ b/freqtrade/optimize/optimize_reports/optimize_reports.py @@ -497,29 +497,25 @@ def generate_strategy_stats( } try: - max_drawdown_legacy, _, _, _, _, _ = calculate_max_drawdown( - results, value_col="profit_ratio" - ) - (drawdown_abs, drawdown_start, drawdown_end, high_val, low_val, max_drawdown) = ( - calculate_max_drawdown(results, value_col="profit_abs", starting_balance=start_balance) + drawdown = calculate_max_drawdown( + results, value_col="profit_abs", starting_balance=start_balance ) # max_relative_drawdown = Underwater - (_, _, _, _, _, max_relative_drawdown) = calculate_max_drawdown( + underwater = calculate_max_drawdown( results, value_col="profit_abs", starting_balance=start_balance, relative=True ) strat_stats.update( { - "max_drawdown": max_drawdown_legacy, # Deprecated - do not use - "max_drawdown_account": max_drawdown, - "max_relative_drawdown": max_relative_drawdown, - "max_drawdown_abs": drawdown_abs, - "drawdown_start": drawdown_start.strftime(DATETIME_PRINT_FORMAT), - "drawdown_start_ts": drawdown_start.timestamp() * 1000, - "drawdown_end": drawdown_end.strftime(DATETIME_PRINT_FORMAT), - "drawdown_end_ts": drawdown_end.timestamp() * 1000, - "max_drawdown_low": low_val, - "max_drawdown_high": high_val, + "max_drawdown_account": drawdown.relative_account_drawdown, + "max_relative_drawdown": underwater.relative_account_drawdown, + "max_drawdown_abs": drawdown.drawdown_abs, + "drawdown_start": drawdown.high_date.strftime(DATETIME_PRINT_FORMAT), + "drawdown_start_ts": drawdown.high_date.timestamp() * 1000, + "drawdown_end": drawdown.low_date.strftime(DATETIME_PRINT_FORMAT), + "drawdown_end_ts": drawdown.low_date.timestamp() * 1000, + "max_drawdown_low": drawdown.low_value, + "max_drawdown_high": drawdown.high_value, } ) @@ -529,7 +525,6 @@ def generate_strategy_stats( except ValueError: strat_stats.update( { - "max_drawdown": 0.0, "max_drawdown_account": 0.0, "max_relative_drawdown": 0.0, "max_drawdown_abs": 0.0, diff --git a/freqtrade/persistence/trade_model.py b/freqtrade/persistence/trade_model.py index 28080cc20..7d803ea0f 100644 --- a/freqtrade/persistence/trade_model.py +++ b/freqtrade/persistence/trade_model.py @@ -957,7 +957,24 @@ class LocalTrade: 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. @@ -970,6 +987,13 @@ class LocalTrade: ] ) + 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/freqtrade/plot/plotting.py b/freqtrade/plot/plotting.py index acb3a1999..de0910732 100644 --- a/freqtrade/plot/plotting.py +++ b/freqtrade/plot/plotting.py @@ -179,19 +179,17 @@ def add_max_drawdown( Add scatter points indicating max drawdown """ try: - _, highdate, lowdate, _, _, max_drawdown = calculate_max_drawdown( - trades, starting_balance=starting_balance - ) + drawdown = calculate_max_drawdown(trades, starting_balance=starting_balance) drawdown = go.Scatter( - x=[highdate, lowdate], + x=[drawdown.high_date, drawdown.low_date], y=[ - df_comb.loc[timeframe_to_prev_date(timeframe, highdate), "cum_profit"], - df_comb.loc[timeframe_to_prev_date(timeframe, lowdate), "cum_profit"], + df_comb.loc[timeframe_to_prev_date(timeframe, drawdown.high_date), "cum_profit"], + df_comb.loc[timeframe_to_prev_date(timeframe, drawdown.low_date), "cum_profit"], ], mode="markers", - name=f"Max drawdown {max_drawdown:.2%}", - text=f"Max drawdown {max_drawdown:.2%}", + name=f"Max drawdown {drawdown.relative_account_drawdown:.2%}", + text=f"Max drawdown {drawdown.relative_account_drawdown:.2%}", marker=dict(symbol="square-open", size=9, line=dict(width=2), color="green"), ) fig.add_trace(drawdown, row, 1) diff --git a/freqtrade/plugins/pairlist/MarketCapPairList.py b/freqtrade/plugins/pairlist/MarketCapPairList.py index 0ea351634..709b6100b 100644 --- a/freqtrade/plugins/pairlist/MarketCapPairList.py +++ b/freqtrade/plugins/pairlist/MarketCapPairList.py @@ -8,12 +8,12 @@ import logging from typing import Any, Dict, List from cachetools import TTLCache -from pycoingecko import CoinGeckoAPI from freqtrade.constants import Config from freqtrade.exceptions import OperationalException from freqtrade.exchange.types import Tickers from freqtrade.plugins.pairlist.IPairList import IPairList, PairlistParameter +from freqtrade.util.coin_gecko import FtCoinGeckoApi logger = logging.getLogger(__name__) @@ -44,7 +44,13 @@ class MarketCapPairList(IPairList): self._refresh_period = self._pairlistconfig.get("refresh_period", 86400) self._marketcap_cache: TTLCache = TTLCache(maxsize=1, ttl=self._refresh_period) self._def_candletype = self._config["candle_type_def"] - self._coingecko: CoinGeckoAPI = CoinGeckoAPI() + + _coingecko_config = config.get("coingecko", {}) + + self._coingecko: FtCoinGeckoApi = FtCoinGeckoApi( + api_key=_coingecko_config.get("api_key", ""), + is_demo=_coingecko_config.get("is_demo", True), + ) if self._max_rank > 250: raise OperationalException("This filter only support marketcap rank up to 250.") diff --git a/freqtrade/plugins/protections/max_drawdown_protection.py b/freqtrade/plugins/protections/max_drawdown_protection.py index 3e252185f..a1ba166fa 100644 --- a/freqtrade/plugins/protections/max_drawdown_protection.py +++ b/freqtrade/plugins/protections/max_drawdown_protection.py @@ -59,7 +59,8 @@ class MaxDrawdown(IProtection): # Drawdown is always positive try: # TODO: This should use absolute profit calculation, considering account balance. - drawdown, _, _, _, _, _ = calculate_max_drawdown(trades_df, value_col="close_profit") + drawdown_obj = calculate_max_drawdown(trades_df, value_col="close_profit") + drawdown = drawdown_obj.drawdown_abs except ValueError: return None diff --git a/freqtrade/rpc/api_server/webserver.py b/freqtrade/rpc/api_server/webserver.py index 79909f96e..57f321739 100644 --- a/freqtrade/rpc/api_server/webserver.py +++ b/freqtrade/rpc/api_server/webserver.py @@ -1,5 +1,5 @@ import logging -from ipaddress import IPv4Address +from ipaddress import ip_address from typing import Any, Optional import orjson @@ -180,7 +180,7 @@ class ApiServer(RPCHandler): rest_port = self._config["api_server"]["listen_port"] logger.info(f"Starting HTTP Server at {rest_ip}:{rest_port}") - if not IPv4Address(rest_ip).is_loopback and not running_in_docker(): + if not ip_address(rest_ip).is_loopback and not running_in_docker(): logger.warning("SECURITY WARNING - Local Rest Server listening to external connections") logger.warning( "SECURITY WARNING - This is insecure please set to your loopback," diff --git a/freqtrade/rpc/fiat_convert.py b/freqtrade/rpc/fiat_convert.py index 96758d296..20f8df468 100644 --- a/freqtrade/rpc/fiat_convert.py +++ b/freqtrade/rpc/fiat_convert.py @@ -5,14 +5,14 @@ e.g BTC to USD import logging from datetime import datetime -from typing import Dict, List +from typing import Any, Dict, List from cachetools import TTLCache -from pycoingecko import CoinGeckoAPI from requests.exceptions import RequestException -from freqtrade.constants import SUPPORTED_FIAT +from freqtrade.constants import SUPPORTED_FIAT, Config from freqtrade.mixins.logging_mixin import LoggingMixin +from freqtrade.util.coin_gecko import FtCoinGeckoApi logger = logging.getLogger(__name__) @@ -40,28 +40,28 @@ class CryptoToFiatConverter(LoggingMixin): """ __instance = None - _coingecko: CoinGeckoAPI = None + _coinlistings: List[Dict] = [] _backoff: float = 0.0 - def __new__(cls): + def __new__(cls, *args: Any, **kwargs: Any) -> Any: """ - This class is a singleton - cannot be instantiated twice. + Singleton pattern to ensure only one instance is created. """ - if CryptoToFiatConverter.__instance is None: - CryptoToFiatConverter.__instance = object.__new__(cls) - try: - # Limit retires to 1 (0 and 1) - # otherwise we risk bot impact if coingecko is down. - CryptoToFiatConverter._coingecko = CoinGeckoAPI(retries=1) - except BaseException: - CryptoToFiatConverter._coingecko = None - return CryptoToFiatConverter.__instance + if not cls.__instance: + cls.__instance = super().__new__(cls) + return cls.__instance - def __init__(self) -> None: + def __init__(self, config: Config) -> None: # Timeout: 6h self._pair_price: TTLCache = TTLCache(maxsize=500, ttl=6 * 60 * 60) + _coingecko_config = config.get("coingecko", {}) + self._coingecko = FtCoinGeckoApi( + api_key=_coingecko_config.get("api_key", ""), + is_demo=_coingecko_config.get("is_demo", True), + retries=1, + ) LoggingMixin.__init__(self, logger, 3600) self._load_cryptomap() diff --git a/freqtrade/rpc/rpc.py b/freqtrade/rpc/rpc.py index bcc262194..4cafa12ad 100644 --- a/freqtrade/rpc/rpc.py +++ b/freqtrade/rpc/rpc.py @@ -19,7 +19,7 @@ from freqtrade import __version__ from freqtrade.configuration.timerange import TimeRange from freqtrade.constants import CANCEL_REASON, DEFAULT_DATAFRAME_COLUMNS, Config from freqtrade.data.history import load_data -from freqtrade.data.metrics import calculate_expectancy, calculate_max_drawdown +from freqtrade.data.metrics import DrawDownResult, calculate_expectancy, calculate_max_drawdown from freqtrade.enums import ( CandleType, ExitCheckTuple, @@ -107,7 +107,7 @@ class RPC: self._freqtrade = freqtrade self._config: Config = freqtrade.config if self._config.get("fiat_display_currency"): - self._fiat_converter = CryptoToFiatConverter() + self._fiat_converter = CryptoToFiatConverter(self._config) @staticmethod def _rpc_show_config( @@ -592,21 +592,10 @@ class RPC: expectancy, expectancy_ratio = calculate_expectancy(trades_df) - max_drawdown_abs = 0.0 - max_drawdown = 0.0 - drawdown_start: Optional[datetime] = None - drawdown_end: Optional[datetime] = None - dd_high_val = dd_low_val = 0.0 + drawdown = DrawDownResult() if len(trades_df) > 0: try: - ( - max_drawdown_abs, - drawdown_start, - drawdown_end, - dd_high_val, - dd_low_val, - max_drawdown, - ) = calculate_max_drawdown( + drawdown = calculate_max_drawdown( trades_df, value_col="profit_abs", date_col="close_date_dt", @@ -663,14 +652,14 @@ class RPC: "winrate": winrate, "expectancy": expectancy, "expectancy_ratio": expectancy_ratio, - "max_drawdown": max_drawdown, - "max_drawdown_abs": max_drawdown_abs, - "max_drawdown_start": format_date(drawdown_start), - "max_drawdown_start_timestamp": dt_ts_def(drawdown_start), - "max_drawdown_end": format_date(drawdown_end), - "max_drawdown_end_timestamp": dt_ts_def(drawdown_end), - "drawdown_high": dd_high_val, - "drawdown_low": dd_low_val, + "max_drawdown": drawdown.relative_account_drawdown, + "max_drawdown_abs": drawdown.drawdown_abs, + "max_drawdown_start": format_date(drawdown.high_date), + "max_drawdown_start_timestamp": dt_ts_def(drawdown.high_date), + "max_drawdown_end": format_date(drawdown.low_date), + "max_drawdown_end_timestamp": dt_ts_def(drawdown.low_date), + "drawdown_high": drawdown.high_value, + "drawdown_low": drawdown.low_value, "trading_volume": trading_volume, "bot_start_timestamp": dt_ts_def(bot_start, 0), "bot_start_date": format_date(bot_start), diff --git a/freqtrade/templates/base_config.json.j2 b/freqtrade/templates/base_config.json.j2 index caa27a69e..4956cf056 100644 --- a/freqtrade/templates/base_config.json.j2 +++ b/freqtrade/templates/base_config.json.j2 @@ -10,7 +10,8 @@ "stake_currency": "{{ stake_currency }}", "stake_amount": {{ stake_amount }}, "tradable_balance_ratio": 0.99, - "fiat_display_currency": "{{ fiat_display_currency }}",{{ ('\n "timeframe": "' + timeframe + '",') if timeframe else '' }} +{{- ('\n "fiat_display_currency": "' + fiat_display_currency + '",') if fiat_display_currency else ''}} +{{- ('\n "timeframe": "' + timeframe + '",') if timeframe else '' }} "dry_run": {{ dry_run | lower }}, "dry_run_wallet": 1000, "cancel_open_orders_on_exit": false, diff --git a/freqtrade/util/coin_gecko.py b/freqtrade/util/coin_gecko.py new file mode 100644 index 000000000..47b80875b --- /dev/null +++ b/freqtrade/util/coin_gecko.py @@ -0,0 +1,26 @@ +from pycoingecko import CoinGeckoAPI + + +class FtCoinGeckoApi(CoinGeckoAPI): + """ + Simple wrapper around pycoingecko's api to support Demo API keys. + + """ + + __API_URL_BASE = "https://api.coingecko.com/api/v3/" + __PRO_API_URL_BASE = "https://pro-api.coingecko.com/api/v3/" + _api_key: str = "" + + def __init__(self, api_key: str = "", *, is_demo=True, retries=5): + super().__init__(retries=retries) + # Doint' pass api_key to parent, instead set the header on the session directly + self._api_key = api_key + + if api_key and not is_demo: + self.api_base_url = self.__PRO_API_URL_BASE + self.session.params.update({"x_cg_pro_api_key": api_key}) + else: + # Use demo api key + self.api_base_url = self.__API_URL_BASE + if api_key: + self.session.params.update({"x_cg_demo_api_key": api_key}) diff --git a/ft_client/freqtrade_client/__init__.py b/ft_client/freqtrade_client/__init__.py index ac0b906b4..9ede4dd12 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 56def4059..35406c1d0 100644 --- a/ft_client/requirements.txt +++ b/ft_client/requirements.txt @@ -1,3 +1,3 @@ # Requirements for freqtrade client library -requests==2.31.0 -python-rapidjson==1.16 +requests==2.32.3 +python-rapidjson==1.17 diff --git a/mkdocs.yml b/mkdocs.yml index 0e80228a1..6a8e796dc 100644 --- a/mkdocs.yml +++ b/mkdocs.yml @@ -1,5 +1,6 @@ site_name: Freqtrade -site_url: https://www.freqtrade.io/en/latest/ +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 @@ -49,9 +50,9 @@ nav: - Advanced Hyperopt: advanced-hyperopt.md - Advanced Orderflow: advanced-orderflow.md - Producer/Consumer mode: producer-consumer.md + - SQL Cheat-sheet: sql_cheatsheet.md - Edge Positioning: edge.md - FAQ: faq.md - - SQL Cheat-sheet: sql_cheatsheet.md - Strategy migration: strategy_migration.md - Updating Freqtrade: updating.md - Deprecated Features: deprecated.md diff --git a/pyproject.toml b/pyproject.toml index cfdbb3ece..0c9222530 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 29e11c42b..b4865f067 100644 --- a/requirements-dev.txt +++ b/requirements-dev.txt @@ -6,12 +6,12 @@ -r requirements-freqai-rl.txt -r docs/requirements-docs.txt -coveralls==4.0.0 -ruff==0.4.4 +coveralls==4.0.1 +ruff==0.4.7 mypy==1.10.0 pre-commit==3.7.1 -pytest==8.2.0 -pytest-asyncio==0.23.6 +pytest==8.2.1 +pytest-asyncio==0.23.7 pytest-cov==5.0.0 pytest-mock==3.14.0 pytest-random-order==1.1.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 f508e42c3..cdc32e11b 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 54643d697..99d4ee5c6 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 c006bba9a..9f9da2cec 100644 --- a/requirements.txt +++ b/requirements.txt @@ -1,20 +1,22 @@ numpy==1.26.4 pandas==2.2.2 +bottleneck==1.3.8 +numexpr==2.10.0 pandas-ta==0.3.14b -ccxt==4.3.21 +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.31.0 +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 @@ -22,13 +24,13 @@ jinja2==3.1.4 tables==3.9.1 joblib==1.4.2 rich==13.7.1 -pyarrow==16.0.0; platform_machine != 'armv7l' +pyarrow==16.1.0; platform_machine != 'armv7l' # find first, C search in arrays py_find_1st==1.1.6 # Load ticker files 30% faster -python-rapidjson==1.16 +python-rapidjson==1.17 # Properly format api responses orjson==3.10.3 @@ -37,8 +39,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 @@ -53,7 +55,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 000000000..8647bea94 --- /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/setup.py b/setup.py index 7a7fc99df..8865f46be 100644 --- a/setup.py +++ b/setup.py @@ -69,7 +69,7 @@ setup( ], install_requires=[ # from requirements.txt - "ccxt>=4.2.47", + "ccxt>=4.3.24", "SQLAlchemy>=2.0.6", "python-telegram-bot>=20.1", "humanize>=4.0.0", diff --git a/setup.sh b/setup.sh index 68374a689..18f7682d8 100755 --- a/setup.sh +++ b/setup.sh @@ -25,7 +25,7 @@ function check_installed_python() { exit 2 fi - for v in 11 10 9 + for v in 12 11 10 9 do PYTHON="python3.${v}" which $PYTHON @@ -277,7 +277,7 @@ function install() { install_redhat else echo "This script does not support your OS." - echo "If you have Python version 3.9 - 3.11, pip, virtualenv, ta-lib you can continue." + echo "If you have Python version 3.9 - 3.12, pip, virtualenv, ta-lib you can continue." echo "Wait 10 seconds to continue the next install steps or use ctrl+c to interrupt this shell." sleep 10 fi diff --git a/tests/commands/test_commands.py b/tests/commands/test_commands.py index 77cabc51b..c98c6302e 100644 --- a/tests/commands/test_commands.py +++ b/tests/commands/test_commands.py @@ -54,6 +54,7 @@ from tests.conftest import ( patch_exchange, patched_configuration_load_config_file, ) +from tests.conftest_hyperopt import hyperopt_test_result from tests.conftest_trades import MOCK_TRADE_COUNT @@ -1137,7 +1138,8 @@ def test_start_test_pairlist(mocker, caplog, tickers, default_conf, capsys): pytest.fail(f"Expected well formed JSON, but failed to parse: {captured.out}") -def test_hyperopt_list(mocker, capsys, caplog, saved_hyperopt_results, tmp_path): +def test_hyperopt_list(mocker, capsys, caplog, tmp_path): + saved_hyperopt_results = hyperopt_test_result() csv_file = tmp_path / "test.csv" mocker.patch( "freqtrade.optimize.hyperopt_tools.HyperoptTools._test_hyperopt_results_exist", @@ -1507,7 +1509,8 @@ def test_hyperopt_list(mocker, capsys, caplog, saved_hyperopt_results, tmp_path) csv_file.unlink() -def test_hyperopt_show(mocker, capsys, saved_hyperopt_results): +def test_hyperopt_show(mocker, capsys): + saved_hyperopt_results = hyperopt_test_result() mocker.patch( "freqtrade.optimize.hyperopt_tools.HyperoptTools._test_hyperopt_results_exist", return_value=True, diff --git a/tests/conftest.py b/tests/conftest.py index a5c1f1437..464f7b30b 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)) @@ -539,7 +539,7 @@ def patch_coingecko(mocker) -> None: ] ) mocker.patch.multiple( - "freqtrade.rpc.fiat_convert.CoinGeckoAPI", + "freqtrade.rpc.fiat_convert.FtCoinGeckoApi", get_price=tickermock, get_coins_list=listmock, ) @@ -2708,1004 +2708,6 @@ def open_trade_usdt(): return trade -@pytest.fixture -def saved_hyperopt_results(): - hyperopt_res = [ - { - "loss": 0.4366182531161519, - "params_dict": { - "mfi-value": 15, - "fastd-value": 20, - "adx-value": 25, - "rsi-value": 28, - "mfi-enabled": False, - "fastd-enabled": True, - "adx-enabled": True, - "rsi-enabled": True, - "trigger": "macd_cross_signal", - "sell-mfi-value": 88, - "sell-fastd-value": 97, - "sell-adx-value": 51, - "sell-rsi-value": 67, - "sell-mfi-enabled": False, - "sell-fastd-enabled": False, - "sell-adx-enabled": True, - "sell-rsi-enabled": True, - "sell-trigger": "sell-bb_upper", - "roi_t1": 1190, - "roi_t2": 541, - "roi_t3": 408, - "roi_p1": 0.026035863879169705, - "roi_p2": 0.12508730043628782, - "roi_p3": 0.27766427921605896, - "stoploss": -0.2562930402099556, - }, # noqa: E501 - "params_details": { - "buy": { - "mfi-value": 15, - "fastd-value": 20, - "adx-value": 25, - "rsi-value": 28, - "mfi-enabled": False, - "fastd-enabled": True, - "adx-enabled": True, - "rsi-enabled": True, - "trigger": "macd_cross_signal", - }, - "sell": { - "sell-mfi-value": 88, - "sell-fastd-value": 97, - "sell-adx-value": 51, - "sell-rsi-value": 67, - "sell-mfi-enabled": False, - "sell-fastd-enabled": False, - "sell-adx-enabled": True, - "sell-rsi-enabled": True, - "sell-trigger": "sell-bb_upper", - }, - "roi": { - 0: 0.4287874435315165, - 408: 0.15112316431545753, - 949: 0.026035863879169705, - 2139: 0, - }, - "stoploss": {"stoploss": -0.2562930402099556}, - }, # noqa: E501 - "results_metrics": { - "total_trades": 2, - "trade_count_long": 2, - "trade_count_short": 0, - "wins": 0, - "draws": 0, - "losses": 2, - "profit_mean": -0.01254995, - "profit_median": -0.012222, - "profit_total": -0.00125625, - "profit_total_abs": -2.50999, - "max_drawdown": 0.23, - "max_drawdown_abs": -0.00125625, - "holding_avg": timedelta(minutes=3930.0), - "stake_currency": "BTC", - "strategy_name": "SampleStrategy", - }, # noqa: E501 - "results_explanation": " 2 trades. Avg profit -1.25%. Total profit -0.00125625 BTC ( -2.51Σ%). Avg duration 3930.0 min.", # noqa: E501 - "total_profit": -0.00125625, - "current_epoch": 1, - "is_initial_point": True, - "is_random": False, - "is_best": True, - }, - { - "loss": 20.0, - "params_dict": { - "mfi-value": 17, - "fastd-value": 38, - "adx-value": 48, - "rsi-value": 22, - "mfi-enabled": True, - "fastd-enabled": False, - "adx-enabled": True, - "rsi-enabled": True, - "trigger": "macd_cross_signal", - "sell-mfi-value": 96, - "sell-fastd-value": 68, - "sell-adx-value": 63, - "sell-rsi-value": 81, - "sell-mfi-enabled": False, - "sell-fastd-enabled": True, - "sell-adx-enabled": True, - "sell-rsi-enabled": True, - "sell-trigger": "sell-sar_reversal", - "roi_t1": 334, - "roi_t2": 683, - "roi_t3": 140, - "roi_p1": 0.06403981740598495, - "roi_p2": 0.055519840060645045, - "roi_p3": 0.3253712811342459, - "stoploss": -0.338070047333259, - }, # noqa: E501 - "params_details": { - "buy": { - "mfi-value": 17, - "fastd-value": 38, - "adx-value": 48, - "rsi-value": 22, - "mfi-enabled": True, - "fastd-enabled": False, - "adx-enabled": True, - "rsi-enabled": True, - "trigger": "macd_cross_signal", - }, # noqa: E501 - "sell": { - "sell-mfi-value": 96, - "sell-fastd-value": 68, - "sell-adx-value": 63, - "sell-rsi-value": 81, - "sell-mfi-enabled": False, - "sell-fastd-enabled": True, - "sell-adx-enabled": True, - "sell-rsi-enabled": True, - "sell-trigger": "sell-sar_reversal", - }, # noqa: E501 - "roi": { - 0: 0.4449309386008759, - 140: 0.11955965746663, - 823: 0.06403981740598495, - 1157: 0, - }, # noqa: E501 - "stoploss": {"stoploss": -0.338070047333259}, - }, - "results_metrics": { - "total_trades": 1, - "trade_count_long": 1, - "trade_count_short": 0, - "wins": 0, - "draws": 0, - "losses": 1, - "profit_mean": 0.012357, - "profit_median": -0.012222, - "profit_total": 6.185e-05, - "profit_total_abs": 0.12357, - "max_drawdown": 0.23, - "max_drawdown_abs": -0.00125625, - "holding_avg": timedelta(minutes=1200.0), - }, # noqa: E501 - "results_explanation": " 1 trades. Avg profit 0.12%. Total profit 0.00006185 BTC ( 0.12Σ%). Avg duration 1200.0 min.", # noqa: E501 - "total_profit": 6.185e-05, - "current_epoch": 2, - "is_initial_point": True, - "is_random": False, - "is_best": False, - }, - { - "loss": 14.241196856510731, - "params_dict": { - "mfi-value": 25, - "fastd-value": 16, - "adx-value": 29, - "rsi-value": 20, - "mfi-enabled": False, - "fastd-enabled": False, - "adx-enabled": False, - "rsi-enabled": False, - "trigger": "macd_cross_signal", - "sell-mfi-value": 98, - "sell-fastd-value": 72, - "sell-adx-value": 51, - "sell-rsi-value": 82, - "sell-mfi-enabled": True, - "sell-fastd-enabled": True, - "sell-adx-enabled": True, - "sell-rsi-enabled": True, - "sell-trigger": "sell-macd_cross_signal", - "roi_t1": 889, - "roi_t2": 533, - "roi_t3": 263, - "roi_p1": 0.04759065393663096, - "roi_p2": 0.1488819964638463, - "roi_p3": 0.4102801822104605, - "stoploss": -0.05394588767607611, - }, # noqa: E501 - "params_details": { - "buy": { - "mfi-value": 25, - "fastd-value": 16, - "adx-value": 29, - "rsi-value": 20, - "mfi-enabled": False, - "fastd-enabled": False, - "adx-enabled": False, - "rsi-enabled": False, - "trigger": "macd_cross_signal", - }, - "sell": { - "sell-mfi-value": 98, - "sell-fastd-value": 72, - "sell-adx-value": 51, - "sell-rsi-value": 82, - "sell-mfi-enabled": True, - "sell-fastd-enabled": True, - "sell-adx-enabled": True, - "sell-rsi-enabled": True, - "sell-trigger": "sell-macd_cross_signal", - }, - "roi": { - 0: 0.6067528326109377, - 263: 0.19647265040047726, - 796: 0.04759065393663096, - 1685: 0, - }, - "stoploss": {"stoploss": -0.05394588767607611}, - }, # noqa: E501 - "results_metrics": { - "total_trades": 621, - "trade_count_long": 621, - "trade_count_short": 0, - "wins": 320, - "draws": 0, - "losses": 301, - "profit_mean": -0.043883302093397747, - "profit_median": -0.012222, - "profit_total": -0.13639474, - "profit_total_abs": -272.515306, - "max_drawdown": 0.25, - "max_drawdown_abs": -272.515306, - "holding_avg": timedelta(minutes=1691.207729468599), - }, # noqa: E501 - "results_explanation": " 621 trades. Avg profit -0.44%. Total profit -0.13639474 BTC (-272.52Σ%). Avg duration 1691.2 min.", # noqa: E501 - "total_profit": -0.13639474, - "current_epoch": 3, - "is_initial_point": True, - "is_random": False, - "is_best": False, - }, - { - "loss": 100000, - "params_dict": { - "mfi-value": 13, - "fastd-value": 35, - "adx-value": 39, - "rsi-value": 29, - "mfi-enabled": True, - "fastd-enabled": False, - "adx-enabled": False, - "rsi-enabled": True, - "trigger": "macd_cross_signal", - "sell-mfi-value": 87, - "sell-fastd-value": 54, - "sell-adx-value": 63, - "sell-rsi-value": 93, - "sell-mfi-enabled": False, - "sell-fastd-enabled": True, - "sell-adx-enabled": True, - "sell-rsi-enabled": True, - "sell-trigger": "sell-bb_upper", - "roi_t1": 1402, - "roi_t2": 676, - "roi_t3": 215, - "roi_p1": 0.06264755784937427, - "roi_p2": 0.14258587851894644, - "roi_p3": 0.20671291201040828, - "stoploss": -0.11818343570194478, - }, # noqa: E501 - "params_details": { - "buy": { - "mfi-value": 13, - "fastd-value": 35, - "adx-value": 39, - "rsi-value": 29, - "mfi-enabled": True, - "fastd-enabled": False, - "adx-enabled": False, - "rsi-enabled": True, - "trigger": "macd_cross_signal", - }, - "sell": { - "sell-mfi-value": 87, - "sell-fastd-value": 54, - "sell-adx-value": 63, - "sell-rsi-value": 93, - "sell-mfi-enabled": False, - "sell-fastd-enabled": True, - "sell-adx-enabled": True, - "sell-rsi-enabled": True, - "sell-trigger": "sell-bb_upper", - }, - "roi": { - 0: 0.411946348378729, - 215: 0.2052334363683207, - 891: 0.06264755784937427, - 2293: 0, - }, - "stoploss": {"stoploss": -0.11818343570194478}, - }, # noqa: E501 - "results_metrics": { - "total_trades": 0, - "trade_count_long": 0, - "trade_count_short": 0, - "wins": 0, - "draws": 0, - "losses": 0, - "profit_mean": None, - "profit_median": None, - "profit_total": 0, - "profit": 0.0, - "holding_avg": timedelta(), - }, # noqa: E501 - "results_explanation": " 0 trades. Avg profit nan%. Total profit 0.00000000 BTC ( 0.00Σ%). Avg duration nan min.", # noqa: E501 - "total_profit": 0, - "current_epoch": 4, - "is_initial_point": True, - "is_random": False, - "is_best": False, # noqa: E501 - }, - { - "loss": 0.22195522184191518, - "params_dict": { - "mfi-value": 17, - "fastd-value": 21, - "adx-value": 38, - "rsi-value": 33, - "mfi-enabled": True, - "fastd-enabled": False, - "adx-enabled": True, - "rsi-enabled": False, - "trigger": "macd_cross_signal", - "sell-mfi-value": 87, - "sell-fastd-value": 82, - "sell-adx-value": 78, - "sell-rsi-value": 69, - "sell-mfi-enabled": True, - "sell-fastd-enabled": False, - "sell-adx-enabled": True, - "sell-rsi-enabled": False, - "sell-trigger": "sell-macd_cross_signal", - "roi_t1": 1269, - "roi_t2": 601, - "roi_t3": 444, - "roi_p1": 0.07280999507931168, - "roi_p2": 0.08946698095898986, - "roi_p3": 0.1454876733325284, - "stoploss": -0.18181041180901014, - }, # noqa: E501 - "params_details": { - "buy": { - "mfi-value": 17, - "fastd-value": 21, - "adx-value": 38, - "rsi-value": 33, - "mfi-enabled": True, - "fastd-enabled": False, - "adx-enabled": True, - "rsi-enabled": False, - "trigger": "macd_cross_signal", - }, - "sell": { - "sell-mfi-value": 87, - "sell-fastd-value": 82, - "sell-adx-value": 78, - "sell-rsi-value": 69, - "sell-mfi-enabled": True, - "sell-fastd-enabled": False, - "sell-adx-enabled": True, - "sell-rsi-enabled": False, - "sell-trigger": "sell-macd_cross_signal", - }, - "roi": { - 0: 0.3077646493708299, - 444: 0.16227697603830155, - 1045: 0.07280999507931168, - 2314: 0, - }, - "stoploss": {"stoploss": -0.18181041180901014}, - }, # noqa: E501 - "results_metrics": { - "total_trades": 14, - "trade_count_long": 14, - "trade_count_short": 0, - "wins": 6, - "draws": 0, - "losses": 8, - "profit_mean": -0.003539515, - "profit_median": -0.012222, - "profit_total": -0.002480140000000001, - "profit_total_abs": -4.955321, - "max_drawdown": 0.34, - "max_drawdown_abs": -4.955321, - "holding_avg": timedelta(minutes=3402.8571428571427), - }, # noqa: E501 - "results_explanation": " 14 trades. Avg profit -0.35%. Total profit -0.00248014 BTC ( -4.96Σ%). Avg duration 3402.9 min.", # noqa: E501 - "total_profit": -0.002480140000000001, - "current_epoch": 5, - "is_initial_point": True, - "is_random": False, - "is_best": True, - }, - { - "loss": 0.545315889154162, - "params_dict": { - "mfi-value": 22, - "fastd-value": 43, - "adx-value": 46, - "rsi-value": 20, - "mfi-enabled": False, - "fastd-enabled": False, - "adx-enabled": True, - "rsi-enabled": True, - "trigger": "bb_lower", - "sell-mfi-value": 87, - "sell-fastd-value": 65, - "sell-adx-value": 94, - "sell-rsi-value": 63, - "sell-mfi-enabled": False, - "sell-fastd-enabled": True, - "sell-adx-enabled": True, - "sell-rsi-enabled": True, - "sell-trigger": "sell-macd_cross_signal", - "roi_t1": 319, - "roi_t2": 556, - "roi_t3": 216, - "roi_p1": 0.06251955472249589, - "roi_p2": 0.11659519602202795, - "roi_p3": 0.0953744132197762, - "stoploss": -0.024551752215582423, - }, # noqa: E501 - "params_details": { - "buy": { - "mfi-value": 22, - "fastd-value": 43, - "adx-value": 46, - "rsi-value": 20, - "mfi-enabled": False, - "fastd-enabled": False, - "adx-enabled": True, - "rsi-enabled": True, - "trigger": "bb_lower", - }, - "sell": { - "sell-mfi-value": 87, - "sell-fastd-value": 65, - "sell-adx-value": 94, - "sell-rsi-value": 63, - "sell-mfi-enabled": False, - "sell-fastd-enabled": True, - "sell-adx-enabled": True, - "sell-rsi-enabled": True, - "sell-trigger": "sell-macd_cross_signal", - }, - "roi": { - 0: 0.2744891639643, - 216: 0.17911475074452382, - 772: 0.06251955472249589, - 1091: 0, - }, - "stoploss": {"stoploss": -0.024551752215582423}, - }, # noqa: E501 - "results_metrics": { - "total_trades": 39, - "trade_count_long": 39, - "trade_count_short": 0, - "wins": 20, - "draws": 0, - "losses": 19, - "profit_mean": -0.0021400679487179478, - "profit_median": -0.012222, - "profit_total": -0.0041773, - "profit_total_abs": -8.346264999999997, - "max_drawdown": 0.45, - "max_drawdown_abs": -4.955321, - "holding_avg": timedelta(minutes=636.9230769230769), - }, # noqa: E501 - "results_explanation": " 39 trades. Avg profit -0.21%. Total profit -0.00417730 BTC ( -8.35Σ%). Avg duration 636.9 min.", # noqa: E501 - "total_profit": -0.0041773, - "current_epoch": 6, - "is_initial_point": True, - "is_random": False, - "is_best": False, - }, - { - "loss": 4.713497421432944, - "params_dict": { - "mfi-value": 13, - "fastd-value": 41, - "adx-value": 21, - "rsi-value": 29, - "mfi-enabled": False, - "fastd-enabled": True, - "adx-enabled": False, - "rsi-enabled": False, - "trigger": "bb_lower", - "sell-mfi-value": 99, - "sell-fastd-value": 60, - "sell-adx-value": 81, - "sell-rsi-value": 69, - "sell-mfi-enabled": True, - "sell-fastd-enabled": True, - "sell-adx-enabled": True, - "sell-rsi-enabled": False, - "sell-trigger": "sell-macd_cross_signal", - "roi_t1": 771, - "roi_t2": 620, - "roi_t3": 145, - "roi_p1": 0.0586919200378493, - "roi_p2": 0.04984118697312542, - "roi_p3": 0.37521058680247044, - "stoploss": -0.14613268022709905, - }, # noqa: E501 - "params_details": { - "buy": { - "mfi-value": 13, - "fastd-value": 41, - "adx-value": 21, - "rsi-value": 29, - "mfi-enabled": False, - "fastd-enabled": True, - "adx-enabled": False, - "rsi-enabled": False, - "trigger": "bb_lower", - }, - "sell": { - "sell-mfi-value": 99, - "sell-fastd-value": 60, - "sell-adx-value": 81, - "sell-rsi-value": 69, - "sell-mfi-enabled": True, - "sell-fastd-enabled": True, - "sell-adx-enabled": True, - "sell-rsi-enabled": False, - "sell-trigger": "sell-macd_cross_signal", - }, - "roi": { - 0: 0.4837436938134452, - 145: 0.10853310701097472, - 765: 0.0586919200378493, - 1536: 0, - }, # noqa: E501 - "stoploss": {"stoploss": -0.14613268022709905}, - }, # noqa: E501 - "results_metrics": { - "total_trades": 318, - "trade_count_long": 318, - "trade_count_short": 0, - "wins": 100, - "draws": 0, - "losses": 218, - "profit_mean": -0.0039833954716981146, - "profit_median": -0.012222, - "profit_total": -0.06339929, - "profit_total_abs": -126.67197600000004, - "max_drawdown": 0.50, - "max_drawdown_abs": -200.955321, - "holding_avg": timedelta(minutes=3140.377358490566), - }, # noqa: E501 - "results_explanation": " 318 trades. Avg profit -0.40%. Total profit -0.06339929 BTC (-126.67Σ%). Avg duration 3140.4 min.", # noqa: E501 - "total_profit": -0.06339929, - "current_epoch": 7, - "is_initial_point": True, - "is_random": False, - "is_best": False, - }, - { - "loss": 20.0, # noqa: E501 - "params_dict": { - "mfi-value": 24, - "fastd-value": 43, - "adx-value": 33, - "rsi-value": 20, - "mfi-enabled": False, - "fastd-enabled": True, - "adx-enabled": True, - "rsi-enabled": True, - "trigger": "sar_reversal", - "sell-mfi-value": 89, - "sell-fastd-value": 74, - "sell-adx-value": 70, - "sell-rsi-value": 70, - "sell-mfi-enabled": False, - "sell-fastd-enabled": False, - "sell-adx-enabled": False, - "sell-rsi-enabled": True, - "sell-trigger": "sell-sar_reversal", - "roi_t1": 1149, - "roi_t2": 375, - "roi_t3": 289, - "roi_p1": 0.05571820757172588, - "roi_p2": 0.0606240398618907, - "roi_p3": 0.1729012220156157, - "stoploss": -0.1588514289110401, - }, # noqa: E501 - "params_details": { - "buy": { - "mfi-value": 24, - "fastd-value": 43, - "adx-value": 33, - "rsi-value": 20, - "mfi-enabled": False, - "fastd-enabled": True, - "adx-enabled": True, - "rsi-enabled": True, - "trigger": "sar_reversal", - }, - "sell": { - "sell-mfi-value": 89, - "sell-fastd-value": 74, - "sell-adx-value": 70, - "sell-rsi-value": 70, - "sell-mfi-enabled": False, - "sell-fastd-enabled": False, - "sell-adx-enabled": False, - "sell-rsi-enabled": True, - "sell-trigger": "sell-sar_reversal", - }, - "roi": { - 0: 0.2892434694492323, - 289: 0.11634224743361658, - 664: 0.05571820757172588, - 1813: 0, - }, - "stoploss": {"stoploss": -0.1588514289110401}, - }, # noqa: E501 - "results_metrics": { - "total_trades": 1, - "trade_count_long": 1, - "trade_count_short": 0, - "wins": 0, - "draws": 1, - "losses": 0, - "profit_mean": 0.0, - "profit_median": 0.0, - "profit_total": 0.0, - "profit_total_abs": 0.0, - "max_drawdown": 0.0, - "max_drawdown_abs": 0.52, - "holding_avg": timedelta(minutes=5340.0), - }, # noqa: E501 - "results_explanation": " 1 trades. Avg profit 0.00%. Total profit 0.00000000 BTC ( 0.00Σ%). Avg duration 5340.0 min.", # noqa: E501 - "total_profit": 0.0, - "current_epoch": 8, - "is_initial_point": True, - "is_random": False, - "is_best": False, - }, - { - "loss": 2.4731817780991223, - "params_dict": { - "mfi-value": 22, - "fastd-value": 20, - "adx-value": 29, - "rsi-value": 40, - "mfi-enabled": False, - "fastd-enabled": False, - "adx-enabled": False, - "rsi-enabled": False, - "trigger": "sar_reversal", - "sell-mfi-value": 97, - "sell-fastd-value": 65, - "sell-adx-value": 81, - "sell-rsi-value": 64, - "sell-mfi-enabled": True, - "sell-fastd-enabled": True, - "sell-adx-enabled": True, - "sell-rsi-enabled": True, - "sell-trigger": "sell-bb_upper", - "roi_t1": 1012, - "roi_t2": 584, - "roi_t3": 422, - "roi_p1": 0.036764323603472565, - "roi_p2": 0.10335480573205287, - "roi_p3": 0.10322347377503042, - "stoploss": -0.2780610808108503, - }, # noqa: E501 - "params_details": { - "buy": { - "mfi-value": 22, - "fastd-value": 20, - "adx-value": 29, - "rsi-value": 40, - "mfi-enabled": False, - "fastd-enabled": False, - "adx-enabled": False, - "rsi-enabled": False, - "trigger": "sar_reversal", - }, - "sell": { - "sell-mfi-value": 97, - "sell-fastd-value": 65, - "sell-adx-value": 81, - "sell-rsi-value": 64, - "sell-mfi-enabled": True, - "sell-fastd-enabled": True, - "sell-adx-enabled": True, - "sell-rsi-enabled": True, - "sell-trigger": "sell-bb_upper", - }, - "roi": { - 0: 0.2433426031105559, - 422: 0.14011912933552545, - 1006: 0.036764323603472565, - 2018: 0, - }, - "stoploss": {"stoploss": -0.2780610808108503}, - }, # noqa: E501 - "results_metrics": { - "total_trades": 229, - "trade_count_long": 229, - "trade_count_short": 0, - "wins": 150, - "draws": 0, - "losses": 79, - "profit_mean": -0.0038433433624454144, - "profit_median": -0.012222, - "profit_total": -0.044050070000000004, - "profit_total_abs": -88.01256299999999, - "max_drawdown": 0.41, - "max_drawdown_abs": -150.955321, - "holding_avg": timedelta(minutes=6505.676855895196), - }, # noqa: E501 - "results_explanation": " 229 trades. Avg profit -0.38%. Total profit -0.04405007 BTC ( -88.01Σ%). Avg duration 6505.7 min.", # noqa: E501 - "total_profit": -0.044050070000000004, # noqa: E501 - "current_epoch": 9, - "is_initial_point": True, - "is_random": False, - "is_best": False, - }, - { - "loss": -0.2604606005845212, # noqa: E501 - "params_dict": { - "mfi-value": 23, - "fastd-value": 24, - "adx-value": 22, - "rsi-value": 24, - "mfi-enabled": False, - "fastd-enabled": False, - "adx-enabled": False, - "rsi-enabled": True, - "trigger": "macd_cross_signal", - "sell-mfi-value": 97, - "sell-fastd-value": 70, - "sell-adx-value": 64, - "sell-rsi-value": 80, - "sell-mfi-enabled": False, - "sell-fastd-enabled": True, - "sell-adx-enabled": True, - "sell-rsi-enabled": True, - "sell-trigger": "sell-sar_reversal", - "roi_t1": 792, - "roi_t2": 464, - "roi_t3": 215, - "roi_p1": 0.04594053535385903, - "roi_p2": 0.09623192684243963, - "roi_p3": 0.04428219070850663, - "stoploss": -0.16992287161634415, - }, # noqa: E501 - "params_details": { - "buy": { - "mfi-value": 23, - "fastd-value": 24, - "adx-value": 22, - "rsi-value": 24, - "mfi-enabled": False, - "fastd-enabled": False, - "adx-enabled": False, - "rsi-enabled": True, - "trigger": "macd_cross_signal", - }, - "sell": { - "sell-mfi-value": 97, - "sell-fastd-value": 70, - "sell-adx-value": 64, - "sell-rsi-value": 80, - "sell-mfi-enabled": False, - "sell-fastd-enabled": True, - "sell-adx-enabled": True, - "sell-rsi-enabled": True, - "sell-trigger": "sell-sar_reversal", - }, - "roi": { - 0: 0.18645465290480528, - 215: 0.14217246219629864, - 679: 0.04594053535385903, - 1471: 0, - }, - "stoploss": {"stoploss": -0.16992287161634415}, - }, # noqa: E501 - "results_metrics": { - "total_trades": 4, - "trade_count_long": 4, - "trade_count_short": 0, - "wins": 0, - "draws": 0, - "losses": 4, - "profit_mean": 0.001080385, - "profit_median": -0.012222, - "profit_total": 0.00021629, - "profit_total_abs": 0.432154, - "max_drawdown": 0.13, - "max_drawdown_abs": -4.955321, - "holding_avg": timedelta(minutes=2850.0), - }, # noqa: E501 - "results_explanation": " 4 trades. Avg profit 0.11%. Total profit 0.00021629 BTC ( 0.43Σ%). Avg duration 2850.0 min.", # noqa: E501 - "total_profit": 0.00021629, - "current_epoch": 10, - "is_initial_point": True, - "is_random": False, - "is_best": True, - }, - { - "loss": 4.876465945994304, # noqa: E501 - "params_dict": { - "mfi-value": 20, - "fastd-value": 32, - "adx-value": 49, - "rsi-value": 23, - "mfi-enabled": True, - "fastd-enabled": True, - "adx-enabled": False, - "rsi-enabled": False, - "trigger": "bb_lower", - "sell-mfi-value": 75, - "sell-fastd-value": 56, - "sell-adx-value": 61, - "sell-rsi-value": 62, - "sell-mfi-enabled": False, - "sell-fastd-enabled": False, - "sell-adx-enabled": True, - "sell-rsi-enabled": True, - "sell-trigger": "sell-macd_cross_signal", - "roi_t1": 579, - "roi_t2": 614, - "roi_t3": 273, - "roi_p1": 0.05307643172744114, - "roi_p2": 0.1352282078262871, - "roi_p3": 0.1913307406325751, - "stoploss": -0.25728526022513887, - }, # noqa: E501 - "params_details": { - "buy": { - "mfi-value": 20, - "fastd-value": 32, - "adx-value": 49, - "rsi-value": 23, - "mfi-enabled": True, - "fastd-enabled": True, - "adx-enabled": False, - "rsi-enabled": False, - "trigger": "bb_lower", - }, - "sell": { - "sell-mfi-value": 75, - "sell-fastd-value": 56, - "sell-adx-value": 61, - "sell-rsi-value": 62, - "sell-mfi-enabled": False, - "sell-fastd-enabled": False, - "sell-adx-enabled": True, - "sell-rsi-enabled": True, - "sell-trigger": "sell-macd_cross_signal", - }, - "roi": { - 0: 0.3796353801863034, - 273: 0.18830463955372825, - 887: 0.05307643172744114, - 1466: 0, - }, - "stoploss": {"stoploss": -0.25728526022513887}, - }, # noqa: E501 - # New Hyperopt mode! - "results_metrics": { - "total_trades": 117, - "trade_count_long": 117, - "trade_count_short": 0, - "wins": 67, - "draws": 0, - "losses": 50, - "profit_mean": -0.012698609145299145, - "profit_median": -0.012222, - "profit_total": -0.07436117, - "profit_total_abs": -148.573727, - "max_drawdown": 0.52, - "max_drawdown_abs": -224.955321, - "holding_avg": timedelta(minutes=4282.5641025641025), - }, # noqa: E501 - "results_explanation": " 117 trades. Avg profit -1.27%. Total profit -0.07436117 BTC (-148.57Σ%). Avg duration 4282.6 min.", # noqa: E501 - "total_profit": -0.07436117, - "current_epoch": 11, - "is_initial_point": True, - "is_random": False, - "is_best": False, - }, - { - "loss": 100000, - "params_dict": { - "mfi-value": 10, - "fastd-value": 36, - "adx-value": 31, - "rsi-value": 22, - "mfi-enabled": True, - "fastd-enabled": True, - "adx-enabled": True, - "rsi-enabled": False, - "trigger": "sar_reversal", - "sell-mfi-value": 80, - "sell-fastd-value": 71, - "sell-adx-value": 60, - "sell-rsi-value": 85, - "sell-mfi-enabled": False, - "sell-fastd-enabled": False, - "sell-adx-enabled": True, - "sell-rsi-enabled": True, - "sell-trigger": "sell-bb_upper", - "roi_t1": 1156, - "roi_t2": 581, - "roi_t3": 408, - "roi_p1": 0.06860454019988212, - "roi_p2": 0.12473718444931989, - "roi_p3": 0.2896360635226823, - "stoploss": -0.30889015124682806, - }, # noqa: E501 - "params_details": { - "buy": { - "mfi-value": 10, - "fastd-value": 36, - "adx-value": 31, - "rsi-value": 22, - "mfi-enabled": True, - "fastd-enabled": True, - "adx-enabled": True, - "rsi-enabled": False, - "trigger": "sar_reversal", - }, - "sell": { - "sell-mfi-value": 80, - "sell-fastd-value": 71, - "sell-adx-value": 60, - "sell-rsi-value": 85, - "sell-mfi-enabled": False, - "sell-fastd-enabled": False, - "sell-adx-enabled": True, - "sell-rsi-enabled": True, - "sell-trigger": "sell-bb_upper", - }, - "roi": { - 0: 0.4829777881718843, - 408: 0.19334172464920202, - 989: 0.06860454019988212, - 2145: 0, - }, - "stoploss": {"stoploss": -0.30889015124682806}, - }, # noqa: E501 - "results_metrics": { - "total_trades": 0, - "trade_count_long": 0, - "trade_count_short": 0, - "wins": 0, - "draws": 0, - "losses": 0, - "profit_mean": None, - "profit_median": None, - "profit_total": 0, - "profit_total_abs": 0.0, - "max_drawdown": 0.0, - "max_drawdown_abs": 0.0, - "holding_avg": timedelta(), - }, # noqa: E501 - "results_explanation": " 0 trades. Avg profit nan%. Total profit 0.00000000 BTC ( 0.00Σ%). Avg duration nan min.", # noqa: E501 - "total_profit": 0, - "current_epoch": 12, - "is_initial_point": True, - "is_random": False, - "is_best": False, - }, - ] - - for res in hyperopt_res: - res["results_metrics"]["holding_avg_s"] = res["results_metrics"][ - "holding_avg" - ].total_seconds() - - return hyperopt_res - - @pytest.fixture(scope="function") def limit_buy_order_usdt_open(): return { diff --git a/tests/conftest_hyperopt.py b/tests/conftest_hyperopt.py new file mode 100644 index 000000000..af4039a3c --- /dev/null +++ b/tests/conftest_hyperopt.py @@ -0,0 +1,1000 @@ +from datetime import timedelta + + +def hyperopt_test_result(): + """ + Sample hyperopt test result, used for some tests. + """ + hyperopt_res = [ + { + "loss": 0.4366182531161519, + "params_dict": { + "mfi-value": 15, + "fastd-value": 20, + "adx-value": 25, + "rsi-value": 28, + "mfi-enabled": False, + "fastd-enabled": True, + "adx-enabled": True, + "rsi-enabled": True, + "trigger": "macd_cross_signal", + "sell-mfi-value": 88, + "sell-fastd-value": 97, + "sell-adx-value": 51, + "sell-rsi-value": 67, + "sell-mfi-enabled": False, + "sell-fastd-enabled": False, + "sell-adx-enabled": True, + "sell-rsi-enabled": True, + "sell-trigger": "sell-bb_upper", + "roi_t1": 1190, + "roi_t2": 541, + "roi_t3": 408, + "roi_p1": 0.026035863879169705, + "roi_p2": 0.12508730043628782, + "roi_p3": 0.27766427921605896, + "stoploss": -0.2562930402099556, + }, # noqa: E501 + "params_details": { + "buy": { + "mfi-value": 15, + "fastd-value": 20, + "adx-value": 25, + "rsi-value": 28, + "mfi-enabled": False, + "fastd-enabled": True, + "adx-enabled": True, + "rsi-enabled": True, + "trigger": "macd_cross_signal", + }, + "sell": { + "sell-mfi-value": 88, + "sell-fastd-value": 97, + "sell-adx-value": 51, + "sell-rsi-value": 67, + "sell-mfi-enabled": False, + "sell-fastd-enabled": False, + "sell-adx-enabled": True, + "sell-rsi-enabled": True, + "sell-trigger": "sell-bb_upper", + }, + "roi": { + 0: 0.4287874435315165, + 408: 0.15112316431545753, + 949: 0.026035863879169705, + 2139: 0, + }, + "stoploss": {"stoploss": -0.2562930402099556}, + }, # noqa: E501 + "results_metrics": { + "total_trades": 2, + "trade_count_long": 2, + "trade_count_short": 0, + "wins": 0, + "draws": 0, + "losses": 2, + "profit_mean": -0.01254995, + "profit_median": -0.012222, + "profit_total": -0.00125625, + "profit_total_abs": -2.50999, + "max_drawdown_account": 0.23, + "max_drawdown_abs": -0.00125625, + "holding_avg": timedelta(minutes=3930.0), + "stake_currency": "BTC", + "strategy_name": "SampleStrategy", + }, # noqa: E501 + "results_explanation": " 2 trades. Avg profit -1.25%. Total profit -0.00125625 BTC ( -2.51Σ%). Avg duration 3930.0 min.", # noqa: E501 + "total_profit": -0.00125625, + "current_epoch": 1, + "is_initial_point": True, + "is_random": False, + "is_best": True, + }, + { + "loss": 20.0, + "params_dict": { + "mfi-value": 17, + "fastd-value": 38, + "adx-value": 48, + "rsi-value": 22, + "mfi-enabled": True, + "fastd-enabled": False, + "adx-enabled": True, + "rsi-enabled": True, + "trigger": "macd_cross_signal", + "sell-mfi-value": 96, + "sell-fastd-value": 68, + "sell-adx-value": 63, + "sell-rsi-value": 81, + "sell-mfi-enabled": False, + "sell-fastd-enabled": True, + "sell-adx-enabled": True, + "sell-rsi-enabled": True, + "sell-trigger": "sell-sar_reversal", + "roi_t1": 334, + "roi_t2": 683, + "roi_t3": 140, + "roi_p1": 0.06403981740598495, + "roi_p2": 0.055519840060645045, + "roi_p3": 0.3253712811342459, + "stoploss": -0.338070047333259, + }, # noqa: E501 + "params_details": { + "buy": { + "mfi-value": 17, + "fastd-value": 38, + "adx-value": 48, + "rsi-value": 22, + "mfi-enabled": True, + "fastd-enabled": False, + "adx-enabled": True, + "rsi-enabled": True, + "trigger": "macd_cross_signal", + }, # noqa: E501 + "sell": { + "sell-mfi-value": 96, + "sell-fastd-value": 68, + "sell-adx-value": 63, + "sell-rsi-value": 81, + "sell-mfi-enabled": False, + "sell-fastd-enabled": True, + "sell-adx-enabled": True, + "sell-rsi-enabled": True, + "sell-trigger": "sell-sar_reversal", + }, # noqa: E501 + "roi": { + 0: 0.4449309386008759, + 140: 0.11955965746663, + 823: 0.06403981740598495, + 1157: 0, + }, # noqa: E501 + "stoploss": {"stoploss": -0.338070047333259}, + }, + "results_metrics": { + "total_trades": 1, + "trade_count_long": 1, + "trade_count_short": 0, + "wins": 0, + "draws": 0, + "losses": 1, + "profit_mean": 0.012357, + "profit_median": -0.012222, + "profit_total": 6.185e-05, + "profit_total_abs": 0.12357, + "max_drawdown_account": 0.23, + "max_drawdown_abs": -0.00125625, + "holding_avg": timedelta(minutes=1200.0), + }, # noqa: E501 + "results_explanation": " 1 trades. Avg profit 0.12%. Total profit 0.00006185 BTC ( 0.12Σ%). Avg duration 1200.0 min.", # noqa: E501 + "total_profit": 6.185e-05, + "current_epoch": 2, + "is_initial_point": True, + "is_random": False, + "is_best": False, + }, + { + "loss": 14.241196856510731, + "params_dict": { + "mfi-value": 25, + "fastd-value": 16, + "adx-value": 29, + "rsi-value": 20, + "mfi-enabled": False, + "fastd-enabled": False, + "adx-enabled": False, + "rsi-enabled": False, + "trigger": "macd_cross_signal", + "sell-mfi-value": 98, + "sell-fastd-value": 72, + "sell-adx-value": 51, + "sell-rsi-value": 82, + "sell-mfi-enabled": True, + "sell-fastd-enabled": True, + "sell-adx-enabled": True, + "sell-rsi-enabled": True, + "sell-trigger": "sell-macd_cross_signal", + "roi_t1": 889, + "roi_t2": 533, + "roi_t3": 263, + "roi_p1": 0.04759065393663096, + "roi_p2": 0.1488819964638463, + "roi_p3": 0.4102801822104605, + "stoploss": -0.05394588767607611, + }, # noqa: E501 + "params_details": { + "buy": { + "mfi-value": 25, + "fastd-value": 16, + "adx-value": 29, + "rsi-value": 20, + "mfi-enabled": False, + "fastd-enabled": False, + "adx-enabled": False, + "rsi-enabled": False, + "trigger": "macd_cross_signal", + }, + "sell": { + "sell-mfi-value": 98, + "sell-fastd-value": 72, + "sell-adx-value": 51, + "sell-rsi-value": 82, + "sell-mfi-enabled": True, + "sell-fastd-enabled": True, + "sell-adx-enabled": True, + "sell-rsi-enabled": True, + "sell-trigger": "sell-macd_cross_signal", + }, + "roi": { + 0: 0.6067528326109377, + 263: 0.19647265040047726, + 796: 0.04759065393663096, + 1685: 0, + }, + "stoploss": {"stoploss": -0.05394588767607611}, + }, # noqa: E501 + "results_metrics": { + "total_trades": 621, + "trade_count_long": 621, + "trade_count_short": 0, + "wins": 320, + "draws": 0, + "losses": 301, + "profit_mean": -0.043883302093397747, + "profit_median": -0.012222, + "profit_total": -0.13639474, + "profit_total_abs": -272.515306, + "max_drawdown_account": 0.25, + "max_drawdown_abs": -272.515306, + "holding_avg": timedelta(minutes=1691.207729468599), + }, # noqa: E501 + "results_explanation": " 621 trades. Avg profit -0.44%. Total profit -0.13639474 BTC (-272.52Σ%). Avg duration 1691.2 min.", # noqa: E501 + "total_profit": -0.13639474, + "current_epoch": 3, + "is_initial_point": True, + "is_random": False, + "is_best": False, + }, + { + "loss": 100000, + "params_dict": { + "mfi-value": 13, + "fastd-value": 35, + "adx-value": 39, + "rsi-value": 29, + "mfi-enabled": True, + "fastd-enabled": False, + "adx-enabled": False, + "rsi-enabled": True, + "trigger": "macd_cross_signal", + "sell-mfi-value": 87, + "sell-fastd-value": 54, + "sell-adx-value": 63, + "sell-rsi-value": 93, + "sell-mfi-enabled": False, + "sell-fastd-enabled": True, + "sell-adx-enabled": True, + "sell-rsi-enabled": True, + "sell-trigger": "sell-bb_upper", + "roi_t1": 1402, + "roi_t2": 676, + "roi_t3": 215, + "roi_p1": 0.06264755784937427, + "roi_p2": 0.14258587851894644, + "roi_p3": 0.20671291201040828, + "stoploss": -0.11818343570194478, + }, # noqa: E501 + "params_details": { + "buy": { + "mfi-value": 13, + "fastd-value": 35, + "adx-value": 39, + "rsi-value": 29, + "mfi-enabled": True, + "fastd-enabled": False, + "adx-enabled": False, + "rsi-enabled": True, + "trigger": "macd_cross_signal", + }, + "sell": { + "sell-mfi-value": 87, + "sell-fastd-value": 54, + "sell-adx-value": 63, + "sell-rsi-value": 93, + "sell-mfi-enabled": False, + "sell-fastd-enabled": True, + "sell-adx-enabled": True, + "sell-rsi-enabled": True, + "sell-trigger": "sell-bb_upper", + }, + "roi": { + 0: 0.411946348378729, + 215: 0.2052334363683207, + 891: 0.06264755784937427, + 2293: 0, + }, + "stoploss": {"stoploss": -0.11818343570194478}, + }, # noqa: E501 + "results_metrics": { + "total_trades": 0, + "trade_count_long": 0, + "trade_count_short": 0, + "wins": 0, + "draws": 0, + "losses": 0, + "profit_mean": None, + "profit_median": None, + "profit_total": 0, + "profit": 0.0, + "holding_avg": timedelta(), + }, # noqa: E501 + "results_explanation": " 0 trades. Avg profit nan%. Total profit 0.00000000 BTC ( 0.00Σ%). Avg duration nan min.", # noqa: E501 + "total_profit": 0, + "current_epoch": 4, + "is_initial_point": True, + "is_random": False, + "is_best": False, # noqa: E501 + }, + { + "loss": 0.22195522184191518, + "params_dict": { + "mfi-value": 17, + "fastd-value": 21, + "adx-value": 38, + "rsi-value": 33, + "mfi-enabled": True, + "fastd-enabled": False, + "adx-enabled": True, + "rsi-enabled": False, + "trigger": "macd_cross_signal", + "sell-mfi-value": 87, + "sell-fastd-value": 82, + "sell-adx-value": 78, + "sell-rsi-value": 69, + "sell-mfi-enabled": True, + "sell-fastd-enabled": False, + "sell-adx-enabled": True, + "sell-rsi-enabled": False, + "sell-trigger": "sell-macd_cross_signal", + "roi_t1": 1269, + "roi_t2": 601, + "roi_t3": 444, + "roi_p1": 0.07280999507931168, + "roi_p2": 0.08946698095898986, + "roi_p3": 0.1454876733325284, + "stoploss": -0.18181041180901014, + }, # noqa: E501 + "params_details": { + "buy": { + "mfi-value": 17, + "fastd-value": 21, + "adx-value": 38, + "rsi-value": 33, + "mfi-enabled": True, + "fastd-enabled": False, + "adx-enabled": True, + "rsi-enabled": False, + "trigger": "macd_cross_signal", + }, + "sell": { + "sell-mfi-value": 87, + "sell-fastd-value": 82, + "sell-adx-value": 78, + "sell-rsi-value": 69, + "sell-mfi-enabled": True, + "sell-fastd-enabled": False, + "sell-adx-enabled": True, + "sell-rsi-enabled": False, + "sell-trigger": "sell-macd_cross_signal", + }, + "roi": { + 0: 0.3077646493708299, + 444: 0.16227697603830155, + 1045: 0.07280999507931168, + 2314: 0, + }, + "stoploss": {"stoploss": -0.18181041180901014}, + }, # noqa: E501 + "results_metrics": { + "total_trades": 14, + "trade_count_long": 14, + "trade_count_short": 0, + "wins": 6, + "draws": 0, + "losses": 8, + "profit_mean": -0.003539515, + "profit_median": -0.012222, + "profit_total": -0.002480140000000001, + "profit_total_abs": -4.955321, + "max_drawdown_account": 0.34, + "max_drawdown_abs": -4.955321, + "holding_avg": timedelta(minutes=3402.8571428571427), + }, # noqa: E501 + "results_explanation": " 14 trades. Avg profit -0.35%. Total profit -0.00248014 BTC ( -4.96Σ%). Avg duration 3402.9 min.", # noqa: E501 + "total_profit": -0.002480140000000001, + "current_epoch": 5, + "is_initial_point": True, + "is_random": False, + "is_best": True, + }, + { + "loss": 0.545315889154162, + "params_dict": { + "mfi-value": 22, + "fastd-value": 43, + "adx-value": 46, + "rsi-value": 20, + "mfi-enabled": False, + "fastd-enabled": False, + "adx-enabled": True, + "rsi-enabled": True, + "trigger": "bb_lower", + "sell-mfi-value": 87, + "sell-fastd-value": 65, + "sell-adx-value": 94, + "sell-rsi-value": 63, + "sell-mfi-enabled": False, + "sell-fastd-enabled": True, + "sell-adx-enabled": True, + "sell-rsi-enabled": True, + "sell-trigger": "sell-macd_cross_signal", + "roi_t1": 319, + "roi_t2": 556, + "roi_t3": 216, + "roi_p1": 0.06251955472249589, + "roi_p2": 0.11659519602202795, + "roi_p3": 0.0953744132197762, + "stoploss": -0.024551752215582423, + }, # noqa: E501 + "params_details": { + "buy": { + "mfi-value": 22, + "fastd-value": 43, + "adx-value": 46, + "rsi-value": 20, + "mfi-enabled": False, + "fastd-enabled": False, + "adx-enabled": True, + "rsi-enabled": True, + "trigger": "bb_lower", + }, + "sell": { + "sell-mfi-value": 87, + "sell-fastd-value": 65, + "sell-adx-value": 94, + "sell-rsi-value": 63, + "sell-mfi-enabled": False, + "sell-fastd-enabled": True, + "sell-adx-enabled": True, + "sell-rsi-enabled": True, + "sell-trigger": "sell-macd_cross_signal", + }, + "roi": { + 0: 0.2744891639643, + 216: 0.17911475074452382, + 772: 0.06251955472249589, + 1091: 0, + }, + "stoploss": {"stoploss": -0.024551752215582423}, + }, # noqa: E501 + "results_metrics": { + "total_trades": 39, + "trade_count_long": 39, + "trade_count_short": 0, + "wins": 20, + "draws": 0, + "losses": 19, + "profit_mean": -0.0021400679487179478, + "profit_median": -0.012222, + "profit_total": -0.0041773, + "profit_total_abs": -8.346264999999997, + "max_drawdown_account": 0.45, + "max_drawdown_abs": -4.955321, + "holding_avg": timedelta(minutes=636.9230769230769), + }, # noqa: E501 + "results_explanation": " 39 trades. Avg profit -0.21%. Total profit -0.00417730 BTC ( -8.35Σ%). Avg duration 636.9 min.", # noqa: E501 + "total_profit": -0.0041773, + "current_epoch": 6, + "is_initial_point": True, + "is_random": False, + "is_best": False, + }, + { + "loss": 4.713497421432944, + "params_dict": { + "mfi-value": 13, + "fastd-value": 41, + "adx-value": 21, + "rsi-value": 29, + "mfi-enabled": False, + "fastd-enabled": True, + "adx-enabled": False, + "rsi-enabled": False, + "trigger": "bb_lower", + "sell-mfi-value": 99, + "sell-fastd-value": 60, + "sell-adx-value": 81, + "sell-rsi-value": 69, + "sell-mfi-enabled": True, + "sell-fastd-enabled": True, + "sell-adx-enabled": True, + "sell-rsi-enabled": False, + "sell-trigger": "sell-macd_cross_signal", + "roi_t1": 771, + "roi_t2": 620, + "roi_t3": 145, + "roi_p1": 0.0586919200378493, + "roi_p2": 0.04984118697312542, + "roi_p3": 0.37521058680247044, + "stoploss": -0.14613268022709905, + }, # noqa: E501 + "params_details": { + "buy": { + "mfi-value": 13, + "fastd-value": 41, + "adx-value": 21, + "rsi-value": 29, + "mfi-enabled": False, + "fastd-enabled": True, + "adx-enabled": False, + "rsi-enabled": False, + "trigger": "bb_lower", + }, + "sell": { + "sell-mfi-value": 99, + "sell-fastd-value": 60, + "sell-adx-value": 81, + "sell-rsi-value": 69, + "sell-mfi-enabled": True, + "sell-fastd-enabled": True, + "sell-adx-enabled": True, + "sell-rsi-enabled": False, + "sell-trigger": "sell-macd_cross_signal", + }, + "roi": { + 0: 0.4837436938134452, + 145: 0.10853310701097472, + 765: 0.0586919200378493, + 1536: 0, + }, # noqa: E501 + "stoploss": {"stoploss": -0.14613268022709905}, + }, # noqa: E501 + "results_metrics": { + "total_trades": 318, + "trade_count_long": 318, + "trade_count_short": 0, + "wins": 100, + "draws": 0, + "losses": 218, + "profit_mean": -0.0039833954716981146, + "profit_median": -0.012222, + "profit_total": -0.06339929, + "profit_total_abs": -126.67197600000004, + "max_drawdown_account": 0.50, + "max_drawdown_abs": -200.955321, + "holding_avg": timedelta(minutes=3140.377358490566), + }, # noqa: E501 + "results_explanation": " 318 trades. Avg profit -0.40%. Total profit -0.06339929 BTC (-126.67Σ%). Avg duration 3140.4 min.", # noqa: E501 + "total_profit": -0.06339929, + "current_epoch": 7, + "is_initial_point": True, + "is_random": False, + "is_best": False, + }, + { + "loss": 20.0, # noqa: E501 + "params_dict": { + "mfi-value": 24, + "fastd-value": 43, + "adx-value": 33, + "rsi-value": 20, + "mfi-enabled": False, + "fastd-enabled": True, + "adx-enabled": True, + "rsi-enabled": True, + "trigger": "sar_reversal", + "sell-mfi-value": 89, + "sell-fastd-value": 74, + "sell-adx-value": 70, + "sell-rsi-value": 70, + "sell-mfi-enabled": False, + "sell-fastd-enabled": False, + "sell-adx-enabled": False, + "sell-rsi-enabled": True, + "sell-trigger": "sell-sar_reversal", + "roi_t1": 1149, + "roi_t2": 375, + "roi_t3": 289, + "roi_p1": 0.05571820757172588, + "roi_p2": 0.0606240398618907, + "roi_p3": 0.1729012220156157, + "stoploss": -0.1588514289110401, + }, # noqa: E501 + "params_details": { + "buy": { + "mfi-value": 24, + "fastd-value": 43, + "adx-value": 33, + "rsi-value": 20, + "mfi-enabled": False, + "fastd-enabled": True, + "adx-enabled": True, + "rsi-enabled": True, + "trigger": "sar_reversal", + }, + "sell": { + "sell-mfi-value": 89, + "sell-fastd-value": 74, + "sell-adx-value": 70, + "sell-rsi-value": 70, + "sell-mfi-enabled": False, + "sell-fastd-enabled": False, + "sell-adx-enabled": False, + "sell-rsi-enabled": True, + "sell-trigger": "sell-sar_reversal", + }, + "roi": { + 0: 0.2892434694492323, + 289: 0.11634224743361658, + 664: 0.05571820757172588, + 1813: 0, + }, + "stoploss": {"stoploss": -0.1588514289110401}, + }, # noqa: E501 + "results_metrics": { + "total_trades": 1, + "trade_count_long": 1, + "trade_count_short": 0, + "wins": 0, + "draws": 1, + "losses": 0, + "profit_mean": 0.0, + "profit_median": 0.0, + "profit_total": 0.0, + "profit_total_abs": 0.0, + "max_drawdown_account": 0.0, + "max_drawdown_abs": 0.52, + "holding_avg": timedelta(minutes=5340.0), + }, # noqa: E501 + "results_explanation": " 1 trades. Avg profit 0.00%. Total profit 0.00000000 BTC ( 0.00Σ%). Avg duration 5340.0 min.", # noqa: E501 + "total_profit": 0.0, + "current_epoch": 8, + "is_initial_point": True, + "is_random": False, + "is_best": False, + }, + { + "loss": 2.4731817780991223, + "params_dict": { + "mfi-value": 22, + "fastd-value": 20, + "adx-value": 29, + "rsi-value": 40, + "mfi-enabled": False, + "fastd-enabled": False, + "adx-enabled": False, + "rsi-enabled": False, + "trigger": "sar_reversal", + "sell-mfi-value": 97, + "sell-fastd-value": 65, + "sell-adx-value": 81, + "sell-rsi-value": 64, + "sell-mfi-enabled": True, + "sell-fastd-enabled": True, + "sell-adx-enabled": True, + "sell-rsi-enabled": True, + "sell-trigger": "sell-bb_upper", + "roi_t1": 1012, + "roi_t2": 584, + "roi_t3": 422, + "roi_p1": 0.036764323603472565, + "roi_p2": 0.10335480573205287, + "roi_p3": 0.10322347377503042, + "stoploss": -0.2780610808108503, + }, # noqa: E501 + "params_details": { + "buy": { + "mfi-value": 22, + "fastd-value": 20, + "adx-value": 29, + "rsi-value": 40, + "mfi-enabled": False, + "fastd-enabled": False, + "adx-enabled": False, + "rsi-enabled": False, + "trigger": "sar_reversal", + }, + "sell": { + "sell-mfi-value": 97, + "sell-fastd-value": 65, + "sell-adx-value": 81, + "sell-rsi-value": 64, + "sell-mfi-enabled": True, + "sell-fastd-enabled": True, + "sell-adx-enabled": True, + "sell-rsi-enabled": True, + "sell-trigger": "sell-bb_upper", + }, + "roi": { + 0: 0.2433426031105559, + 422: 0.14011912933552545, + 1006: 0.036764323603472565, + 2018: 0, + }, + "stoploss": {"stoploss": -0.2780610808108503}, + }, # noqa: E501 + "results_metrics": { + "total_trades": 229, + "trade_count_long": 229, + "trade_count_short": 0, + "wins": 150, + "draws": 0, + "losses": 79, + "profit_mean": -0.0038433433624454144, + "profit_median": -0.012222, + "profit_total": -0.044050070000000004, + "profit_total_abs": -88.01256299999999, + "max_drawdown_account": 0.41, + "max_drawdown_abs": -150.955321, + "holding_avg": timedelta(minutes=6505.676855895196), + }, # noqa: E501 + "results_explanation": " 229 trades. Avg profit -0.38%. Total profit -0.04405007 BTC ( -88.01Σ%). Avg duration 6505.7 min.", # noqa: E501 + "total_profit": -0.044050070000000004, # noqa: E501 + "current_epoch": 9, + "is_initial_point": True, + "is_random": False, + "is_best": False, + }, + { + "loss": -0.2604606005845212, # noqa: E501 + "params_dict": { + "mfi-value": 23, + "fastd-value": 24, + "adx-value": 22, + "rsi-value": 24, + "mfi-enabled": False, + "fastd-enabled": False, + "adx-enabled": False, + "rsi-enabled": True, + "trigger": "macd_cross_signal", + "sell-mfi-value": 97, + "sell-fastd-value": 70, + "sell-adx-value": 64, + "sell-rsi-value": 80, + "sell-mfi-enabled": False, + "sell-fastd-enabled": True, + "sell-adx-enabled": True, + "sell-rsi-enabled": True, + "sell-trigger": "sell-sar_reversal", + "roi_t1": 792, + "roi_t2": 464, + "roi_t3": 215, + "roi_p1": 0.04594053535385903, + "roi_p2": 0.09623192684243963, + "roi_p3": 0.04428219070850663, + "stoploss": -0.16992287161634415, + }, # noqa: E501 + "params_details": { + "buy": { + "mfi-value": 23, + "fastd-value": 24, + "adx-value": 22, + "rsi-value": 24, + "mfi-enabled": False, + "fastd-enabled": False, + "adx-enabled": False, + "rsi-enabled": True, + "trigger": "macd_cross_signal", + }, + "sell": { + "sell-mfi-value": 97, + "sell-fastd-value": 70, + "sell-adx-value": 64, + "sell-rsi-value": 80, + "sell-mfi-enabled": False, + "sell-fastd-enabled": True, + "sell-adx-enabled": True, + "sell-rsi-enabled": True, + "sell-trigger": "sell-sar_reversal", + }, + "roi": { + 0: 0.18645465290480528, + 215: 0.14217246219629864, + 679: 0.04594053535385903, + 1471: 0, + }, + "stoploss": {"stoploss": -0.16992287161634415}, + }, # noqa: E501 + "results_metrics": { + "total_trades": 4, + "trade_count_long": 4, + "trade_count_short": 0, + "wins": 0, + "draws": 0, + "losses": 4, + "profit_mean": 0.001080385, + "profit_median": -0.012222, + "profit_total": 0.00021629, + "profit_total_abs": 0.432154, + "max_drawdown_account": 0.13, + "max_drawdown_abs": -4.955321, + "holding_avg": timedelta(minutes=2850.0), + }, # noqa: E501 + "results_explanation": " 4 trades. Avg profit 0.11%. Total profit 0.00021629 BTC ( 0.43Σ%). Avg duration 2850.0 min.", # noqa: E501 + "total_profit": 0.00021629, + "current_epoch": 10, + "is_initial_point": True, + "is_random": False, + "is_best": True, + }, + { + "loss": 4.876465945994304, # noqa: E501 + "params_dict": { + "mfi-value": 20, + "fastd-value": 32, + "adx-value": 49, + "rsi-value": 23, + "mfi-enabled": True, + "fastd-enabled": True, + "adx-enabled": False, + "rsi-enabled": False, + "trigger": "bb_lower", + "sell-mfi-value": 75, + "sell-fastd-value": 56, + "sell-adx-value": 61, + "sell-rsi-value": 62, + "sell-mfi-enabled": False, + "sell-fastd-enabled": False, + "sell-adx-enabled": True, + "sell-rsi-enabled": True, + "sell-trigger": "sell-macd_cross_signal", + "roi_t1": 579, + "roi_t2": 614, + "roi_t3": 273, + "roi_p1": 0.05307643172744114, + "roi_p2": 0.1352282078262871, + "roi_p3": 0.1913307406325751, + "stoploss": -0.25728526022513887, + }, # noqa: E501 + "params_details": { + "buy": { + "mfi-value": 20, + "fastd-value": 32, + "adx-value": 49, + "rsi-value": 23, + "mfi-enabled": True, + "fastd-enabled": True, + "adx-enabled": False, + "rsi-enabled": False, + "trigger": "bb_lower", + }, + "sell": { + "sell-mfi-value": 75, + "sell-fastd-value": 56, + "sell-adx-value": 61, + "sell-rsi-value": 62, + "sell-mfi-enabled": False, + "sell-fastd-enabled": False, + "sell-adx-enabled": True, + "sell-rsi-enabled": True, + "sell-trigger": "sell-macd_cross_signal", + }, + "roi": { + 0: 0.3796353801863034, + 273: 0.18830463955372825, + 887: 0.05307643172744114, + 1466: 0, + }, + "stoploss": {"stoploss": -0.25728526022513887}, + }, # noqa: E501 + # New Hyperopt mode! + "results_metrics": { + "total_trades": 117, + "trade_count_long": 117, + "trade_count_short": 0, + "wins": 67, + "draws": 0, + "losses": 50, + "profit_mean": -0.012698609145299145, + "profit_median": -0.012222, + "profit_total": -0.07436117, + "profit_total_abs": -148.573727, + "max_drawdown_account": 0.52, + "max_drawdown_abs": -224.955321, + "holding_avg": timedelta(minutes=4282.5641025641025), + }, # noqa: E501 + "results_explanation": " 117 trades. Avg profit -1.27%. Total profit -0.07436117 BTC (-148.57Σ%). Avg duration 4282.6 min.", # noqa: E501 + "total_profit": -0.07436117, + "current_epoch": 11, + "is_initial_point": True, + "is_random": False, + "is_best": False, + }, + { + "loss": 100000, + "params_dict": { + "mfi-value": 10, + "fastd-value": 36, + "adx-value": 31, + "rsi-value": 22, + "mfi-enabled": True, + "fastd-enabled": True, + "adx-enabled": True, + "rsi-enabled": False, + "trigger": "sar_reversal", + "sell-mfi-value": 80, + "sell-fastd-value": 71, + "sell-adx-value": 60, + "sell-rsi-value": 85, + "sell-mfi-enabled": False, + "sell-fastd-enabled": False, + "sell-adx-enabled": True, + "sell-rsi-enabled": True, + "sell-trigger": "sell-bb_upper", + "roi_t1": 1156, + "roi_t2": 581, + "roi_t3": 408, + "roi_p1": 0.06860454019988212, + "roi_p2": 0.12473718444931989, + "roi_p3": 0.2896360635226823, + "stoploss": -0.30889015124682806, + }, # noqa: E501 + "params_details": { + "buy": { + "mfi-value": 10, + "fastd-value": 36, + "adx-value": 31, + "rsi-value": 22, + "mfi-enabled": True, + "fastd-enabled": True, + "adx-enabled": True, + "rsi-enabled": False, + "trigger": "sar_reversal", + }, + "sell": { + "sell-mfi-value": 80, + "sell-fastd-value": 71, + "sell-adx-value": 60, + "sell-rsi-value": 85, + "sell-mfi-enabled": False, + "sell-fastd-enabled": False, + "sell-adx-enabled": True, + "sell-rsi-enabled": True, + "sell-trigger": "sell-bb_upper", + }, + "roi": { + 0: 0.4829777881718843, + 408: 0.19334172464920202, + 989: 0.06860454019988212, + 2145: 0, + }, + "stoploss": {"stoploss": -0.30889015124682806}, + }, # noqa: E501 + "results_metrics": { + "total_trades": 0, + "trade_count_long": 0, + "trade_count_short": 0, + "wins": 0, + "draws": 0, + "losses": 0, + "profit_mean": None, + "profit_median": None, + "profit_total": 0, + "profit_total_abs": 0.0, + "max_drawdown_account": 0.0, + "max_drawdown_abs": 0.0, + "holding_avg": timedelta(), + }, # noqa: E501 + "results_explanation": " 0 trades. Avg profit nan%. Total profit 0.00000000 BTC ( 0.00Σ%). Avg duration nan min.", # noqa: E501 + "total_profit": 0, + "current_epoch": 12, + "is_initial_point": True, + "is_random": False, + "is_best": False, + }, + ] + + for res in hyperopt_res: + res["results_metrics"]["holding_avg_s"] = res["results_metrics"][ + "holding_avg" + ].total_seconds() + return hyperopt_res diff --git a/tests/data/test_btanalysis.py b/tests/data/test_btanalysis.py index be4c638a7..b9dee6d59 100644 --- a/tests/data/test_btanalysis.py +++ b/tests/data/test_btanalysis.py @@ -343,17 +343,15 @@ def test_create_cum_profit1(testdatadir): def test_calculate_max_drawdown(testdatadir): filename = testdatadir / "backtest_results/backtest-result.json" bt_data = load_backtest_data(filename) - _, hdate, lowdate, hval, lval, drawdown = calculate_max_drawdown( - bt_data, value_col="profit_abs" - ) - assert isinstance(drawdown, float) - assert pytest.approx(drawdown) == 0.29753914 - assert isinstance(hdate, Timestamp) - assert isinstance(lowdate, Timestamp) - assert isinstance(hval, float) - assert isinstance(lval, float) - assert hdate == Timestamp("2018-01-16 19:30:00", tz="UTC") - assert lowdate == Timestamp("2018-01-16 22:25:00", tz="UTC") + drawdown = calculate_max_drawdown(bt_data, value_col="profit_abs") + assert isinstance(drawdown.relative_account_drawdown, float) + assert pytest.approx(drawdown.relative_account_drawdown) == 0.29753914 + assert isinstance(drawdown.high_date, Timestamp) + assert isinstance(drawdown.low_date, Timestamp) + assert isinstance(drawdown.high_value, float) + assert isinstance(drawdown.low_value, float) + assert drawdown.high_date == Timestamp("2018-01-16 19:30:00", tz="UTC") + assert drawdown.low_date == Timestamp("2018-01-16 22:25:00", tz="UTC") underwater = calculate_underwater(bt_data) assert isinstance(underwater, DataFrame) @@ -509,19 +507,20 @@ def test_calculate_max_drawdown2(): # sort by profit and reset index df = df.sort_values("profit").reset_index(drop=True) df1 = df.copy() - drawdown, hdate, ldate, hval, lval, drawdown_rel = calculate_max_drawdown( - df, date_col="open_date", value_col="profit" + drawdown = calculate_max_drawdown( + df, date_col="open_date", starting_balance=0.2, value_col="profit" ) # Ensure df has not been altered. assert df.equals(df1) - assert isinstance(drawdown, float) - assert isinstance(drawdown_rel, float) + assert isinstance(drawdown.drawdown_abs, float) + assert isinstance(drawdown.relative_account_drawdown, float) # High must be before low - assert hdate < ldate + assert drawdown.high_date < drawdown.low_date # High value must be higher than low value - assert hval > lval - assert drawdown == 0.091755 + assert drawdown.high_value > drawdown.low_value + assert drawdown.drawdown_abs == 0.091755 + assert pytest.approx(drawdown.relative_account_drawdown) == 0.32129575 df = DataFrame(zip(values[:5], dates[:5]), columns=["profit", "open_date"]) with pytest.raises(ValueError, match="No losing trade, therefore no drawdown."): @@ -530,10 +529,8 @@ def test_calculate_max_drawdown2(): df1 = DataFrame(zip(values[:5], dates[:5]), columns=["profit", "open_date"]) df1.loc[:, "profit"] = df1["profit"] * -1 # No winning trade ... - drawdown, hdate, ldate, hval, lval, drawdown_rel = calculate_max_drawdown( - df1, date_col="open_date", value_col="profit" - ) - assert drawdown == 0.043965 + drawdown = calculate_max_drawdown(df1, date_col="open_date", value_col="profit") + assert drawdown.drawdown_abs == 0.043965 @pytest.mark.parametrize( @@ -555,20 +552,20 @@ def test_calculate_max_drawdown_abs(profits, relative, highd, lowdays, result, r # sort by profit and reset index df = df.sort_values("profit_abs").reset_index(drop=True) df1 = df.copy() - drawdown, hdate, ldate, hval, lval, drawdown_rel = calculate_max_drawdown( + drawdown = calculate_max_drawdown( df, date_col="open_date", starting_balance=1000, relative=relative ) # Ensure df has not been altered. assert df.equals(df1) - assert isinstance(drawdown, float) - assert isinstance(drawdown_rel, float) - assert hdate == init_date + timedelta(days=highd) - assert ldate == init_date + timedelta(days=lowdays) + assert isinstance(drawdown.drawdown_abs, float) + assert isinstance(drawdown.relative_account_drawdown, float) + assert drawdown.high_date == init_date + timedelta(days=highd) + assert drawdown.low_date == init_date + timedelta(days=lowdays) # High must be before low - assert hdate < ldate + assert drawdown.high_date < drawdown.low_date # High value must be higher than low value - assert hval > lval - assert drawdown == result - assert pytest.approx(drawdown_rel) == result_rel + assert drawdown.high_value > drawdown.low_value + assert drawdown.drawdown_abs == result + assert pytest.approx(drawdown.relative_account_drawdown) == result_rel diff --git a/tests/data/test_download_data.py b/tests/data/test_download_data.py index cd7f4ab8f..4922a213f 100644 --- a/tests/data/test_download_data.py +++ b/tests/data/test_download_data.py @@ -69,13 +69,19 @@ def test_download_data_main_trades(mocker): assert dl_mock.call_args[1]["timerange"].starttype == "date" assert dl_mock.call_count == 1 - assert convert_mock.call_count == 1 + assert convert_mock.call_count == 0 + dl_mock.reset_mock() + config.update( { - "download_trades": True, - "trading_mode": "futures", + "convert_trades": True, } ) + download_data_main(config) + + assert dl_mock.call_args[1]["timerange"].starttype == "date" + assert dl_mock.call_count == 1 + assert convert_mock.call_count == 1 def test_download_data_main_data_invalid(mocker): diff --git a/tests/exchange/test_exchange.py b/tests/exchange/test_exchange.py index 488ef8428..b033dcdaa 100644 --- a/tests/exchange/test_exchange.py +++ b/tests/exchange/test_exchange.py @@ -182,7 +182,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") @@ -519,7 +519,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) @@ -528,28 +528,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) @@ -564,12 +562,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 @@ -578,42 +576,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"}, @@ -624,7 +625,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) @@ -632,7 +632,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"}, @@ -643,14 +643,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( @@ -695,24 +694,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( - 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}._load_async_markets", + return_value={ + "ETH/BTC": {"quote": "BTC"}, + "LTC/BTC": {"quote": "BTC"}, + "XRP/BTC": {"quote": "BTC"}, + "NEO/BTC": {"quote": "BTC"}, + }, + ) 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) @@ -752,7 +753,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"}, @@ -762,7 +763,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") @@ -775,9 +775,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"}, @@ -788,17 +788,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"}, @@ -809,7 +808,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") @@ -817,10 +815,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"}, @@ -831,7 +829,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.*"): @@ -848,7 +845,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") @@ -866,7 +863,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") @@ -896,7 +893,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( @@ -918,7 +915,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( @@ -940,7 +937,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") @@ -956,7 +953,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") @@ -992,7 +989,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") @@ -1051,7 +1048,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") @@ -1076,7 +1073,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") @@ -1948,7 +1945,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 ad1bdb870..8820ce3e7 100644 --- a/tests/exchange_online/conftest.py +++ b/tests/exchange_online/conftest.py @@ -45,7 +45,25 @@ EXCHANGES = { "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": { @@ -200,6 +218,24 @@ EXCHANGES = { "rebated_fee_currency": "USDT", }, ], + "sample_my_trades": [ + { + "id": "123412341234", + "create_time": "167997798", + "create_time_ms": "167997798825.566200", + "currency_pair": "ETH_USDT", + "side": "sell", + "role": "taker", + "amount": "0.0115", + "price": "1712.63", + "order_id": "1234123412", + "fee": "0.0", + "fee_currency": "USDT", + "point_fee": "0.03939049", + "gt_fee": "0.0", + "amend_text": "-", + } + ], }, "okx": { "pair": "BTC/USDT", @@ -270,6 +306,36 @@ EXCHANGES = { "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 b764f8f75..49fbfc60d 100644 --- a/tests/exchange_online/test_ccxt_compat.py +++ b/tests/exchange_online/test_ccxt_compat.py @@ -76,7 +76,8 @@ class TestCCXTExchange: 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 @@ -86,6 +87,33 @@ class TestCCXTExchange: else: pytest.skip(f"No sample order available for exchange {exchange_name}") + def test_ccxt_my_trades_parse(self, exchange: EXCHANGE_FIXTURE_TYPE): + exch, exchange_name = exchange + if trades := EXCHANGES[exchange_name].get("sample_my_trades"): + pair = "SOL/USDT" + for trade in trades: + market = exch._api.markets[pair] + po = exch._api.parse_trade(trade) + (trade, market) + assert isinstance(po["id"], str) + assert isinstance(po["side"], str) + assert isinstance(po["amount"], float) + assert isinstance(po["price"], float) + assert isinstance(po["datetime"], str) + assert isinstance(po["timestamp"], int) + + if fees := po.get("fees"): + assert isinstance(fees, list) + for fee in fees: + assert isinstance(fee, dict) + assert isinstance(fee["cost"], str) + # TODO: this should be a float! + # assert isinstance(fee["cost"], float) + assert isinstance(fee["currency"], str) + + else: + pytest.skip(f"No sample Trades available for exchange {exchange_name}") + def test_ccxt_fetch_tickers(self, exchange: EXCHANGE_FIXTURE_TYPE): exch, exchangename = exchange pair = EXCHANGES[exchangename]["pair"] diff --git a/tests/freqai/conftest.py b/tests/freqai/conftest.py index fce01b9ee..887dfe3a4 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 826e7ebf4..db2e96b37 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 08f0408ed..d2ff5b8d3 100644 --- a/tests/optimize/test_hyperopt.py +++ b/tests/optimize/test_hyperopt.py @@ -47,7 +47,7 @@ def generate_result_metrics(): "profit_total_abs": 0.001, "profit_total": 0.01, "holding_avg": timedelta(minutes=20), - "max_drawdown": 0.001, + "max_drawdown_account": 0.001, "max_drawdown_abs": 0.001, "loss": 0.001, "is_initial_point": 0.001, @@ -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 a30761486..0545ac861 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 df48d4b98..f6c58a1e7 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) @@ -2306,7 +2308,7 @@ def test_MarketCapPairList_filter( ) mocker.patch( - "freqtrade.plugins.pairlist.MarketCapPairList.CoinGeckoAPI.get_coins_markets", + "freqtrade.plugins.pairlist.MarketCapPairList.FtCoinGeckoApi.get_coins_markets", return_value=test_value, ) @@ -2344,7 +2346,7 @@ def test_MarketCapPairList_timing(mocker, default_conf_usdt, markets, time_machi ) mocker.patch( - "freqtrade.plugins.pairlist.MarketCapPairList.CoinGeckoAPI.get_coins_markets", + "freqtrade.plugins.pairlist.MarketCapPairList.FtCoinGeckoApi.get_coins_markets", return_value=test_value, ) diff --git a/tests/rpc/test_fiat_convert.py b/tests/rpc/test_fiat_convert.py index 049716414..061df2e53 100644 --- a/tests/rpc/test_fiat_convert.py +++ b/tests/rpc/test_fiat_convert.py @@ -8,11 +8,20 @@ import pytest from requests.exceptions import RequestException from freqtrade.rpc.fiat_convert import CryptoToFiatConverter +from freqtrade.util.coin_gecko import FtCoinGeckoApi from tests.conftest import log_has, log_has_re -def test_fiat_convert_is_supported(mocker): - fiat_convert = CryptoToFiatConverter() +def test_fiat_convert_is_singleton(): + fiat_convert = CryptoToFiatConverter({"a": 22}) + fiat_convert2 = CryptoToFiatConverter({}) + + assert fiat_convert is fiat_convert2 + assert id(fiat_convert) == id(fiat_convert2) + + +def test_fiat_convert_is_supported(): + fiat_convert = CryptoToFiatConverter({}) assert fiat_convert._is_supported_fiat(fiat="USD") is True assert fiat_convert._is_supported_fiat(fiat="usd") is True assert fiat_convert._is_supported_fiat(fiat="abc") is False @@ -20,7 +29,7 @@ def test_fiat_convert_is_supported(mocker): def test_fiat_convert_find_price(mocker): - fiat_convert = CryptoToFiatConverter() + fiat_convert = CryptoToFiatConverter({}) fiat_convert._coinlistings = {} fiat_convert._backoff = 0 @@ -48,7 +57,7 @@ def test_fiat_convert_find_price(mocker): def test_fiat_convert_unsupported_crypto(mocker, caplog): mocker.patch("freqtrade.rpc.fiat_convert.CryptoToFiatConverter._coinlistings", return_value=[]) - fiat_convert = CryptoToFiatConverter() + fiat_convert = CryptoToFiatConverter({}) assert fiat_convert._find_price(crypto_symbol="CRYPTO_123", fiat_symbol="EUR") == 0.0 assert log_has("unsupported crypto-symbol CRYPTO_123 - returning 0.0", caplog) @@ -58,7 +67,7 @@ def test_fiat_convert_get_price(mocker): "freqtrade.rpc.fiat_convert.CryptoToFiatConverter._find_price", return_value=28000.0 ) - fiat_convert = CryptoToFiatConverter() + fiat_convert = CryptoToFiatConverter({}) with pytest.raises(ValueError, match=r"The fiat us dollar is not supported."): fiat_convert.get_price(crypto_symbol="btc", fiat_symbol="US Dollar") @@ -77,20 +86,20 @@ def test_fiat_convert_get_price(mocker): assert find_price.call_count == 1 -def test_fiat_convert_same_currencies(mocker): - fiat_convert = CryptoToFiatConverter() +def test_fiat_convert_same_currencies(): + fiat_convert = CryptoToFiatConverter({}) assert fiat_convert.get_price(crypto_symbol="USD", fiat_symbol="USD") == 1.0 -def test_fiat_convert_two_FIAT(mocker): - fiat_convert = CryptoToFiatConverter() +def test_fiat_convert_two_FIAT(): + fiat_convert = CryptoToFiatConverter({}) assert fiat_convert.get_price(crypto_symbol="USD", fiat_symbol="EUR") == 0.0 -def test_loadcryptomap(mocker): - fiat_convert = CryptoToFiatConverter() +def test_loadcryptomap(): + fiat_convert = CryptoToFiatConverter({}) assert len(fiat_convert._coinlistings) == 2 assert fiat_convert._get_gecko_id("btc") == "bitcoin" @@ -100,28 +109,28 @@ def test_fiat_init_network_exception(mocker): # Because CryptoToFiatConverter is a Singleton we reset the listings listmock = MagicMock(side_effect=RequestException) mocker.patch.multiple( - "freqtrade.rpc.fiat_convert.CoinGeckoAPI", + "freqtrade.rpc.fiat_convert.FtCoinGeckoApi", get_coins_list=listmock, ) # with pytest.raises(RequestEsxception): - fiat_convert = CryptoToFiatConverter() + fiat_convert = CryptoToFiatConverter({}) fiat_convert._coinlistings = {} fiat_convert._load_cryptomap() assert len(fiat_convert._coinlistings) == 0 -def test_fiat_convert_without_network(mocker): +def test_fiat_convert_without_network(): # Because CryptoToFiatConverter is a Singleton we reset the value of _coingecko - fiat_convert = CryptoToFiatConverter() + fiat_convert = CryptoToFiatConverter({}) - cmc_temp = CryptoToFiatConverter._coingecko - CryptoToFiatConverter._coingecko = None + cmc_temp = fiat_convert._coingecko + fiat_convert._coingecko = None assert fiat_convert._coingecko is None assert fiat_convert._find_price(crypto_symbol="btc", fiat_symbol="usd") == 0.0 - CryptoToFiatConverter._coingecko = cmc_temp + fiat_convert._coingecko = cmc_temp def test_fiat_too_many_requests_response(mocker, caplog): @@ -129,11 +138,11 @@ def test_fiat_too_many_requests_response(mocker, caplog): req_exception = "429 Too Many Requests" listmock = MagicMock(return_value="{}", side_effect=RequestException(req_exception)) mocker.patch.multiple( - "freqtrade.rpc.fiat_convert.CoinGeckoAPI", + "freqtrade.rpc.fiat_convert.FtCoinGeckoApi", get_coins_list=listmock, ) # with pytest.raises(RequestEsxception): - fiat_convert = CryptoToFiatConverter() + fiat_convert = CryptoToFiatConverter({}) fiat_convert._coinlistings = {} fiat_convert._load_cryptomap() @@ -144,8 +153,8 @@ def test_fiat_too_many_requests_response(mocker, caplog): ) -def test_fiat_multiple_coins(mocker, caplog): - fiat_convert = CryptoToFiatConverter() +def test_fiat_multiple_coins(caplog): + fiat_convert = CryptoToFiatConverter({}) fiat_convert._coinlistings = [ {"id": "helium", "symbol": "hnt", "name": "Helium"}, {"id": "hymnode", "symbol": "hnt", "name": "Hymnode"}, @@ -165,11 +174,11 @@ def test_fiat_invalid_response(mocker, caplog): # Because CryptoToFiatConverter is a Singleton we reset the listings listmock = MagicMock(return_value=None) mocker.patch.multiple( - "freqtrade.rpc.fiat_convert.CoinGeckoAPI", + "freqtrade.rpc.fiat_convert.FtCoinGeckoApi", get_coins_list=listmock, ) # with pytest.raises(RequestEsxception): - fiat_convert = CryptoToFiatConverter() + fiat_convert = CryptoToFiatConverter({}) fiat_convert._coinlistings = [] fiat_convert._load_cryptomap() @@ -182,7 +191,7 @@ def test_fiat_invalid_response(mocker, caplog): def test_convert_amount(mocker): mocker.patch("freqtrade.rpc.fiat_convert.CryptoToFiatConverter.get_price", return_value=12345.0) - fiat_convert = CryptoToFiatConverter() + fiat_convert = CryptoToFiatConverter({}) result = fiat_convert.convert_amount(crypto_amount=1.23, crypto_symbol="BTC", fiat_symbol="USD") assert result == 15184.35 @@ -193,3 +202,18 @@ def test_convert_amount(mocker): crypto_amount="1.23", crypto_symbol="BTC", fiat_symbol="BTC" ) assert result == 1.23 + + +def test_FtCoinGeckoApi(): + ftc = FtCoinGeckoApi() + assert ftc._api_key == "" + assert ftc.api_base_url == "https://api.coingecko.com/api/v3/" + + # defaults to demo + ftc = FtCoinGeckoApi(api_key="123456") + assert ftc._api_key == "123456" + assert ftc.api_base_url == "https://api.coingecko.com/api/v3/" + + ftc = FtCoinGeckoApi(api_key="123456", is_demo=False) + assert ftc._api_key == "123456" + assert ftc.api_base_url == "https://pro-api.coingecko.com/api/v3/" diff --git a/tests/rpc/test_rpc.py b/tests/rpc/test_rpc.py index d421ba556..a0c235cd5 100644 --- a/tests/rpc/test_rpc.py +++ b/tests/rpc/test_rpc.py @@ -225,7 +225,7 @@ def test_rpc_trade_status(default_conf, ticker, fee, mocker) -> None: def test_rpc_status_table(default_conf, ticker, fee, mocker) -> None: mocker.patch.multiple( - "freqtrade.rpc.fiat_convert.CoinGeckoAPI", + "freqtrade.rpc.fiat_convert.FtCoinGeckoApi", get_price=MagicMock(return_value={"bitcoin": {"usd": 15000.0}}), ) mocker.patch("freqtrade.rpc.rpc.CryptoToFiatConverter._find_price", return_value=15000.0) @@ -266,7 +266,7 @@ def test_rpc_status_table(default_conf, ticker, fee, mocker) -> None: assert "-0.00" == f"{fiat_profit_sum:.2f}" # Test with fiat convert - rpc._fiat_converter = CryptoToFiatConverter() + rpc._fiat_converter = CryptoToFiatConverter({}) result, headers, fiat_profit_sum = rpc._rpc_status_table(default_conf["stake_currency"], "USD") assert "Since" in headers assert "Pair" in headers @@ -312,7 +312,7 @@ def test__rpc_timeunit_profit( fiat_display_currency = default_conf_usdt["fiat_display_currency"] rpc = RPC(freqtradebot) - rpc._fiat_converter = CryptoToFiatConverter() + rpc._fiat_converter = CryptoToFiatConverter({}) # Try valid data days = rpc._rpc_timeunit_profit(7, stake_currency, fiat_display_currency) @@ -344,7 +344,7 @@ def test_rpc_trade_history(mocker, default_conf, markets, fee, is_short): freqtradebot = get_patched_freqtradebot(mocker, default_conf) create_mock_trades(fee, is_short) rpc = RPC(freqtradebot) - rpc._fiat_converter = CryptoToFiatConverter() + rpc._fiat_converter = CryptoToFiatConverter({}) trades = rpc._rpc_trade_history(2) assert len(trades["trades"]) == 2 assert trades["trades_count"] == 2 @@ -434,7 +434,7 @@ def test_rpc_trade_statistics(default_conf_usdt, ticker, fee, mocker) -> None: fiat_display_currency = default_conf_usdt["fiat_display_currency"] rpc = RPC(freqtradebot) - rpc._fiat_converter = CryptoToFiatConverter() + rpc._fiat_converter = CryptoToFiatConverter({}) res = rpc._rpc_trade_statistics(stake_currency, fiat_display_currency) assert res["trade_count"] == 0 @@ -495,7 +495,7 @@ def test_rpc_balance_handle_error(default_conf, mocker): # ETH will be skipped due to mocked Error below mocker.patch.multiple( - "freqtrade.rpc.fiat_convert.CoinGeckoAPI", + "freqtrade.rpc.fiat_convert.FtCoinGeckoApi", get_price=MagicMock(return_value={"bitcoin": {"usd": 15000.0}}), ) mocker.patch("freqtrade.rpc.rpc.CryptoToFiatConverter._find_price", return_value=15000.0) @@ -509,7 +509,7 @@ def test_rpc_balance_handle_error(default_conf, mocker): freqtradebot = get_patched_freqtradebot(mocker, default_conf) patch_get_signal(freqtradebot) rpc = RPC(freqtradebot) - rpc._fiat_converter = CryptoToFiatConverter() + rpc._fiat_converter = CryptoToFiatConverter({}) with pytest.raises(RPCException, match="Error getting current tickers."): rpc._rpc_balance(default_conf["stake_currency"], default_conf["fiat_display_currency"]) @@ -558,7 +558,7 @@ def test_rpc_balance_handle(default_conf_usdt, mocker, tickers): ] mocker.patch.multiple( - "freqtrade.rpc.fiat_convert.CoinGeckoAPI", + "freqtrade.rpc.fiat_convert.FtCoinGeckoApi", get_price=MagicMock(return_value={"bitcoin": {"usd": 1.2}}), ) mocker.patch("freqtrade.rpc.rpc.CryptoToFiatConverter._find_price", return_value=1.2) @@ -578,7 +578,7 @@ def test_rpc_balance_handle(default_conf_usdt, mocker, tickers): freqtradebot = get_patched_freqtradebot(mocker, default_conf_usdt) patch_get_signal(freqtradebot) rpc = RPC(freqtradebot) - rpc._fiat_converter = CryptoToFiatConverter() + rpc._fiat_converter = CryptoToFiatConverter({}) result = rpc._rpc_balance( default_conf_usdt["stake_currency"], default_conf_usdt["fiat_display_currency"] diff --git a/tests/rpc/test_rpc_telegram.py b/tests/rpc/test_rpc_telegram.py index 3063e644b..6e4aa3384 100644 --- a/tests/rpc/test_rpc_telegram.py +++ b/tests/rpc/test_rpc_telegram.py @@ -2564,10 +2564,14 @@ def test_send_msg_buy_notification_no_fiat( ("Short", "short_signal_01", 2.0), ], ) +@pytest.mark.parametrize("fiat", ["", None]) def test_send_msg_exit_notification_no_fiat( - default_conf, mocker, direction, enter_signal, leverage, time_machine + default_conf, mocker, direction, enter_signal, leverage, time_machine, fiat ) -> None: - del default_conf["fiat_display_currency"] + if fiat is None: + del default_conf["fiat_display_currency"] + else: + default_conf["fiat_display_currency"] = fiat time_machine.move_to("2022-05-02 00:00:00 +00:00", tick=False) telegram, _, msg_mock = get_telegram_testobject(mocker, default_conf) diff --git a/tests/setup.Tests.ps1 b/tests/setup.Tests.ps1 new file mode 100644 index 000000000..e58a4729d --- /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 + } + } +} diff --git a/tests/strategy/strats/hyperoptable_strategy.py b/tests/strategy/strats/hyperoptable_strategy.py index bc71ed3ae..3a2b210be 100644 --- a/tests/strategy/strats/hyperoptable_strategy.py +++ b/tests/strategy/strats/hyperoptable_strategy.py @@ -52,7 +52,7 @@ class HyperoptableStrategy(StrategyTestV3): bot_loop_started = False bot_started = False - def bot_loop_start(self): + def bot_loop_start(self, **kwargs): self.bot_loop_started = True def bot_start(self, **kwargs) -> None: diff --git a/tests/strategy/strats/hyperoptable_strategy_v2.py b/tests/strategy/strats/hyperoptable_strategy_v2.py index 650c587f5..40e139e1f 100644 --- a/tests/strategy/strats/hyperoptable_strategy_v2.py +++ b/tests/strategy/strats/hyperoptable_strategy_v2.py @@ -48,7 +48,7 @@ class HyperoptableStrategyV2(StrategyTestV2): bot_loop_started = False - def bot_loop_start(self): + def bot_loop_start(self, **kwargs): self.bot_loop_started = True def bot_start(self, **kwargs) -> None: diff --git a/tests/test_configuration.py b/tests/test_configuration.py index 96d07a42e..0b4b37b25 100644 --- a/tests/test_configuration.py +++ b/tests/test_configuration.py @@ -659,6 +659,16 @@ def test_validate_default_conf(default_conf) -> None: validate_config_schema(default_conf) +@pytest.mark.parametrize("fiat", ["EUR", "USD", "", None]) +def test_validate_fiat_currency_options(default_conf, fiat) -> None: + # Validate via our validator - we allow setting defaults! + if fiat is not None: + default_conf["fiat_display_currency"] = fiat + else: + del default_conf["fiat_display_currency"] + validate_config_schema(default_conf) + + def test_validate_max_open_trades(default_conf): default_conf["max_open_trades"] = float("inf") default_conf["stake_amount"] = "unlimited"