diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml index fc8dbecbe..cb2174e74 100644 --- a/.github/workflows/ci.yml +++ b/.github/workflows/ci.yml @@ -24,7 +24,7 @@ jobs: runs-on: ${{ matrix.os }} strategy: matrix: - os: [ ubuntu-20.04, ubuntu-22.04 ] + os: [ "ubuntu-20.04", "ubuntu-22.04", "ubuntu-24.04" ] python-version: ["3.9", "3.10", "3.11", "3.12"] steps: @@ -322,7 +322,7 @@ jobs: run: | $PSVersionTable Set-PSRepository psgallery -InstallationPolicy trusted - Install-Module -Name Pester -RequiredVersion 5.3.1 -Confirm:$false -Force + Install-Module -Name Pester -RequiredVersion 5.3.1 -Confirm:$false -Force -SkipPublisherCheck $Error.clear() Invoke-Pester -Path "tests" -CI if ($Error.Length -gt 0) {exit 1} @@ -533,12 +533,12 @@ jobs: - name: Publish to PyPI (Test) - uses: pypa/gh-action-pypi-publish@v1.8.14 + uses: pypa/gh-action-pypi-publish@v1.9.0 with: repository-url: https://test.pypi.org/legacy/ - name: Publish to PyPI - uses: pypa/gh-action-pypi-publish@v1.8.14 + uses: pypa/gh-action-pypi-publish@v1.9.0 deploy-docker: diff --git a/.pre-commit-config.yaml b/.pre-commit-config.yaml index 992e9b373..6844925af 100644 --- a/.pre-commit-config.yaml +++ b/.pre-commit-config.yaml @@ -2,7 +2,7 @@ # See https://pre-commit.com/hooks.html for more hooks repos: - repo: https://github.com/pycqa/flake8 - rev: "7.0.0" + rev: "7.1.0" hooks: - id: flake8 additional_dependencies: [Flake8-pyproject] @@ -31,7 +31,7 @@ repos: - repo: https://github.com/charliermarsh/ruff-pre-commit # Ruff version. - rev: 'v0.4.7' + rev: 'v0.4.9' hooks: - id: ruff diff --git a/Dockerfile b/Dockerfile index 9491f2e05..c2bfbeb52 100644 --- a/Dockerfile +++ b/Dockerfile @@ -1,4 +1,4 @@ -FROM python:3.12.3-slim-bookworm as base +FROM python:3.12.4-slim-bookworm as base # Setup env ENV LANG C.UTF-8 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 deleted file mode 100644 index 68d6df6b3..000000000 Binary files a/build_helpers/TA_Lib-0.4.30-cp310-cp310-win_amd64.whl and /dev/null 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 deleted file mode 100644 index 941a2aa9d..000000000 Binary files a/build_helpers/TA_Lib-0.4.30-cp311-cp311-linux_armv7l.whl and /dev/null 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 deleted file mode 100644 index 2a8253e39..000000000 Binary files a/build_helpers/TA_Lib-0.4.30-cp312-cp312-win_amd64.whl and /dev/null 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 deleted file mode 100644 index a5cab7919..000000000 Binary files a/build_helpers/TA_Lib-0.4.30-cp39-cp39-linux_armv7l.whl and /dev/null 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 deleted file mode 100644 index a03c3ca8c..000000000 Binary files a/build_helpers/TA_Lib-0.4.30-cp39-cp39-win_amd64.whl and /dev/null differ diff --git a/build_helpers/TA_Lib-0.4.31-cp310-cp310-win_amd64.whl b/build_helpers/TA_Lib-0.4.31-cp310-cp310-win_amd64.whl new file mode 100644 index 000000000..4ce492a40 Binary files /dev/null and b/build_helpers/TA_Lib-0.4.31-cp310-cp310-win_amd64.whl differ diff --git a/build_helpers/TA_Lib-0.4.31-cp311-cp311-linux_armv7l.whl b/build_helpers/TA_Lib-0.4.31-cp311-cp311-linux_armv7l.whl new file mode 100644 index 000000000..a664f12e6 Binary files /dev/null and b/build_helpers/TA_Lib-0.4.31-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.31-cp311-cp311-win_amd64.whl similarity index 88% rename from build_helpers/TA_Lib-0.4.30-cp311-cp311-win_amd64.whl rename to build_helpers/TA_Lib-0.4.31-cp311-cp311-win_amd64.whl index 702f51559..e5b8cb4ef 100644 Binary files a/build_helpers/TA_Lib-0.4.30-cp311-cp311-win_amd64.whl and b/build_helpers/TA_Lib-0.4.31-cp311-cp311-win_amd64.whl differ diff --git a/build_helpers/TA_Lib-0.4.31-cp312-cp312-win_amd64.whl b/build_helpers/TA_Lib-0.4.31-cp312-cp312-win_amd64.whl new file mode 100644 index 000000000..79596cf0c Binary files /dev/null and b/build_helpers/TA_Lib-0.4.31-cp312-cp312-win_amd64.whl differ diff --git a/build_helpers/TA_Lib-0.4.31-cp39-cp39-linux_armv7l.whl b/build_helpers/TA_Lib-0.4.31-cp39-cp39-linux_armv7l.whl new file mode 100644 index 000000000..bceb21773 Binary files /dev/null and b/build_helpers/TA_Lib-0.4.31-cp39-cp39-linux_armv7l.whl differ diff --git a/build_helpers/TA_Lib-0.4.31-cp39-cp39-win_amd64.whl b/build_helpers/TA_Lib-0.4.31-cp39-cp39-win_amd64.whl new file mode 100644 index 000000000..01d3c626e Binary files /dev/null and b/build_helpers/TA_Lib-0.4.31-cp39-cp39-win_amd64.whl differ diff --git a/docs/backtesting.md b/docs/backtesting.md index 5fdfd6556..5adeae54b 100644 --- a/docs/backtesting.md +++ b/docs/backtesting.md @@ -253,36 +253,36 @@ A backtesting result will look like that: ``` ================================================ BACKTESTING REPORT ================================================= -| Pair | Entries | Avg Profit % | Tot Profit BTC | Tot Profit % | Avg Duration | Wins Draws Loss Win% | -|:---------|--------:|---------------:|-----------------:|---------------:|:-------------|-------------------------:| -| ADA/BTC | 35 | -0.11 | -0.00019428 | -1.94 | 4:35:00 | 14 0 21 40.0 | -| ARK/BTC | 11 | -0.41 | -0.00022647 | -2.26 | 2:03:00 | 3 0 8 27.3 | -| BTS/BTC | 32 | 0.31 | 0.00048938 | 4.89 | 5:05:00 | 18 0 14 56.2 | -| DASH/BTC | 13 | -0.08 | -0.00005343 | -0.53 | 4:39:00 | 6 0 7 46.2 | -| ENG/BTC | 18 | 1.36 | 0.00122807 | 12.27 | 2:50:00 | 8 0 10 44.4 | -| EOS/BTC | 36 | 0.08 | 0.00015304 | 1.53 | 3:34:00 | 16 0 20 44.4 | -| ETC/BTC | 26 | 0.37 | 0.00047576 | 4.75 | 6:14:00 | 11 0 15 42.3 | -| ETH/BTC | 33 | 0.30 | 0.00049856 | 4.98 | 7:31:00 | 16 0 17 48.5 | -| IOTA/BTC | 32 | 0.03 | 0.00005444 | 0.54 | 3:12:00 | 14 0 18 43.8 | -| LSK/BTC | 15 | 1.75 | 0.00131413 | 13.13 | 2:58:00 | 6 0 9 40.0 | -| LTC/BTC | 32 | -0.04 | -0.00006886 | -0.69 | 4:49:00 | 11 0 21 34.4 | -| NANO/BTC | 17 | 1.26 | 0.00107058 | 10.70 | 1:55:00 | 10 0 7 58.5 | -| NEO/BTC | 23 | 0.82 | 0.00094936 | 9.48 | 2:59:00 | 10 0 13 43.5 | -| REQ/BTC | 9 | 1.17 | 0.00052734 | 5.27 | 3:47:00 | 4 0 5 44.4 | -| XLM/BTC | 16 | 1.22 | 0.00097800 | 9.77 | 3:15:00 | 7 0 9 43.8 | -| XMR/BTC | 23 | -0.18 | -0.00020696 | -2.07 | 5:30:00 | 12 0 11 52.2 | -| XRP/BTC | 35 | 0.66 | 0.00114897 | 11.48 | 3:49:00 | 12 0 23 34.3 | -| ZEC/BTC | 22 | -0.46 | -0.00050971 | -5.09 | 2:22:00 | 7 0 15 31.8 | -| TOTAL | 429 | 0.36 | 0.00762792 | 76.20 | 4:12:00 | 186 0 243 43.4 | +| Pair | Trades | Avg Profit % | Tot Profit BTC | Tot Profit % | Avg Duration | Wins Draws Loss Win% | +|----------+--------+----------------+------------------+----------------+--------------+--------------------------| +| ADA/BTC | 35 | -0.11 | -0.00019428 | -1.94 | 4:35:00 | 14 0 21 40.0 | +| ARK/BTC | 11 | -0.41 | -0.00022647 | -2.26 | 2:03:00 | 3 0 8 27.3 | +| BTS/BTC | 32 | 0.31 | 0.00048938 | 4.89 | 5:05:00 | 18 0 14 56.2 | +| DASH/BTC | 13 | -0.08 | -0.00005343 | -0.53 | 4:39:00 | 6 0 7 46.2 | +| ENG/BTC | 18 | 1.36 | 0.00122807 | 12.27 | 2:50:00 | 8 0 10 44.4 | +| EOS/BTC | 36 | 0.08 | 0.00015304 | 1.53 | 3:34:00 | 16 0 20 44.4 | +| ETC/BTC | 26 | 0.37 | 0.00047576 | 4.75 | 6:14:00 | 11 0 15 42.3 | +| ETH/BTC | 33 | 0.30 | 0.00049856 | 4.98 | 7:31:00 | 16 0 17 48.5 | +| IOTA/BTC | 32 | 0.03 | 0.00005444 | 0.54 | 3:12:00 | 14 0 18 43.8 | +| LSK/BTC | 15 | 1.75 | 0.00131413 | 13.13 | 2:58:00 | 6 0 9 40.0 | +| LTC/BTC | 32 | -0.04 | -0.00006886 | -0.69 | 4:49:00 | 11 0 21 34.4 | +| NANO/BTC | 17 | 1.26 | 0.00107058 | 10.70 | 1:55:00 | 10 0 7 58.5 | +| NEO/BTC | 23 | 0.82 | 0.00094936 | 9.48 | 2:59:00 | 10 0 13 43.5 | +| REQ/BTC | 9 | 1.17 | 0.00052734 | 5.27 | 3:47:00 | 4 0 5 44.4 | +| XLM/BTC | 16 | 1.22 | 0.00097800 | 9.77 | 3:15:00 | 7 0 9 43.8 | +| XMR/BTC | 23 | -0.18 | -0.00020696 | -2.07 | 5:30:00 | 12 0 11 52.2 | +| XRP/BTC | 35 | 0.66 | 0.00114897 | 11.48 | 3:49:00 | 12 0 23 34.3 | +| ZEC/BTC | 22 | -0.46 | -0.00050971 | -5.09 | 2:22:00 | 7 0 15 31.8 | +| TOTAL | 429 | 0.36 | 0.00762792 | 76.20 | 4:12:00 | 186 0 243 43.4 | ============================================= LEFT OPEN TRADES REPORT ============================================= -| Pair | Entries | Avg Profit % | Tot Profit BTC | Tot Profit % | Avg Duration | Win Draw Loss Win% | -|:---------|---------:|---------------:|-----------------:|---------------:|:---------------|--------------------:| -| ADA/BTC | 1 | 0.89 | 0.00004434 | 0.44 | 6:00:00 | 1 0 0 100 | -| LTC/BTC | 1 | 0.68 | 0.00003421 | 0.34 | 2:00:00 | 1 0 0 100 | -| TOTAL | 2 | 0.78 | 0.00007855 | 0.78 | 4:00:00 | 2 0 0 100 | +| Pair | Trades | Avg Profit % | Tot Profit BTC | Tot Profit % | Avg Duration | Win Draw Loss Win% | +|----------+---------+----------------+------------------+----------------+----------------+---------------------| +| ADA/BTC | 1 | 0.89 | 0.00004434 | 0.44 | 6:00:00 | 1 0 0 100 | +| LTC/BTC | 1 | 0.68 | 0.00003421 | 0.34 | 2:00:00 | 1 0 0 100 | +| TOTAL | 2 | 0.78 | 0.00007855 | 0.78 | 4:00:00 | 2 0 0 100 | ==================== EXIT REASON STATS ==================== | Exit Reason | Exits | Wins | Draws | Losses | -|:-------------------|--------:|------:|-------:|--------:| +|--------------------+---------+-------+--------+---------| | trailing_stop_loss | 205 | 150 | 0 | 55 | | stop_loss | 166 | 0 | 0 | 166 | | exit_signal | 56 | 36 | 0 | 20 | @@ -631,10 +631,10 @@ Detailed output for all strategies one after the other will be available, so mak ``` ================================================== STRATEGY SUMMARY =================================================================== -| Strategy | Entries | Avg Profit % | Tot Profit BTC | Tot Profit % | Avg Duration | Wins | Draws | Losses | Drawdown % | -|:------------|---------:|---------------:|-----------------:|---------------:|:---------------|------:|-------:|-------:|-----------:| -| Strategy1 | 429 | 0.36 | 0.00762792 | 76.20 | 4:12:00 | 186 | 0 | 243 | 45.2 | -| Strategy2 | 1487 | -0.13 | -0.00988917 | -98.79 | 4:43:00 | 662 | 0 | 825 | 241.68 | +| Strategy | Trades | Avg Profit % | Tot Profit BTC | Tot Profit % | Avg Duration | Wins | Draws | Losses | Drawdown % | +|-------------+---------+----------------+------------------+----------------+----------------+-------+--------+--------+------------| +| Strategy1 | 429 | 0.36 | 0.00762792 | 76.20 | 4:12:00 | 186 | 0 | 243 | 45.2 | +| Strategy2 | 1487 | -0.13 | -0.00988917 | -98.79 | 4:43:00 | 662 | 0 | 825 | 241.68 | ``` ## Next step diff --git a/docs/requirements-docs.txt b/docs/requirements-docs.txt index c290fb852..845aba7bb 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.25 +mkdocs-material==9.5.27 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 2b55c2563..3b5c8928f 100644 --- a/docs/rest-api.md +++ b/docs/rest-api.md @@ -118,6 +118,14 @@ By default, the script assumes `127.0.0.1` (localhost) and port `8080` to be use freqtrade-client --config rest_config.json [optional parameters] ``` +Commands with many arguments may require keyword arguments (for clarity) - which can be provided as follows: + +``` bash +freqtrade-client --config rest_config.json forceenter BTC/USDT long enter_tag=GutFeeling +``` + +This method will work for all arguments - check the "show" command for a list of available parameters. + ??? Note "Programmatic use" The `freqtrade-client` package (installable independent of freqtrade) can be used in your own scripts to interact with the freqtrade API. to do so, please use the following: diff --git a/docs/strategy-callbacks.md b/docs/strategy-callbacks.md index b1e46d356..2a14a3c84 100644 --- a/docs/strategy-callbacks.md +++ b/docs/strategy-callbacks.md @@ -467,7 +467,7 @@ The helper function `stoploss_from_absolute()` can be used to convert from an ab ??? Example "Returning a stoploss using absolute price from the custom stoploss function" - If we want to trail a stop price at 2xATR below current price we can call `stoploss_from_absolute(current_rate + (side * candle['atr'] * 2), current_rate, is_short=trade.is_short, leverage=trade.leverage)`. + If we want to trail a stop price at 2xATR below current price we can call `stoploss_from_absolute(current_rate + (side * candle['atr'] * 2), current_rate=current_rate, is_short=trade.is_short, leverage=trade.leverage)`. For futures, we need to adjust the direction (up or down), as well as adjust for leverage, since the [`custom_stoploss`](strategy-callbacks.md#custom-stoploss) callback returns the ["risk for this trade"](stoploss.md#stoploss-and-leverage) - not the relative price movement. ``` python @@ -492,7 +492,8 @@ The helper function `stoploss_from_absolute()` can be used to convert from an ab candle = dataframe.iloc[-1].squeeze() side = 1 if trade.is_short else -1 return stoploss_from_absolute(current_rate + (side * candle['atr'] * 2), - current_rate, is_short=trade.is_short, + current_rate=current_rate, + is_short=trade.is_short, leverage=trade.leverage) ``` diff --git a/docs/trade-object.md b/docs/trade-object.md index fa8b2dbb1..ec9cf14ec 100644 --- a/docs/trade-object.md +++ b/docs/trade-object.md @@ -13,28 +13,28 @@ The following attributes / properties are available for each individual trade - | Attribute | DataType | Description | |------------|-------------|-------------| -`pair`| string | Pair of this trade -`is_open`| boolean | Is the trade currently open, or has it been concluded -`open_rate`| float | Rate this trade was entered at (Avg. entry rate in case of trade-adjustments) -`close_rate`| float | Close rate - only set when is_open = False -`stake_amount`| float | Amount in Stake (or Quote) currency. -`amount`| float | Amount in Asset / Base currency that is currently owned. -`open_date`| datetime | Timestamp when trade was opened **use `open_date_utc` instead** -`open_date_utc`| datetime | Timestamp when trade was opened - in UTC -`close_date`| datetime | Timestamp when trade was closed **use `close_date_utc` instead** -`close_date_utc`| datetime | Timestamp when trade was closed - in UTC -`close_profit`| float | Relative profit at the time of trade closure. `0.01` == 1% -`close_profit_abs`| float | Absolute profit (in stake currency) at the time of trade closure. -`leverage` | float | Leverage used for this trade - defaults to 1.0 in spot markets. -`enter_tag`| string | Tag provided on entry via the `enter_tag` column in the dataframe -`is_short` | boolean | True for short trades, False otherwise -`orders` | Order[] | List of order objects attached to this trade (includes both filled and cancelled orders) -`date_last_filled_utc` | datetime | Time of the last filled order -`entry_side` | "buy" / "sell" | Order Side the trade was entered -`exit_side` | "buy" / "sell" | Order Side that will result in a trade exit / position reduction. -`trade_direction` | "long" / "short" | Trade direction in text - long or short. -`nr_of_successful_entries` | int | Number of successful (filled) entry orders -`nr_of_successful_exits` | int | Number of successful (filled) exit orders +| `pair` | string | Pair of this trade. | +| `is_open` | boolean | Is the trade currently open, or has it been concluded. | +| `open_rate` | float | Rate this trade was entered at (Avg. entry rate in case of trade-adjustments). | +| `close_rate` | float | Close rate - only set when is_open = False. | +| `stake_amount` | float | Amount in Stake (or Quote) currency. | +| `amount` | float | Amount in Asset / Base currency that is currently owned. | +| `open_date` | datetime | Timestamp when trade was opened **use `open_date_utc` instead** | +| `open_date_utc` | datetime | Timestamp when trade was opened - in UTC. | +| `close_date` | datetime | Timestamp when trade was closed **use `close_date_utc` instead** | +| `close_date_utc` | datetime | Timestamp when trade was closed - in UTC. | +| `close_profit` | float | Relative profit at the time of trade closure. `0.01` == 1% | +| `close_profit_abs` | float | Absolute profit (in stake currency) at the time of trade closure. | +| `leverage` | float | Leverage used for this trade - defaults to 1.0 in spot markets. | +| `enter_tag` | string | Tag provided on entry via the `enter_tag` column in the dataframe. | +| `is_short` | boolean | True for short trades, False otherwise. | +| `orders` | Order[] | List of order objects attached to this trade (includes both filled and cancelled orders). | +| `date_last_filled_utc` | datetime | Time of the last filled order. | +| `entry_side` | "buy" / "sell" | Order Side the trade was entered. | +| `exit_side` | "buy" / "sell" | Order Side that will result in a trade exit / position reduction. | +| `trade_direction` | "long" / "short" | Trade direction in text - long or short. | +| `nr_of_successful_entries` | int | Number of successful (filled) entry orders. | +| `nr_of_successful_exits` | int | Number of successful (filled) exit orders. | ## Class methods diff --git a/freqtrade/__init__.py b/freqtrade/__init__.py index 932f9b701..5c4e6df5d 100644 --- a/freqtrade/__init__.py +++ b/freqtrade/__init__.py @@ -30,5 +30,5 @@ if "dev" in __version__: versionfile = Path("./freqtrade_commit") if versionfile.is_file(): __version__ = f"docker-{__version__}-{versionfile.read_text()[:8]}" - except Exception: + except Exception: # noqa: S110 pass diff --git a/freqtrade/commands/build_config_commands.py b/freqtrade/commands/build_config_commands.py index 1e771a372..a5ab8cb41 100644 --- a/freqtrade/commands/build_config_commands.py +++ b/freqtrade/commands/build_config_commands.py @@ -187,7 +187,7 @@ def ask_user_config() -> Dict[str, Any]: "Insert Api server Listen Address (0.0.0.0 for docker, " "otherwise best left untouched)" ), - "default": "127.0.0.1" if not running_in_docker() else "0.0.0.0", + "default": "127.0.0.1" if not running_in_docker() else "0.0.0.0", # noqa: S104 "when": lambda x: x["api_server"], }, { diff --git a/freqtrade/configuration/configuration.py b/freqtrade/configuration/configuration.py index cc8b5407e..184f9decf 100644 --- a/freqtrade/configuration/configuration.py +++ b/freqtrade/configuration/configuration.py @@ -2,6 +2,7 @@ This module contains the configuration class """ +import ast import logging import warnings from copy import deepcopy @@ -301,7 +302,7 @@ class Configuration: # Edge section: if "stoploss_range" in self.args and self.args["stoploss_range"]: - txt_range = eval(self.args["stoploss_range"]) + txt_range = ast.literal_eval(self.args["stoploss_range"]) config["edge"].update({"stoploss_range_min": txt_range[0]}) config["edge"].update({"stoploss_range_max": txt_range[1]}) config["edge"].update({"stoploss_range_step": txt_range[2]}) diff --git a/freqtrade/exchange/binance_leverage_tiers.json b/freqtrade/exchange/binance_leverage_tiers.json index 71744bf36..cae0a1074 100644 --- a/freqtrade/exchange/binance_leverage_tiers.json +++ b/freqtrade/exchange/binance_leverage_tiers.json @@ -150,13 +150,13 @@ "tier": 2.0, "currency": "USDT", "minNotional": 5000.0, - "maxNotional": 25000.0, + "maxNotional": 50000.0, "maintenanceMarginRate": 0.02, "maxLeverage": 25.0, "info": { "bracket": "2", "initialLeverage": "25", - "notionalCap": "25000", + "notionalCap": "50000", "notionalFloor": "5000", "maintMarginRatio": "0.02", "cum": "25.0" @@ -165,97 +165,97 @@ { "tier": 3.0, "currency": "USDT", - "minNotional": 25000.0, - "maxNotional": 50000.0, + "minNotional": 50000.0, + "maxNotional": 100000.0, "maintenanceMarginRate": 0.025, "maxLeverage": 20.0, "info": { "bracket": "3", "initialLeverage": "20", - "notionalCap": "50000", - "notionalFloor": "25000", + "notionalCap": "100000", + "notionalFloor": "50000", "maintMarginRatio": "0.025", - "cum": "150.0" + "cum": "275.0" } }, { "tier": 4.0, "currency": "USDT", - "minNotional": 50000.0, - "maxNotional": 500000.0, + "minNotional": 100000.0, + "maxNotional": 1000000.0, "maintenanceMarginRate": 0.05, "maxLeverage": 10.0, "info": { "bracket": "4", "initialLeverage": "10", - "notionalCap": "500000", - "notionalFloor": "50000", + "notionalCap": "1000000", + "notionalFloor": "100000", "maintMarginRatio": "0.05", - "cum": "1400.0" + "cum": "2775.0" } }, { "tier": 5.0, "currency": "USDT", - "minNotional": 500000.0, - "maxNotional": 1000000.0, + "minNotional": 1000000.0, + "maxNotional": 2000000.0, "maintenanceMarginRate": 0.1, "maxLeverage": 5.0, "info": { "bracket": "5", "initialLeverage": "5", - "notionalCap": "1000000", - "notionalFloor": "500000", + "notionalCap": "2000000", + "notionalFloor": "1000000", "maintMarginRatio": "0.1", - "cum": "26400.0" + "cum": "52775.0" } }, { "tier": 6.0, "currency": "USDT", - "minNotional": 1000000.0, - "maxNotional": 1250000.0, + "minNotional": 2000000.0, + "maxNotional": 2500000.0, "maintenanceMarginRate": 0.125, "maxLeverage": 4.0, "info": { "bracket": "6", "initialLeverage": "4", - "notionalCap": "1250000", - "notionalFloor": "1000000", + "notionalCap": "2500000", + "notionalFloor": "2000000", "maintMarginRatio": "0.125", - "cum": "51400.0" + "cum": "102775.0" } }, { "tier": 7.0, "currency": "USDT", - "minNotional": 1250000.0, - "maxNotional": 2500000.0, + "minNotional": 2500000.0, + "maxNotional": 5000000.0, "maintenanceMarginRate": 0.25, "maxLeverage": 2.0, "info": { "bracket": "7", "initialLeverage": "2", - "notionalCap": "2500000", - "notionalFloor": "1250000", + "notionalCap": "5000000", + "notionalFloor": "2500000", "maintMarginRatio": "0.25", - "cum": "207650.0" + "cum": "415275.0" } }, { "tier": 8.0, "currency": "USDT", - "minNotional": 2500000.0, - "maxNotional": 5000000.0, + "minNotional": 5000000.0, + "maxNotional": 10000000.0, "maintenanceMarginRate": 0.5, "maxLeverage": 1.0, "info": { "bracket": "8", "initialLeverage": "1", - "notionalCap": "5000000", - "notionalFloor": "2500000", + "notionalCap": "10000000", + "notionalFloor": "5000000", "maintMarginRatio": "0.5", - "cum": "832650.0" + "cum": "1665275.0" } } ], @@ -265,14 +265,14 @@ "currency": "USDT", "minNotional": 0.0, "maxNotional": 5000.0, - "maintenanceMarginRate": 0.02, - "maxLeverage": 20.0, + "maintenanceMarginRate": 0.015, + "maxLeverage": 50.0, "info": { "bracket": "1", - "initialLeverage": "20", + "initialLeverage": "50", "notionalCap": "5000", "notionalFloor": "0", - "maintMarginRatio": "0.02", + "maintMarginRatio": "0.015", "cum": "0.0" } }, @@ -280,96 +280,112 @@ "tier": 2.0, "currency": "USDT", "minNotional": 5000.0, - "maxNotional": 25000.0, - "maintenanceMarginRate": 0.025, - "maxLeverage": 15.0, + "maxNotional": 50000.0, + "maintenanceMarginRate": 0.02, + "maxLeverage": 25.0, "info": { "bracket": "2", - "initialLeverage": "15", - "notionalCap": "25000", + "initialLeverage": "25", + "notionalCap": "50000", "notionalFloor": "5000", - "maintMarginRatio": "0.025", + "maintMarginRatio": "0.02", "cum": "25.0" } }, { "tier": 3.0, "currency": "USDT", - "minNotional": 25000.0, - "maxNotional": 300000.0, - "maintenanceMarginRate": 0.05, - "maxLeverage": 10.0, + "minNotional": 50000.0, + "maxNotional": 100000.0, + "maintenanceMarginRate": 0.025, + "maxLeverage": 20.0, "info": { "bracket": "3", - "initialLeverage": "10", - "notionalCap": "300000", - "notionalFloor": "25000", - "maintMarginRatio": "0.05", - "cum": "650.0" + "initialLeverage": "20", + "notionalCap": "100000", + "notionalFloor": "50000", + "maintMarginRatio": "0.025", + "cum": "275.0" } }, { "tier": 4.0, "currency": "USDT", - "minNotional": 300000.0, - "maxNotional": 800000.0, - "maintenanceMarginRate": 0.1, - "maxLeverage": 5.0, + "minNotional": 100000.0, + "maxNotional": 1000000.0, + "maintenanceMarginRate": 0.05, + "maxLeverage": 10.0, "info": { "bracket": "4", - "initialLeverage": "5", - "notionalCap": "800000", - "notionalFloor": "300000", - "maintMarginRatio": "0.1", - "cum": "15650.0" + "initialLeverage": "10", + "notionalCap": "1000000", + "notionalFloor": "100000", + "maintMarginRatio": "0.05", + "cum": "2775.0" } }, { "tier": 5.0, "currency": "USDT", - "minNotional": 800000.0, - "maxNotional": 1000000.0, - "maintenanceMarginRate": 0.125, - "maxLeverage": 4.0, + "minNotional": 1000000.0, + "maxNotional": 2000000.0, + "maintenanceMarginRate": 0.1, + "maxLeverage": 5.0, "info": { "bracket": "5", - "initialLeverage": "4", - "notionalCap": "1000000", - "notionalFloor": "800000", - "maintMarginRatio": "0.125", - "cum": "35650.0" + "initialLeverage": "5", + "notionalCap": "2000000", + "notionalFloor": "1000000", + "maintMarginRatio": "0.1", + "cum": "52775.0" } }, { "tier": 6.0, "currency": "USDT", - "minNotional": 1000000.0, - "maxNotional": 3000000.0, - "maintenanceMarginRate": 0.25, - "maxLeverage": 2.0, + "minNotional": 2000000.0, + "maxNotional": 2500000.0, + "maintenanceMarginRate": 0.125, + "maxLeverage": 4.0, "info": { "bracket": "6", - "initialLeverage": "2", - "notionalCap": "3000000", - "notionalFloor": "1000000", - "maintMarginRatio": "0.25", - "cum": "160650.0" + "initialLeverage": "4", + "notionalCap": "2500000", + "notionalFloor": "2000000", + "maintMarginRatio": "0.125", + "cum": "102775.0" } }, { "tier": 7.0, "currency": "USDT", - "minNotional": 3000000.0, + "minNotional": 2500000.0, "maxNotional": 5000000.0, + "maintenanceMarginRate": 0.25, + "maxLeverage": 2.0, + "info": { + "bracket": "7", + "initialLeverage": "2", + "notionalCap": "5000000", + "notionalFloor": "2500000", + "maintMarginRatio": "0.25", + "cum": "415275.0" + } + }, + { + "tier": 8.0, + "currency": "USDT", + "minNotional": 5000000.0, + "maxNotional": 10000000.0, "maintenanceMarginRate": 0.5, "maxLeverage": 1.0, "info": { - "bracket": "7", + "bracket": "8", "initialLeverage": "1", - "notionalCap": "5000000", - "notionalFloor": "3000000", + "notionalCap": "10000000", + "notionalFloor": "5000000", "maintMarginRatio": "0.5", - "cum": "910650.0" + "cum": "1665275.0" } } ], @@ -5877,95 +5893,111 @@ "currency": "USDT", "minNotional": 5000.0, "maxNotional": 25000.0, - "maintenanceMarginRate": 0.025, - "maxLeverage": 20.0, + "maintenanceMarginRate": 0.02, + "maxLeverage": 25.0, "info": { "bracket": "2", - "initialLeverage": "20", + "initialLeverage": "25", "notionalCap": "25000", "notionalFloor": "5000", - "maintMarginRatio": "0.025", - "cum": "50.0" + "maintMarginRatio": "0.02", + "cum": "25.0" } }, { "tier": 3.0, "currency": "USDT", "minNotional": 25000.0, - "maxNotional": 100000.0, - "maintenanceMarginRate": 0.05, - "maxLeverage": 10.0, + "maxNotional": 80000.0, + "maintenanceMarginRate": 0.025, + "maxLeverage": 20.0, "info": { "bracket": "3", - "initialLeverage": "10", - "notionalCap": "100000", + "initialLeverage": "20", + "notionalCap": "80000", "notionalFloor": "25000", - "maintMarginRatio": "0.05", - "cum": "675.0" + "maintMarginRatio": "0.025", + "cum": "150.0" } }, { "tier": 4.0, "currency": "USDT", - "minNotional": 100000.0, - "maxNotional": 200000.0, - "maintenanceMarginRate": 0.1, - "maxLeverage": 5.0, + "minNotional": 80000.0, + "maxNotional": 800000.0, + "maintenanceMarginRate": 0.05, + "maxLeverage": 10.0, "info": { "bracket": "4", - "initialLeverage": "5", - "notionalCap": "200000", - "notionalFloor": "100000", - "maintMarginRatio": "0.1", - "cum": "5675.0" + "initialLeverage": "10", + "notionalCap": "800000", + "notionalFloor": "80000", + "maintMarginRatio": "0.05", + "cum": "2150.0" } }, { "tier": 5.0, "currency": "USDT", - "minNotional": 200000.0, - "maxNotional": 500000.0, - "maintenanceMarginRate": 0.125, - "maxLeverage": 4.0, + "minNotional": 800000.0, + "maxNotional": 1600000.0, + "maintenanceMarginRate": 0.1, + "maxLeverage": 5.0, "info": { "bracket": "5", - "initialLeverage": "4", - "notionalCap": "500000", - "notionalFloor": "200000", - "maintMarginRatio": "0.125", - "cum": "10675.0" + "initialLeverage": "5", + "notionalCap": "1600000", + "notionalFloor": "800000", + "maintMarginRatio": "0.1", + "cum": "42150.0" } }, { "tier": 6.0, "currency": "USDT", - "minNotional": 500000.0, - "maxNotional": 1000000.0, - "maintenanceMarginRate": 0.25, - "maxLeverage": 2.0, + "minNotional": 1600000.0, + "maxNotional": 2000000.0, + "maintenanceMarginRate": 0.125, + "maxLeverage": 4.0, "info": { "bracket": "6", - "initialLeverage": "2", - "notionalCap": "1000000", - "notionalFloor": "500000", - "maintMarginRatio": "0.25", - "cum": "73175.0" + "initialLeverage": "4", + "notionalCap": "2000000", + "notionalFloor": "1600000", + "maintMarginRatio": "0.125", + "cum": "82150.0" } }, { "tier": 7.0, "currency": "USDT", - "minNotional": 1000000.0, - "maxNotional": 2000000.0, + "minNotional": 2000000.0, + "maxNotional": 4000000.0, + "maintenanceMarginRate": 0.25, + "maxLeverage": 2.0, + "info": { + "bracket": "7", + "initialLeverage": "2", + "notionalCap": "4000000", + "notionalFloor": "2000000", + "maintMarginRatio": "0.25", + "cum": "332150.0" + } + }, + { + "tier": 8.0, + "currency": "USDT", + "minNotional": 4000000.0, + "maxNotional": 8000000.0, "maintenanceMarginRate": 0.5, "maxLeverage": 1.0, "info": { - "bracket": "7", + "bracket": "8", "initialLeverage": "1", - "notionalCap": "2000000", - "notionalFloor": "1000000", + "notionalCap": "8000000", + "notionalFloor": "4000000", "maintMarginRatio": "0.5", - "cum": "323175.0" + "cum": "1332150.0" } } ], @@ -17797,6 +17829,136 @@ } } ], + "IO/USDT:USDT": [ + { + "tier": 1.0, + "currency": "USDT", + "minNotional": 0.0, + "maxNotional": 5000.0, + "maintenanceMarginRate": 0.015, + "maxLeverage": 50.0, + "info": { + "bracket": "1", + "initialLeverage": "50", + "notionalCap": "5000", + "notionalFloor": "0", + "maintMarginRatio": "0.015", + "cum": "0.0" + } + }, + { + "tier": 2.0, + "currency": "USDT", + "minNotional": 5000.0, + "maxNotional": 20000.0, + "maintenanceMarginRate": 0.02, + "maxLeverage": 25.0, + "info": { + "bracket": "2", + "initialLeverage": "25", + "notionalCap": "20000", + "notionalFloor": "5000", + "maintMarginRatio": "0.02", + "cum": "25.0" + } + }, + { + "tier": 3.0, + "currency": "USDT", + "minNotional": 20000.0, + "maxNotional": 25000.0, + "maintenanceMarginRate": 0.025, + "maxLeverage": 20.0, + "info": { + "bracket": "3", + "initialLeverage": "20", + "notionalCap": "25000", + "notionalFloor": "20000", + "maintMarginRatio": "0.025", + "cum": "125.0" + } + }, + { + "tier": 4.0, + "currency": "USDT", + "minNotional": 25000.0, + "maxNotional": 200000.0, + "maintenanceMarginRate": 0.05, + "maxLeverage": 10.0, + "info": { + "bracket": "4", + "initialLeverage": "10", + "notionalCap": "200000", + "notionalFloor": "25000", + "maintMarginRatio": "0.05", + "cum": "750.0" + } + }, + { + "tier": 5.0, + "currency": "USDT", + "minNotional": 200000.0, + "maxNotional": 400000.0, + "maintenanceMarginRate": 0.1, + "maxLeverage": 5.0, + "info": { + "bracket": "5", + "initialLeverage": "5", + "notionalCap": "400000", + "notionalFloor": "200000", + "maintMarginRatio": "0.1", + "cum": "10750.0" + } + }, + { + "tier": 6.0, + "currency": "USDT", + "minNotional": 400000.0, + "maxNotional": 500000.0, + "maintenanceMarginRate": 0.125, + "maxLeverage": 4.0, + "info": { + "bracket": "6", + "initialLeverage": "4", + "notionalCap": "500000", + "notionalFloor": "400000", + "maintMarginRatio": "0.125", + "cum": "20750.0" + } + }, + { + "tier": 7.0, + "currency": "USDT", + "minNotional": 500000.0, + "maxNotional": 1000000.0, + "maintenanceMarginRate": 0.25, + "maxLeverage": 2.0, + "info": { + "bracket": "7", + "initialLeverage": "2", + "notionalCap": "1000000", + "notionalFloor": "500000", + "maintMarginRatio": "0.25", + "cum": "83250.0" + } + }, + { + "tier": 8.0, + "currency": "USDT", + "minNotional": 1000000.0, + "maxNotional": 2000000.0, + "maintenanceMarginRate": 0.5, + "maxLeverage": 1.0, + "info": { + "bracket": "8", + "initialLeverage": "1", + "notionalCap": "2000000", + "notionalFloor": "1000000", + "maintMarginRatio": "0.5", + "cum": "333250.0" + } + } + ], "IOST/USDT:USDT": [ { "tier": 1.0, @@ -23822,96 +23984,112 @@ "tier": 2.0, "currency": "USDT", "minNotional": 5000.0, - "maxNotional": 25000.0, - "maintenanceMarginRate": 0.025, - "maxLeverage": 20.0, + "maxNotional": 50000.0, + "maintenanceMarginRate": 0.02, + "maxLeverage": 25.0, "info": { "bracket": "2", - "initialLeverage": "20", - "notionalCap": "25000", + "initialLeverage": "25", + "notionalCap": "50000", "notionalFloor": "5000", - "maintMarginRatio": "0.025", - "cum": "50.0" + "maintMarginRatio": "0.02", + "cum": "25.0" } }, { "tier": 3.0, "currency": "USDT", - "minNotional": 25000.0, + "minNotional": 50000.0, "maxNotional": 100000.0, - "maintenanceMarginRate": 0.05, - "maxLeverage": 10.0, + "maintenanceMarginRate": 0.025, + "maxLeverage": 20.0, "info": { "bracket": "3", - "initialLeverage": "10", + "initialLeverage": "20", "notionalCap": "100000", - "notionalFloor": "25000", - "maintMarginRatio": "0.05", - "cum": "675.0" + "notionalFloor": "50000", + "maintMarginRatio": "0.025", + "cum": "275.0" } }, { "tier": 4.0, "currency": "USDT", "minNotional": 100000.0, - "maxNotional": 200000.0, - "maintenanceMarginRate": 0.1, - "maxLeverage": 5.0, + "maxNotional": 1000000.0, + "maintenanceMarginRate": 0.05, + "maxLeverage": 10.0, "info": { "bracket": "4", - "initialLeverage": "5", - "notionalCap": "200000", + "initialLeverage": "10", + "notionalCap": "1000000", "notionalFloor": "100000", - "maintMarginRatio": "0.1", - "cum": "5675.0" + "maintMarginRatio": "0.05", + "cum": "2775.0" } }, { "tier": 5.0, "currency": "USDT", - "minNotional": 200000.0, - "maxNotional": 500000.0, - "maintenanceMarginRate": 0.125, - "maxLeverage": 4.0, + "minNotional": 1000000.0, + "maxNotional": 2000000.0, + "maintenanceMarginRate": 0.1, + "maxLeverage": 5.0, "info": { "bracket": "5", - "initialLeverage": "4", - "notionalCap": "500000", - "notionalFloor": "200000", - "maintMarginRatio": "0.125", - "cum": "10675.0" + "initialLeverage": "5", + "notionalCap": "2000000", + "notionalFloor": "1000000", + "maintMarginRatio": "0.1", + "cum": "52775.0" } }, { "tier": 6.0, "currency": "USDT", - "minNotional": 500000.0, - "maxNotional": 1000000.0, - "maintenanceMarginRate": 0.25, - "maxLeverage": 2.0, + "minNotional": 2000000.0, + "maxNotional": 2500000.0, + "maintenanceMarginRate": 0.125, + "maxLeverage": 4.0, "info": { "bracket": "6", - "initialLeverage": "2", - "notionalCap": "1000000", - "notionalFloor": "500000", - "maintMarginRatio": "0.25", - "cum": "73175.0" + "initialLeverage": "4", + "notionalCap": "2500000", + "notionalFloor": "2000000", + "maintMarginRatio": "0.125", + "cum": "102775.0" } }, { "tier": 7.0, "currency": "USDT", - "minNotional": 1000000.0, - "maxNotional": 2000000.0, + "minNotional": 2500000.0, + "maxNotional": 5000000.0, + "maintenanceMarginRate": 0.25, + "maxLeverage": 2.0, + "info": { + "bracket": "7", + "initialLeverage": "2", + "notionalCap": "5000000", + "notionalFloor": "2500000", + "maintMarginRatio": "0.25", + "cum": "415275.0" + } + }, + { + "tier": 8.0, + "currency": "USDT", + "minNotional": 5000000.0, + "maxNotional": 10000000.0, "maintenanceMarginRate": 0.5, "maxLeverage": 1.0, "info": { - "bracket": "7", + "bracket": "8", "initialLeverage": "1", - "notionalCap": "2000000", - "notionalFloor": "1000000", + "notionalCap": "10000000", + "notionalFloor": "5000000", "maintMarginRatio": "0.5", - "cum": "323175.0" + "cum": "1665275.0" } } ], @@ -24133,14 +24311,14 @@ "currency": "USDT", "minNotional": 0.0, "maxNotional": 5000.0, - "maintenanceMarginRate": 0.02, - "maxLeverage": 20.0, + "maintenanceMarginRate": 0.015, + "maxLeverage": 50.0, "info": { "bracket": "1", - "initialLeverage": "20", + "initialLeverage": "50", "notionalCap": "5000", "notionalFloor": "0", - "maintMarginRatio": "0.02", + "maintMarginRatio": "0.015", "cum": "0.0" } }, @@ -24148,80 +24326,112 @@ "tier": 2.0, "currency": "USDT", "minNotional": 5000.0, - "maxNotional": 25000.0, - "maintenanceMarginRate": 0.025, - "maxLeverage": 10.0, + "maxNotional": 20000.0, + "maintenanceMarginRate": 0.02, + "maxLeverage": 25.0, "info": { "bracket": "2", - "initialLeverage": "10", - "notionalCap": "25000", + "initialLeverage": "25", + "notionalCap": "20000", "notionalFloor": "5000", - "maintMarginRatio": "0.025", + "maintMarginRatio": "0.02", "cum": "25.0" } }, { "tier": 3.0, "currency": "USDT", - "minNotional": 25000.0, - "maxNotional": 100000.0, - "maintenanceMarginRate": 0.05, - "maxLeverage": 8.0, + "minNotional": 20000.0, + "maxNotional": 25000.0, + "maintenanceMarginRate": 0.025, + "maxLeverage": 20.0, "info": { "bracket": "3", - "initialLeverage": "8", - "notionalCap": "100000", - "notionalFloor": "25000", - "maintMarginRatio": "0.05", - "cum": "650.0" + "initialLeverage": "20", + "notionalCap": "25000", + "notionalFloor": "20000", + "maintMarginRatio": "0.025", + "cum": "125.0" } }, { "tier": 4.0, "currency": "USDT", - "minNotional": 100000.0, - "maxNotional": 250000.0, - "maintenanceMarginRate": 0.1, - "maxLeverage": 5.0, + "minNotional": 25000.0, + "maxNotional": 200000.0, + "maintenanceMarginRate": 0.05, + "maxLeverage": 10.0, "info": { "bracket": "4", - "initialLeverage": "5", - "notionalCap": "250000", - "notionalFloor": "100000", - "maintMarginRatio": "0.1", - "cum": "5650.0" + "initialLeverage": "10", + "notionalCap": "200000", + "notionalFloor": "25000", + "maintMarginRatio": "0.05", + "cum": "750.0" } }, { "tier": 5.0, "currency": "USDT", - "minNotional": 250000.0, - "maxNotional": 1000000.0, - "maintenanceMarginRate": 0.125, - "maxLeverage": 2.0, + "minNotional": 200000.0, + "maxNotional": 400000.0, + "maintenanceMarginRate": 0.1, + "maxLeverage": 5.0, "info": { "bracket": "5", - "initialLeverage": "2", - "notionalCap": "1000000", - "notionalFloor": "250000", - "maintMarginRatio": "0.125", - "cum": "11900.0" + "initialLeverage": "5", + "notionalCap": "400000", + "notionalFloor": "200000", + "maintMarginRatio": "0.1", + "cum": "10750.0" } }, { "tier": 6.0, "currency": "USDT", + "minNotional": 400000.0, + "maxNotional": 500000.0, + "maintenanceMarginRate": 0.125, + "maxLeverage": 4.0, + "info": { + "bracket": "6", + "initialLeverage": "4", + "notionalCap": "500000", + "notionalFloor": "400000", + "maintMarginRatio": "0.125", + "cum": "20750.0" + } + }, + { + "tier": 7.0, + "currency": "USDT", + "minNotional": 500000.0, + "maxNotional": 1000000.0, + "maintenanceMarginRate": 0.25, + "maxLeverage": 2.0, + "info": { + "bracket": "7", + "initialLeverage": "2", + "notionalCap": "1000000", + "notionalFloor": "500000", + "maintMarginRatio": "0.25", + "cum": "83250.0" + } + }, + { + "tier": 8.0, + "currency": "USDT", "minNotional": 1000000.0, - "maxNotional": 3000000.0, + "maxNotional": 2000000.0, "maintenanceMarginRate": 0.5, "maxLeverage": 1.0, "info": { - "bracket": "6", + "bracket": "8", "initialLeverage": "1", - "notionalCap": "3000000", + "notionalCap": "2000000", "notionalFloor": "1000000", "maintMarginRatio": "0.5", - "cum": "386900.0" + "cum": "333250.0" } } ], @@ -24719,14 +24929,14 @@ "currency": "USDT", "minNotional": 0.0, "maxNotional": 5000.0, - "maintenanceMarginRate": 0.01, - "maxLeverage": 20.0, + "maintenanceMarginRate": 0.015, + "maxLeverage": 50.0, "info": { "bracket": "1", - "initialLeverage": "20", + "initialLeverage": "50", "notionalCap": "5000", "notionalFloor": "0", - "maintMarginRatio": "0.01", + "maintMarginRatio": "0.015", "cum": "0.0" } }, @@ -24734,80 +24944,112 @@ "tier": 2.0, "currency": "USDT", "minNotional": 5000.0, - "maxNotional": 25000.0, - "maintenanceMarginRate": 0.025, - "maxLeverage": 10.0, + "maxNotional": 20000.0, + "maintenanceMarginRate": 0.02, + "maxLeverage": 25.0, "info": { "bracket": "2", - "initialLeverage": "10", - "notionalCap": "25000", + "initialLeverage": "25", + "notionalCap": "20000", "notionalFloor": "5000", - "maintMarginRatio": "0.025", - "cum": "75.0" + "maintMarginRatio": "0.02", + "cum": "25.0" } }, { "tier": 3.0, "currency": "USDT", - "minNotional": 25000.0, - "maxNotional": 100000.0, - "maintenanceMarginRate": 0.05, - "maxLeverage": 8.0, + "minNotional": 20000.0, + "maxNotional": 25000.0, + "maintenanceMarginRate": 0.025, + "maxLeverage": 20.0, "info": { "bracket": "3", - "initialLeverage": "8", - "notionalCap": "100000", - "notionalFloor": "25000", - "maintMarginRatio": "0.05", - "cum": "700.0" + "initialLeverage": "20", + "notionalCap": "25000", + "notionalFloor": "20000", + "maintMarginRatio": "0.025", + "cum": "125.0" } }, { "tier": 4.0, "currency": "USDT", - "minNotional": 100000.0, - "maxNotional": 250000.0, - "maintenanceMarginRate": 0.1, - "maxLeverage": 5.0, + "minNotional": 25000.0, + "maxNotional": 200000.0, + "maintenanceMarginRate": 0.05, + "maxLeverage": 10.0, "info": { "bracket": "4", - "initialLeverage": "5", - "notionalCap": "250000", - "notionalFloor": "100000", - "maintMarginRatio": "0.1", - "cum": "5700.0" + "initialLeverage": "10", + "notionalCap": "200000", + "notionalFloor": "25000", + "maintMarginRatio": "0.05", + "cum": "750.0" } }, { "tier": 5.0, "currency": "USDT", - "minNotional": 250000.0, - "maxNotional": 1000000.0, - "maintenanceMarginRate": 0.125, - "maxLeverage": 2.0, + "minNotional": 200000.0, + "maxNotional": 400000.0, + "maintenanceMarginRate": 0.1, + "maxLeverage": 5.0, "info": { "bracket": "5", - "initialLeverage": "2", - "notionalCap": "1000000", - "notionalFloor": "250000", - "maintMarginRatio": "0.125", - "cum": "11950.0" + "initialLeverage": "5", + "notionalCap": "400000", + "notionalFloor": "200000", + "maintMarginRatio": "0.1", + "cum": "10750.0" } }, { "tier": 6.0, "currency": "USDT", + "minNotional": 400000.0, + "maxNotional": 500000.0, + "maintenanceMarginRate": 0.125, + "maxLeverage": 4.0, + "info": { + "bracket": "6", + "initialLeverage": "4", + "notionalCap": "500000", + "notionalFloor": "400000", + "maintMarginRatio": "0.125", + "cum": "20750.0" + } + }, + { + "tier": 7.0, + "currency": "USDT", + "minNotional": 500000.0, + "maxNotional": 1000000.0, + "maintenanceMarginRate": 0.25, + "maxLeverage": 2.0, + "info": { + "bracket": "7", + "initialLeverage": "2", + "notionalCap": "1000000", + "notionalFloor": "500000", + "maintMarginRatio": "0.25", + "cum": "83250.0" + } + }, + { + "tier": 8.0, + "currency": "USDT", "minNotional": 1000000.0, - "maxNotional": 5000000.0, + "maxNotional": 2000000.0, "maintenanceMarginRate": 0.5, "maxLeverage": 1.0, "info": { - "bracket": "6", + "bracket": "8", "initialLeverage": "1", - "notionalCap": "5000000", + "notionalCap": "2000000", "notionalFloor": "1000000", "maintMarginRatio": "0.5", - "cum": "386950.0" + "cum": "333250.0" } } ], @@ -27924,13 +28166,13 @@ "tier": 2.0, "currency": "USDT", "minNotional": 5000.0, - "maxNotional": 25000.0, + "maxNotional": 50000.0, "maintenanceMarginRate": 0.02, "maxLeverage": 25.0, "info": { "bracket": "2", "initialLeverage": "25", - "notionalCap": "25000", + "notionalCap": "50000", "notionalFloor": "5000", "maintMarginRatio": "0.02", "cum": "25.0" @@ -27939,97 +28181,97 @@ { "tier": 3.0, "currency": "USDT", - "minNotional": 25000.0, - "maxNotional": 80000.0, + "minNotional": 50000.0, + "maxNotional": 100000.0, "maintenanceMarginRate": 0.025, "maxLeverage": 20.0, "info": { "bracket": "3", "initialLeverage": "20", - "notionalCap": "80000", - "notionalFloor": "25000", + "notionalCap": "100000", + "notionalFloor": "50000", "maintMarginRatio": "0.025", - "cum": "150.0" + "cum": "275.0" } }, { "tier": 4.0, "currency": "USDT", - "minNotional": 80000.0, - "maxNotional": 800000.0, + "minNotional": 100000.0, + "maxNotional": 1000000.0, "maintenanceMarginRate": 0.05, "maxLeverage": 10.0, "info": { "bracket": "4", "initialLeverage": "10", - "notionalCap": "800000", - "notionalFloor": "80000", + "notionalCap": "1000000", + "notionalFloor": "100000", "maintMarginRatio": "0.05", - "cum": "2150.0" + "cum": "2775.0" } }, { "tier": 5.0, "currency": "USDT", - "minNotional": 800000.0, - "maxNotional": 1600000.0, + "minNotional": 1000000.0, + "maxNotional": 2000000.0, "maintenanceMarginRate": 0.1, "maxLeverage": 5.0, "info": { "bracket": "5", "initialLeverage": "5", - "notionalCap": "1600000", - "notionalFloor": "800000", + "notionalCap": "2000000", + "notionalFloor": "1000000", "maintMarginRatio": "0.1", - "cum": "42150.0" + "cum": "52775.0" } }, { "tier": 6.0, "currency": "USDT", - "minNotional": 1600000.0, - "maxNotional": 2000000.0, + "minNotional": 2000000.0, + "maxNotional": 2500000.0, "maintenanceMarginRate": 0.125, "maxLeverage": 4.0, "info": { "bracket": "6", "initialLeverage": "4", - "notionalCap": "2000000", - "notionalFloor": "1600000", + "notionalCap": "2500000", + "notionalFloor": "2000000", "maintMarginRatio": "0.125", - "cum": "82150.0" + "cum": "102775.0" } }, { "tier": 7.0, "currency": "USDT", - "minNotional": 2000000.0, - "maxNotional": 4000000.0, + "minNotional": 2500000.0, + "maxNotional": 5000000.0, "maintenanceMarginRate": 0.25, "maxLeverage": 2.0, "info": { "bracket": "7", "initialLeverage": "2", - "notionalCap": "4000000", - "notionalFloor": "2000000", + "notionalCap": "5000000", + "notionalFloor": "2500000", "maintMarginRatio": "0.25", - "cum": "332150.0" + "cum": "415275.0" } }, { "tier": 8.0, "currency": "USDT", - "minNotional": 4000000.0, - "maxNotional": 8000000.0, + "minNotional": 5000000.0, + "maxNotional": 10000000.0, "maintenanceMarginRate": 0.5, "maxLeverage": 1.0, "info": { "bracket": "8", "initialLeverage": "1", - "notionalCap": "8000000", - "notionalFloor": "4000000", + "notionalCap": "10000000", + "notionalFloor": "5000000", "maintMarginRatio": "0.5", - "cum": "1332150.0" + "cum": "1665275.0" } } ], @@ -32660,13 +32902,13 @@ "tier": 2.0, "currency": "USDT", "minNotional": 5000.0, - "maxNotional": 25000.0, + "maxNotional": 50000.0, "maintenanceMarginRate": 0.02, "maxLeverage": 25.0, "info": { "bracket": "2", "initialLeverage": "25", - "notionalCap": "25000", + "notionalCap": "50000", "notionalFloor": "5000", "maintMarginRatio": "0.02", "cum": "25.0" @@ -32675,97 +32917,97 @@ { "tier": 3.0, "currency": "USDT", - "minNotional": 25000.0, - "maxNotional": 80000.0, + "minNotional": 50000.0, + "maxNotional": 100000.0, "maintenanceMarginRate": 0.025, "maxLeverage": 20.0, "info": { "bracket": "3", "initialLeverage": "20", - "notionalCap": "80000", - "notionalFloor": "25000", + "notionalCap": "100000", + "notionalFloor": "50000", "maintMarginRatio": "0.025", - "cum": "150.0" + "cum": "275.0" } }, { "tier": 4.0, "currency": "USDT", - "minNotional": 80000.0, - "maxNotional": 800000.0, + "minNotional": 100000.0, + "maxNotional": 1000000.0, "maintenanceMarginRate": 0.05, "maxLeverage": 10.0, "info": { "bracket": "4", "initialLeverage": "10", - "notionalCap": "800000", - "notionalFloor": "80000", + "notionalCap": "1000000", + "notionalFloor": "100000", "maintMarginRatio": "0.05", - "cum": "2150.0" + "cum": "2775.0" } }, { "tier": 5.0, "currency": "USDT", - "minNotional": 800000.0, - "maxNotional": 1600000.0, + "minNotional": 1000000.0, + "maxNotional": 2000000.0, "maintenanceMarginRate": 0.1, "maxLeverage": 5.0, "info": { "bracket": "5", "initialLeverage": "5", - "notionalCap": "1600000", - "notionalFloor": "800000", + "notionalCap": "2000000", + "notionalFloor": "1000000", "maintMarginRatio": "0.1", - "cum": "42150.0" + "cum": "52775.0" } }, { "tier": 6.0, "currency": "USDT", - "minNotional": 1600000.0, - "maxNotional": 2000000.0, + "minNotional": 2000000.0, + "maxNotional": 2500000.0, "maintenanceMarginRate": 0.125, "maxLeverage": 4.0, "info": { "bracket": "6", "initialLeverage": "4", - "notionalCap": "2000000", - "notionalFloor": "1600000", + "notionalCap": "2500000", + "notionalFloor": "2000000", "maintMarginRatio": "0.125", - "cum": "82150.0" + "cum": "102775.0" } }, { "tier": 7.0, "currency": "USDT", - "minNotional": 2000000.0, - "maxNotional": 4000000.0, + "minNotional": 2500000.0, + "maxNotional": 5000000.0, "maintenanceMarginRate": 0.25, "maxLeverage": 2.0, "info": { "bracket": "7", "initialLeverage": "2", - "notionalCap": "4000000", - "notionalFloor": "2000000", + "notionalCap": "5000000", + "notionalFloor": "2500000", "maintMarginRatio": "0.25", - "cum": "332150.0" + "cum": "415275.0" } }, { "tier": 8.0, "currency": "USDT", - "minNotional": 4000000.0, - "maxNotional": 8000000.0, + "minNotional": 5000000.0, + "maxNotional": 10000000.0, "maintenanceMarginRate": 0.5, "maxLeverage": 1.0, "info": { "bracket": "8", "initialLeverage": "1", - "notionalCap": "8000000", - "notionalFloor": "4000000", + "notionalCap": "10000000", + "notionalFloor": "5000000", "maintMarginRatio": "0.5", - "cum": "1332150.0" + "cum": "1665275.0" } } ], @@ -33159,6 +33401,136 @@ } } ], + "TURBO/USDT:USDT": [ + { + "tier": 1.0, + "currency": "USDT", + "minNotional": 0.0, + "maxNotional": 5000.0, + "maintenanceMarginRate": 0.015, + "maxLeverage": 50.0, + "info": { + "bracket": "1", + "initialLeverage": "50", + "notionalCap": "5000", + "notionalFloor": "0", + "maintMarginRatio": "0.015", + "cum": "0.0" + } + }, + { + "tier": 2.0, + "currency": "USDT", + "minNotional": 5000.0, + "maxNotional": 20000.0, + "maintenanceMarginRate": 0.02, + "maxLeverage": 25.0, + "info": { + "bracket": "2", + "initialLeverage": "25", + "notionalCap": "20000", + "notionalFloor": "5000", + "maintMarginRatio": "0.02", + "cum": "25.0" + } + }, + { + "tier": 3.0, + "currency": "USDT", + "minNotional": 20000.0, + "maxNotional": 25000.0, + "maintenanceMarginRate": 0.025, + "maxLeverage": 20.0, + "info": { + "bracket": "3", + "initialLeverage": "20", + "notionalCap": "25000", + "notionalFloor": "20000", + "maintMarginRatio": "0.025", + "cum": "125.0" + } + }, + { + "tier": 4.0, + "currency": "USDT", + "minNotional": 25000.0, + "maxNotional": 200000.0, + "maintenanceMarginRate": 0.05, + "maxLeverage": 10.0, + "info": { + "bracket": "4", + "initialLeverage": "10", + "notionalCap": "200000", + "notionalFloor": "25000", + "maintMarginRatio": "0.05", + "cum": "750.0" + } + }, + { + "tier": 5.0, + "currency": "USDT", + "minNotional": 200000.0, + "maxNotional": 400000.0, + "maintenanceMarginRate": 0.1, + "maxLeverage": 5.0, + "info": { + "bracket": "5", + "initialLeverage": "5", + "notionalCap": "400000", + "notionalFloor": "200000", + "maintMarginRatio": "0.1", + "cum": "10750.0" + } + }, + { + "tier": 6.0, + "currency": "USDT", + "minNotional": 400000.0, + "maxNotional": 500000.0, + "maintenanceMarginRate": 0.125, + "maxLeverage": 4.0, + "info": { + "bracket": "6", + "initialLeverage": "4", + "notionalCap": "500000", + "notionalFloor": "400000", + "maintMarginRatio": "0.125", + "cum": "20750.0" + } + }, + { + "tier": 7.0, + "currency": "USDT", + "minNotional": 500000.0, + "maxNotional": 1000000.0, + "maintenanceMarginRate": 0.25, + "maxLeverage": 2.0, + "info": { + "bracket": "7", + "initialLeverage": "2", + "notionalCap": "1000000", + "notionalFloor": "500000", + "maintMarginRatio": "0.25", + "cum": "83250.0" + } + }, + { + "tier": 8.0, + "currency": "USDT", + "minNotional": 1000000.0, + "maxNotional": 2000000.0, + "maintenanceMarginRate": 0.5, + "maxLeverage": 1.0, + "info": { + "bracket": "8", + "initialLeverage": "1", + "notionalCap": "2000000", + "notionalFloor": "1000000", + "maintMarginRatio": "0.5", + "cum": "333250.0" + } + } + ], "TWT/USDT:USDT": [ { "tier": 1.0, @@ -34256,10 +34628,10 @@ "minNotional": 0.0, "maxNotional": 5000.0, "maintenanceMarginRate": 0.015, - "maxLeverage": 50.0, + "maxLeverage": 21.0, "info": { "bracket": "1", - "initialLeverage": "50", + "initialLeverage": "21", "notionalCap": "5000", "notionalFloor": "0", "maintMarginRatio": "0.015", @@ -34350,13 +34722,13 @@ "tier": 7.0, "currency": "USDT", "minNotional": 3000000.0, - "maxNotional": 5000000.0, + "maxNotional": 3500000.0, "maintenanceMarginRate": 0.5, "maxLeverage": 1.0, "info": { "bracket": "7", "initialLeverage": "1", - "notionalCap": "5000000", + "notionalCap": "3500000", "notionalFloor": "3000000", "maintMarginRatio": "0.5", "cum": "991300.0" @@ -36660,80 +37032,112 @@ "tier": 2.0, "currency": "USDT", "minNotional": 5000.0, - "maxNotional": 25000.0, - "maintenanceMarginRate": 0.025, - "maxLeverage": 20.0, + "maxNotional": 20000.0, + "maintenanceMarginRate": 0.02, + "maxLeverage": 25.0, "info": { "bracket": "2", - "initialLeverage": "20", - "notionalCap": "25000", + "initialLeverage": "25", + "notionalCap": "20000", "notionalFloor": "5000", - "maintMarginRatio": "0.025", - "cum": "50.0" + "maintMarginRatio": "0.02", + "cum": "25.0" } }, { "tier": 3.0, "currency": "USDT", - "minNotional": 25000.0, - "maxNotional": 100000.0, - "maintenanceMarginRate": 0.05, - "maxLeverage": 10.0, + "minNotional": 20000.0, + "maxNotional": 25000.0, + "maintenanceMarginRate": 0.025, + "maxLeverage": 20.0, "info": { "bracket": "3", - "initialLeverage": "10", - "notionalCap": "100000", - "notionalFloor": "25000", - "maintMarginRatio": "0.05", - "cum": "675.0" + "initialLeverage": "20", + "notionalCap": "25000", + "notionalFloor": "20000", + "maintMarginRatio": "0.025", + "cum": "125.0" } }, { "tier": 4.0, "currency": "USDT", - "minNotional": 100000.0, - "maxNotional": 250000.0, - "maintenanceMarginRate": 0.1, - "maxLeverage": 5.0, + "minNotional": 25000.0, + "maxNotional": 200000.0, + "maintenanceMarginRate": 0.05, + "maxLeverage": 10.0, "info": { "bracket": "4", - "initialLeverage": "5", - "notionalCap": "250000", - "notionalFloor": "100000", - "maintMarginRatio": "0.1", - "cum": "5675.0" + "initialLeverage": "10", + "notionalCap": "200000", + "notionalFloor": "25000", + "maintMarginRatio": "0.05", + "cum": "750.0" } }, { "tier": 5.0, "currency": "USDT", - "minNotional": 250000.0, - "maxNotional": 1000000.0, - "maintenanceMarginRate": 0.125, - "maxLeverage": 2.0, + "minNotional": 200000.0, + "maxNotional": 400000.0, + "maintenanceMarginRate": 0.1, + "maxLeverage": 5.0, "info": { "bracket": "5", - "initialLeverage": "2", - "notionalCap": "1000000", - "notionalFloor": "250000", - "maintMarginRatio": "0.125", - "cum": "11925.0" + "initialLeverage": "5", + "notionalCap": "400000", + "notionalFloor": "200000", + "maintMarginRatio": "0.1", + "cum": "10750.0" } }, { "tier": 6.0, "currency": "USDT", + "minNotional": 400000.0, + "maxNotional": 500000.0, + "maintenanceMarginRate": 0.125, + "maxLeverage": 4.0, + "info": { + "bracket": "6", + "initialLeverage": "4", + "notionalCap": "500000", + "notionalFloor": "400000", + "maintMarginRatio": "0.125", + "cum": "20750.0" + } + }, + { + "tier": 7.0, + "currency": "USDT", + "minNotional": 500000.0, + "maxNotional": 1000000.0, + "maintenanceMarginRate": 0.25, + "maxLeverage": 2.0, + "info": { + "bracket": "7", + "initialLeverage": "2", + "notionalCap": "1000000", + "notionalFloor": "500000", + "maintMarginRatio": "0.25", + "cum": "83250.0" + } + }, + { + "tier": 8.0, + "currency": "USDT", "minNotional": 1000000.0, - "maxNotional": 3000000.0, + "maxNotional": 2000000.0, "maintenanceMarginRate": 0.5, "maxLeverage": 1.0, "info": { - "bracket": "6", + "bracket": "8", "initialLeverage": "1", - "notionalCap": "3000000", + "notionalCap": "2000000", "notionalFloor": "1000000", "maintMarginRatio": "0.5", - "cum": "386925.0" + "cum": "333250.0" } } ], diff --git a/freqtrade/exchange/exchange.py b/freqtrade/exchange/exchange.py index 6b1835dfb..de9050724 100644 --- a/freqtrade/exchange/exchange.py +++ b/freqtrade/exchange/exchange.py @@ -271,7 +271,7 @@ class Exchange: def close(self): logger.debug("Exchange object destroyed, closing async loop") if ( - self._api_async + getattr(self, "_api_async", None) and inspect.iscoroutinefunction(self._api_async.close) and self._api_async.session ): diff --git a/freqtrade/freqai/torch/PyTorchModelTrainer.py b/freqtrade/freqai/torch/PyTorchModelTrainer.py index 602c8e95b..54c42a284 100644 --- a/freqtrade/freqai/torch/PyTorchModelTrainer.py +++ b/freqtrade/freqai/torch/PyTorchModelTrainer.py @@ -148,7 +148,8 @@ class PyTorchModelTrainer(PyTorchTrainerInterface): the motivation here is that `n_steps` is easier to optimize and keep stable, across different n_obs - the number of data points. """ - assert isinstance(self.n_steps, int), "Either `n_steps` or `n_epochs` should be set." + if not isinstance(self.n_steps, int): + raise ValueError("Either `n_steps` or `n_epochs` should be set.") n_batches = n_obs // self.batch_size n_epochs = max(self.n_steps // n_batches, 1) if n_epochs <= 10: diff --git a/freqtrade/freqtradebot.py b/freqtrade/freqtradebot.py index 8d53097a5..8e4fa1d5f 100644 --- a/freqtrade/freqtradebot.py +++ b/freqtrade/freqtradebot.py @@ -217,7 +217,7 @@ class FreqtradeBot(LoggingMixin): except Exception: # Exceptions here will be happening if the db disappeared. # At which point we can no longer commit anyway. - pass + logger.exception("Error during cleanup") def startup(self) -> None: """ diff --git a/freqtrade/optimize/backtest_caching.py b/freqtrade/optimize/backtest_caching.py index 2f9c151ad..766c77ddc 100644 --- a/freqtrade/optimize/backtest_caching.py +++ b/freqtrade/optimize/backtest_caching.py @@ -13,7 +13,7 @@ def get_strategy_run_id(strategy) -> str: :param strategy: strategy object. :return: hex string id. """ - digest = hashlib.sha1() + digest = hashlib.sha1() # noqa: S324 config = deepcopy(strategy.config) # Options that have no impact on results of individual backtest. diff --git a/freqtrade/optimize/hyperopt.py b/freqtrade/optimize/hyperopt.py index 7ae2791bf..b19fca9dc 100644 --- a/freqtrade/optimize/hyperopt.py +++ b/freqtrade/optimize/hyperopt.py @@ -489,7 +489,7 @@ class Hyperopt: ) def _set_random_state(self, random_state: Optional[int]) -> int: - return random_state or random.randint(1, 2**16 - 1) + return random_state or random.randint(1, 2**16 - 1) # noqa: S311 def advise_and_trim(self, data: Dict[str, DataFrame]) -> Dict[str, DataFrame]: preprocessed = self.backtesting.strategy.advise_all_indicators(data) diff --git a/freqtrade/optimize/optimize_reports/bt_output.py b/freqtrade/optimize/optimize_reports/bt_output.py index 00e980e6b..f20d7f190 100644 --- a/freqtrade/optimize/optimize_reports/bt_output.py +++ b/freqtrade/optimize/optimize_reports/bt_output.py @@ -1,5 +1,5 @@ import logging -from typing import Any, Dict, List +from typing import Any, Dict, List, Union from tabulate import tabulate @@ -20,13 +20,13 @@ def _get_line_floatfmt(stake_currency: str) -> List[str]: def _get_line_header( - first_column: str, stake_currency: str, direction: str = "Entries" + first_column: Union[str, List[str]], stake_currency: str, direction: str = "Trades" ) -> List[str]: """ Generate header lines (goes in line with _generate_result_line()) """ return [ - first_column, + *([first_column] if isinstance(first_column, str) else first_column), direction, "Avg Profit %", f"Tot Profit {stake_currency}", @@ -54,7 +54,7 @@ def text_table_bt_results(pair_results: List[Dict[str, Any]], stake_currency: st :return: pretty printed table with tabulate as string """ - headers = _get_line_header("Pair", stake_currency) + headers = _get_line_header("Pair", stake_currency, "Trades") floatfmt = _get_line_floatfmt(stake_currency) output = [ [ @@ -79,20 +79,30 @@ def text_table_tags(tag_type: str, tag_results: List[Dict[str, Any]], stake_curr :param stake_currency: stake-currency - used to correctly name headers :return: pretty printed table with tabulate as string """ + floatfmt = _get_line_floatfmt(stake_currency) fallback: str = "" + is_list = False if tag_type == "enter_tag": - headers = _get_line_header("TAG", stake_currency) - else: + headers = _get_line_header("Enter Tag", stake_currency, "Entries") + elif tag_type == "exit_tag": headers = _get_line_header("Exit Reason", stake_currency, "Exits") fallback = "exit_reason" + else: + # Mix tag + headers = _get_line_header(["Enter Tag", "Exit Reason"], stake_currency, "Trades") + floatfmt.insert(0, "s") + is_list = True - floatfmt = _get_line_floatfmt(stake_currency) output = [ [ - ( - t["key"] + *( + ( + (t["key"] if isinstance(t["key"], list) else [t["key"], ""]) + if is_list + else [t["key"]] + ) if t.get("key") is not None and len(str(t["key"])) > 0 - else t.get(fallback, "OTHER") + else [t.get(fallback, "OTHER")] ), t["trades"], t["profit_mean_pct"], @@ -144,7 +154,7 @@ def text_table_strategy(strategy_results, stake_currency: str) -> str: :return: pretty printed table with tabulate as string """ floatfmt = _get_line_floatfmt(stake_currency) - headers = _get_line_header("Strategy", stake_currency) + headers = _get_line_header("Strategy", stake_currency, "Trades") # _get_line_header() is also used for per-pair summary. Per-pair drawdown is mostly useless # therefore we slip this column in only for strategy summary here. headers.append("Drawdown") @@ -380,6 +390,32 @@ def text_table_add_metrics(strat_results: Dict) -> str: return message +def _show_tag_subresults(results: Dict[str, Any], stake_currency: str): + """ + Print tag subresults (enter_tag, exit_reason_summary, mix_tag_stats) + """ + if (enter_tags := results.get("results_per_enter_tag")) is not None: + table = text_table_tags("enter_tag", enter_tags, stake_currency) + + if isinstance(table, str) and len(table) > 0: + print(" ENTER TAG STATS ".center(len(table.splitlines()[0]), "=")) + print(table) + + if (exit_reasons := results.get("exit_reason_summary")) is not None: + table = text_table_tags("exit_tag", exit_reasons, stake_currency) + + if isinstance(table, str) and len(table) > 0: + print(" EXIT REASON STATS ".center(len(table.splitlines()[0]), "=")) + print(table) + + if (mix_tag := results.get("mix_tag_stats")) is not None: + table = text_table_tags("mix_tag", mix_tag, stake_currency) + + if isinstance(table, str) and len(table) > 0: + print(" MIXED TAG STATS ".center(len(table.splitlines()[0]), "=")) + print(table) + + def show_backtest_result( strategy: str, results: Dict[str, Any], stake_currency: str, backtest_breakdown: List[str] ): @@ -398,19 +434,7 @@ def show_backtest_result( print(" LEFT OPEN TRADES REPORT ".center(len(table.splitlines()[0]), "=")) print(table) - if (enter_tags := results.get("results_per_enter_tag")) is not None: - table = text_table_tags("enter_tag", enter_tags, stake_currency) - - if isinstance(table, str) and len(table) > 0: - print(" ENTER TAG STATS ".center(len(table.splitlines()[0]), "=")) - print(table) - - if (exit_reasons := results.get("exit_reason_summary")) is not None: - table = text_table_tags("exit_tag", exit_reasons, stake_currency) - - if isinstance(table, str) and len(table) > 0: - print(" EXIT REASON STATS ".center(len(table.splitlines()[0]), "=")) - print(table) + _show_tag_subresults(results, stake_currency) for period in backtest_breakdown: if period in results.get("periodic_breakdown", {}): diff --git a/freqtrade/optimize/optimize_reports/optimize_reports.py b/freqtrade/optimize/optimize_reports/optimize_reports.py index 2ca467eb6..ef5fce0e1 100644 --- a/freqtrade/optimize/optimize_reports/optimize_reports.py +++ b/freqtrade/optimize/optimize_reports/optimize_reports.py @@ -1,7 +1,7 @@ import logging from copy import deepcopy from datetime import datetime, timedelta, timezone -from typing import Any, Dict, List, Tuple, Union +from typing import Any, Dict, List, Literal, Tuple, Union import numpy as np from pandas import DataFrame, Series, concat, to_datetime @@ -68,7 +68,9 @@ def generate_rejected_signals( return rejected_candles_only -def _generate_result_line(result: DataFrame, starting_balance: int, first_column: str) -> Dict: +def _generate_result_line( + result: DataFrame, starting_balance: int, first_column: Union[str, List[str]] +) -> Dict: """ Generate one result dict, with "first_column" as key. """ @@ -141,7 +143,10 @@ def generate_pair_metrics( def generate_tag_metrics( - tag_type: str, starting_balance: int, results: DataFrame, skip_nan: bool = False + tag_type: Union[Literal["enter_tag", "exit_reason"], List[Literal["enter_tag", "exit_reason"]]], + starting_balance: int, + results: DataFrame, + skip_nan: bool = False, ) -> List[Dict]: """ Generates and returns a list of metrics for the given tag trades and the results dataframe @@ -153,13 +158,14 @@ def generate_tag_metrics( tabular_data = [] - if tag_type in results.columns: - for tag, count in results[tag_type].value_counts().items(): - result = results[results[tag_type] == tag] - if skip_nan and result["profit_abs"].isnull().all(): + if all( + tag in results.columns for tag in (tag_type if isinstance(tag_type, list) else [tag_type]) + ): + for tags, group in results.groupby(tag_type): + if skip_nan and group["profit_abs"].isnull().all(): continue - tabular_data.append(_generate_result_line(result, starting_balance, tag)) + tabular_data.append(_generate_result_line(group, starting_balance, tags)) # Sort by total profit %: tabular_data = sorted(tabular_data, key=lambda k: k["profit_total_abs"], reverse=True) @@ -378,12 +384,18 @@ def generate_strategy_stats( skip_nan=False, ) - enter_tag_results = generate_tag_metrics( + enter_tag_stats = generate_tag_metrics( "enter_tag", starting_balance=start_balance, results=results, skip_nan=False ) exit_reason_stats = generate_tag_metrics( "exit_reason", starting_balance=start_balance, results=results, skip_nan=False ) + mix_tag_stats = generate_tag_metrics( + ["enter_tag", "exit_reason"], + starting_balance=start_balance, + results=results, + skip_nan=False, + ) left_open_results = generate_pair_metrics( pairlist, stake_currency=stake_currency, @@ -425,8 +437,9 @@ def generate_strategy_stats( "best_pair": best_pair, "worst_pair": worst_pair, "results_per_pair": pair_results, - "results_per_enter_tag": enter_tag_results, + "results_per_enter_tag": enter_tag_stats, "exit_reason_summary": exit_reason_stats, + "mix_tag_stats": mix_tag_stats, "left_open_trades": left_open_results, "total_trades": len(results), "trade_count_long": len(results.loc[~results["is_short"]]), diff --git a/freqtrade/plugins/pairlist/AgeFilter.py b/freqtrade/plugins/pairlist/AgeFilter.py index 0be04d7b8..917dad45c 100644 --- a/freqtrade/plugins/pairlist/AgeFilter.py +++ b/freqtrade/plugins/pairlist/AgeFilter.py @@ -5,11 +5,11 @@ Minimum age (days listed) pair list filter import logging from copy import deepcopy from datetime import timedelta -from typing import Any, Dict, List, Optional +from typing import Dict, List, Optional from pandas import DataFrame -from freqtrade.constants import Config, ListPairsWithTimeframes +from freqtrade.constants import ListPairsWithTimeframes from freqtrade.exceptions import OperationalException from freqtrade.exchange.types import Tickers from freqtrade.misc import plural @@ -21,24 +21,17 @@ logger = logging.getLogger(__name__) class AgeFilter(IPairList): - def __init__( - self, - exchange, - pairlistmanager, - config: Config, - pairlistconfig: Dict[str, Any], - pairlist_pos: int, - ) -> None: - super().__init__(exchange, pairlistmanager, config, pairlistconfig, pairlist_pos) + def __init__(self, *args, **kwargs) -> None: + super().__init__(*args, **kwargs) # Checked symbols cache (dictionary of ticker symbol => timestamp) self._symbolsChecked: Dict[str, int] = {} self._symbolsCheckFailed = PeriodicCache(maxsize=1000, ttl=86_400) - self._min_days_listed = pairlistconfig.get("min_days_listed", 10) - self._max_days_listed = pairlistconfig.get("max_days_listed") + self._min_days_listed = self._pairlistconfig.get("min_days_listed", 10) + self._max_days_listed = self._pairlistconfig.get("max_days_listed") - candle_limit = exchange.ohlcv_candle_limit("1d", self._config["candle_type_def"]) + candle_limit = self._exchange.ohlcv_candle_limit("1d", self._config["candle_type_def"]) if self._min_days_listed < 1: raise OperationalException("AgeFilter requires min_days_listed to be >= 1") if self._min_days_listed > candle_limit: diff --git a/freqtrade/plugins/pairlist/FullTradesFilter.py b/freqtrade/plugins/pairlist/FullTradesFilter.py index 11d98abc5..13586611d 100644 --- a/freqtrade/plugins/pairlist/FullTradesFilter.py +++ b/freqtrade/plugins/pairlist/FullTradesFilter.py @@ -3,9 +3,8 @@ Full trade slots pair list filter """ import logging -from typing import Any, Dict, List +from typing import List -from freqtrade.constants import Config from freqtrade.exchange.types import Tickers from freqtrade.persistence import Trade from freqtrade.plugins.pairlist.IPairList import IPairList @@ -15,16 +14,6 @@ logger = logging.getLogger(__name__) class FullTradesFilter(IPairList): - def __init__( - self, - exchange, - pairlistmanager, - config: Config, - pairlistconfig: Dict[str, Any], - pairlist_pos: int, - ) -> None: - super().__init__(exchange, pairlistmanager, config, pairlistconfig, pairlist_pos) - @property def needstickers(self) -> bool: """ diff --git a/freqtrade/plugins/pairlist/IPairList.py b/freqtrade/plugins/pairlist/IPairList.py index 0db38ff2f..a2e70e649 100644 --- a/freqtrade/plugins/pairlist/IPairList.py +++ b/freqtrade/plugins/pairlist/IPairList.py @@ -3,7 +3,7 @@ PairList Handler base class """ import logging -from abc import ABC, abstractmethod, abstractproperty +from abc import ABC, abstractmethod from copy import deepcopy from typing import Any, Dict, List, Literal, Optional, TypedDict, Union @@ -87,7 +87,8 @@ class IPairList(LoggingMixin, ABC): """ return self.__class__.__name__ - @abstractproperty + @property + @abstractmethod def needstickers(self) -> bool: """ Boolean property defining if tickers are necessary. diff --git a/freqtrade/plugins/pairlist/MarketCapPairList.py b/freqtrade/plugins/pairlist/MarketCapPairList.py index 709b6100b..648766e20 100644 --- a/freqtrade/plugins/pairlist/MarketCapPairList.py +++ b/freqtrade/plugins/pairlist/MarketCapPairList.py @@ -5,11 +5,10 @@ Provides dynamic pair list based on Market Cap """ import logging -from typing import Any, Dict, List +from typing import Dict, List from cachetools import TTLCache -from freqtrade.constants import Config from freqtrade.exceptions import OperationalException from freqtrade.exchange.types import Tickers from freqtrade.plugins.pairlist.IPairList import IPairList, PairlistParameter @@ -22,15 +21,8 @@ logger = logging.getLogger(__name__) class MarketCapPairList(IPairList): is_pairlist_generator = True - def __init__( - self, - exchange, - pairlistmanager, - config: Config, - pairlistconfig: Dict[str, Any], - pairlist_pos: int, - ) -> None: - super().__init__(exchange, pairlistmanager, config, pairlistconfig, pairlist_pos) + def __init__(self, *args, **kwargs) -> None: + super().__init__(*args, **kwargs) if "number_assets" not in self._pairlistconfig: raise OperationalException( @@ -38,14 +30,14 @@ class MarketCapPairList(IPairList): 'for "pairlist.config.number_assets"' ) - self._stake_currency = config["stake_currency"] + self._stake_currency = self._config["stake_currency"] self._number_assets = self._pairlistconfig["number_assets"] self._max_rank = self._pairlistconfig.get("max_rank", 30) 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"] - _coingecko_config = config.get("coingecko", {}) + _coingecko_config = self._config.get("coingecko", {}) self._coingecko: FtCoinGeckoApi = FtCoinGeckoApi( api_key=_coingecko_config.get("api_key", ""), diff --git a/freqtrade/plugins/pairlist/OffsetFilter.py b/freqtrade/plugins/pairlist/OffsetFilter.py index 1fa9e1bd0..bd981358e 100644 --- a/freqtrade/plugins/pairlist/OffsetFilter.py +++ b/freqtrade/plugins/pairlist/OffsetFilter.py @@ -3,9 +3,8 @@ Offset pair list filter """ import logging -from typing import Any, Dict, List +from typing import Dict, List -from freqtrade.constants import Config from freqtrade.exceptions import OperationalException from freqtrade.exchange.types import Tickers from freqtrade.plugins.pairlist.IPairList import IPairList, PairlistParameter @@ -15,18 +14,11 @@ logger = logging.getLogger(__name__) class OffsetFilter(IPairList): - def __init__( - self, - exchange, - pairlistmanager, - config: Config, - pairlistconfig: Dict[str, Any], - pairlist_pos: int, - ) -> None: - super().__init__(exchange, pairlistmanager, config, pairlistconfig, pairlist_pos) + def __init__(self, *args, **kwargs) -> None: + super().__init__(*args, **kwargs) - self._offset = pairlistconfig.get("offset", 0) - self._number_pairs = pairlistconfig.get("number_assets", 0) + self._offset = self._pairlistconfig.get("offset", 0) + self._number_pairs = self._pairlistconfig.get("number_assets", 0) if self._offset < 0: raise OperationalException("OffsetFilter requires offset to be >= 0") diff --git a/freqtrade/plugins/pairlist/PerformanceFilter.py b/freqtrade/plugins/pairlist/PerformanceFilter.py index 930c78334..c10ae7394 100644 --- a/freqtrade/plugins/pairlist/PerformanceFilter.py +++ b/freqtrade/plugins/pairlist/PerformanceFilter.py @@ -3,11 +3,10 @@ Performance pair list filter """ import logging -from typing import Any, Dict, List +from typing import Dict, List import pandas as pd -from freqtrade.constants import Config from freqtrade.exchange.types import Tickers from freqtrade.persistence import Trade from freqtrade.plugins.pairlist.IPairList import IPairList, PairlistParameter @@ -17,18 +16,11 @@ logger = logging.getLogger(__name__) class PerformanceFilter(IPairList): - def __init__( - self, - exchange, - pairlistmanager, - config: Config, - pairlistconfig: Dict[str, Any], - pairlist_pos: int, - ) -> None: - super().__init__(exchange, pairlistmanager, config, pairlistconfig, pairlist_pos) + def __init__(self, *args, **kwargs) -> None: + super().__init__(*args, **kwargs) - self._minutes = pairlistconfig.get("minutes", 0) - self._min_profit = pairlistconfig.get("min_profit") + self._minutes = self._pairlistconfig.get("minutes", 0) + self._min_profit = self._pairlistconfig.get("min_profit") @property def needstickers(self) -> bool: diff --git a/freqtrade/plugins/pairlist/PrecisionFilter.py b/freqtrade/plugins/pairlist/PrecisionFilter.py index 0e8c50849..b2f767a67 100644 --- a/freqtrade/plugins/pairlist/PrecisionFilter.py +++ b/freqtrade/plugins/pairlist/PrecisionFilter.py @@ -3,9 +3,8 @@ Precision pair list filter """ import logging -from typing import Any, Dict, Optional +from typing import Optional -from freqtrade.constants import Config from freqtrade.exceptions import OperationalException from freqtrade.exchange import ROUND_UP from freqtrade.exchange.types import Ticker @@ -16,15 +15,8 @@ logger = logging.getLogger(__name__) class PrecisionFilter(IPairList): - def __init__( - self, - exchange, - pairlistmanager, - config: Config, - pairlistconfig: Dict[str, Any], - pairlist_pos: int, - ) -> None: - super().__init__(exchange, pairlistmanager, config, pairlistconfig, pairlist_pos) + def __init__(self, *args, **kwargs) -> None: + super().__init__(*args, **kwargs) if "stoploss" not in self._config: raise OperationalException( diff --git a/freqtrade/plugins/pairlist/PriceFilter.py b/freqtrade/plugins/pairlist/PriceFilter.py index 81dbdfc33..d651533ce 100644 --- a/freqtrade/plugins/pairlist/PriceFilter.py +++ b/freqtrade/plugins/pairlist/PriceFilter.py @@ -3,9 +3,8 @@ Price pair list filter """ import logging -from typing import Any, Dict, Optional +from typing import Dict, Optional -from freqtrade.constants import Config from freqtrade.exceptions import OperationalException from freqtrade.exchange.types import Ticker from freqtrade.plugins.pairlist.IPairList import IPairList, PairlistParameter @@ -15,26 +14,19 @@ logger = logging.getLogger(__name__) class PriceFilter(IPairList): - def __init__( - self, - exchange, - pairlistmanager, - config: Config, - pairlistconfig: Dict[str, Any], - pairlist_pos: int, - ) -> None: - super().__init__(exchange, pairlistmanager, config, pairlistconfig, pairlist_pos) + def __init__(self, *args, **kwargs) -> None: + super().__init__(*args, **kwargs) - self._low_price_ratio = pairlistconfig.get("low_price_ratio", 0) + self._low_price_ratio = self._pairlistconfig.get("low_price_ratio", 0) if self._low_price_ratio < 0: raise OperationalException("PriceFilter requires low_price_ratio to be >= 0") - self._min_price = pairlistconfig.get("min_price", 0) + self._min_price = self._pairlistconfig.get("min_price", 0) if self._min_price < 0: raise OperationalException("PriceFilter requires min_price to be >= 0") - self._max_price = pairlistconfig.get("max_price", 0) + self._max_price = self._pairlistconfig.get("max_price", 0) if self._max_price < 0: raise OperationalException("PriceFilter requires max_price to be >= 0") - self._max_value = pairlistconfig.get("max_value", 0) + self._max_value = self._pairlistconfig.get("max_value", 0) if self._max_value < 0: raise OperationalException("PriceFilter requires max_value to be >= 0") self._enabled = ( diff --git a/freqtrade/plugins/pairlist/ProducerPairList.py b/freqtrade/plugins/pairlist/ProducerPairList.py index 771f87380..09a0c49d2 100644 --- a/freqtrade/plugins/pairlist/ProducerPairList.py +++ b/freqtrade/plugins/pairlist/ProducerPairList.py @@ -5,7 +5,7 @@ Provides pair list from Leader data """ import logging -from typing import Any, Dict, List, Optional +from typing import Dict, List, Optional from freqtrade.exceptions import OperationalException from freqtrade.exchange.types import Tickers @@ -32,19 +32,12 @@ class ProducerPairList(IPairList): is_pairlist_generator = True - def __init__( - self, - exchange, - pairlistmanager, - config: Dict[str, Any], - pairlistconfig: Dict[str, Any], - pairlist_pos: int, - ) -> None: - super().__init__(exchange, pairlistmanager, config, pairlistconfig, pairlist_pos) + def __init__(self, *args, **kwargs) -> None: + super().__init__(*args, **kwargs) self._num_assets: int = self._pairlistconfig.get("number_assets", 0) self._producer_name = self._pairlistconfig.get("producer_name", "default") - if not config.get("external_message_consumer", {}).get("enabled"): + if not self._config.get("external_message_consumer", {}).get("enabled"): raise OperationalException( "ProducerPairList requires external_message_consumer to be enabled." ) diff --git a/freqtrade/plugins/pairlist/RemotePairList.py b/freqtrade/plugins/pairlist/RemotePairList.py index b15cfa96e..26fadb9ae 100644 --- a/freqtrade/plugins/pairlist/RemotePairList.py +++ b/freqtrade/plugins/pairlist/RemotePairList.py @@ -14,7 +14,6 @@ from cachetools import TTLCache from freqtrade import __version__ from freqtrade.configuration.load_config import CONFIG_PARSE_MODE -from freqtrade.constants import Config from freqtrade.exceptions import OperationalException from freqtrade.exchange.types import Tickers from freqtrade.plugins.pairlist.IPairList import IPairList, PairlistParameter @@ -27,15 +26,8 @@ logger = logging.getLogger(__name__) class RemotePairList(IPairList): is_pairlist_generator = True - def __init__( - self, - exchange, - pairlistmanager, - config: Config, - pairlistconfig: Dict[str, Any], - pairlist_pos: int, - ) -> None: - super().__init__(exchange, pairlistmanager, config, pairlistconfig, pairlist_pos) + def __init__(self, *args, **kwargs) -> None: + super().__init__(*args, **kwargs) if "number_assets" not in self._pairlistconfig: raise OperationalException( diff --git a/freqtrade/plugins/pairlist/ShuffleFilter.py b/freqtrade/plugins/pairlist/ShuffleFilter.py index d7f8a60bc..59ac1ac7c 100644 --- a/freqtrade/plugins/pairlist/ShuffleFilter.py +++ b/freqtrade/plugins/pairlist/ShuffleFilter.py @@ -4,9 +4,8 @@ Shuffle pair list filter import logging import random -from typing import Any, Dict, List, Literal +from typing import Dict, List, Literal -from freqtrade.constants import Config from freqtrade.enums import RunMode from freqtrade.exchange import timeframe_to_seconds from freqtrade.exchange.types import Tickers @@ -20,27 +19,20 @@ ShuffleValues = Literal["candle", "iteration"] class ShuffleFilter(IPairList): - def __init__( - self, - exchange, - pairlistmanager, - config: Config, - pairlistconfig: Dict[str, Any], - pairlist_pos: int, - ) -> None: - super().__init__(exchange, pairlistmanager, config, pairlistconfig, pairlist_pos) + def __init__(self, *args, **kwargs) -> None: + super().__init__(*args, **kwargs) # Apply seed in backtesting mode to get comparable results, # but not in live modes to get a non-repeating order of pairs during live modes. - if config.get("runmode") in (RunMode.LIVE, RunMode.DRY_RUN): + if self._config.get("runmode") in (RunMode.LIVE, RunMode.DRY_RUN): self._seed = None logger.info("Live mode detected, not applying seed.") else: - self._seed = pairlistconfig.get("seed") + self._seed = self._pairlistconfig.get("seed") logger.info(f"Backtesting mode detected, applying seed value: {self._seed}") - self._random = random.Random(self._seed) - self._shuffle_freq: ShuffleValues = pairlistconfig.get("shuffle_frequency", "candle") + self._random = random.Random(self._seed) # noqa: S311 + self._shuffle_freq: ShuffleValues = self._pairlistconfig.get("shuffle_frequency", "candle") self.__pairlist_cache = PeriodicCache( maxsize=1000, ttl=timeframe_to_seconds(self._config["timeframe"]) ) diff --git a/freqtrade/plugins/pairlist/SpreadFilter.py b/freqtrade/plugins/pairlist/SpreadFilter.py index 4aca98f3e..736903abd 100644 --- a/freqtrade/plugins/pairlist/SpreadFilter.py +++ b/freqtrade/plugins/pairlist/SpreadFilter.py @@ -3,9 +3,8 @@ Spread pair list filter """ import logging -from typing import Any, Dict, Optional +from typing import Dict, Optional -from freqtrade.constants import Config from freqtrade.exceptions import OperationalException from freqtrade.exchange.types import Ticker from freqtrade.plugins.pairlist.IPairList import IPairList, PairlistParameter @@ -15,17 +14,10 @@ logger = logging.getLogger(__name__) class SpreadFilter(IPairList): - def __init__( - self, - exchange, - pairlistmanager, - config: Config, - pairlistconfig: Dict[str, Any], - pairlist_pos: int, - ) -> None: - super().__init__(exchange, pairlistmanager, config, pairlistconfig, pairlist_pos) + def __init__(self, *args, **kwargs) -> None: + super().__init__(*args, **kwargs) - self._max_spread_ratio = pairlistconfig.get("max_spread_ratio", 0.005) + self._max_spread_ratio = self._pairlistconfig.get("max_spread_ratio", 0.005) self._enabled = self._max_spread_ratio != 0 if not self._exchange.get_option("tickers_have_bid_ask"): diff --git a/freqtrade/plugins/pairlist/StaticPairList.py b/freqtrade/plugins/pairlist/StaticPairList.py index ac1201ca3..922d0fd94 100644 --- a/freqtrade/plugins/pairlist/StaticPairList.py +++ b/freqtrade/plugins/pairlist/StaticPairList.py @@ -6,9 +6,8 @@ Provides pair white list as it configured in config import logging from copy import deepcopy -from typing import Any, Dict, List +from typing import Dict, List -from freqtrade.constants import Config from freqtrade.exchange.types import Tickers from freqtrade.plugins.pairlist.IPairList import IPairList, PairlistParameter @@ -19,15 +18,8 @@ logger = logging.getLogger(__name__) class StaticPairList(IPairList): is_pairlist_generator = True - def __init__( - self, - exchange, - pairlistmanager, - config: Config, - pairlistconfig: Dict[str, Any], - pairlist_pos: int, - ) -> None: - super().__init__(exchange, pairlistmanager, config, pairlistconfig, pairlist_pos) + def __init__(self, *args, **kwargs) -> None: + super().__init__(*args, **kwargs) self._allow_inactive = self._pairlistconfig.get("allow_inactive", False) diff --git a/freqtrade/plugins/pairlist/VolatilityFilter.py b/freqtrade/plugins/pairlist/VolatilityFilter.py index c4088196d..f5af2d0a7 100644 --- a/freqtrade/plugins/pairlist/VolatilityFilter.py +++ b/freqtrade/plugins/pairlist/VolatilityFilter.py @@ -5,13 +5,13 @@ Volatility pairlist filter import logging import sys from datetime import timedelta -from typing import Any, Dict, List, Optional +from typing import Dict, List, Optional import numpy as np from cachetools import TTLCache from pandas import DataFrame -from freqtrade.constants import Config, ListPairsWithTimeframes +from freqtrade.constants import ListPairsWithTimeframes from freqtrade.exceptions import OperationalException from freqtrade.exchange.types import Tickers from freqtrade.misc import plural @@ -27,26 +27,19 @@ class VolatilityFilter(IPairList): Filters pairs by volatility """ - def __init__( - self, - exchange, - pairlistmanager, - config: Config, - pairlistconfig: Dict[str, Any], - pairlist_pos: int, - ) -> None: - super().__init__(exchange, pairlistmanager, config, pairlistconfig, pairlist_pos) + def __init__(self, *args, **kwargs) -> None: + super().__init__(*args, **kwargs) - self._days = pairlistconfig.get("lookback_days", 10) - self._min_volatility = pairlistconfig.get("min_volatility", 0) - self._max_volatility = pairlistconfig.get("max_volatility", sys.maxsize) - self._refresh_period = pairlistconfig.get("refresh_period", 1440) + self._days = self._pairlistconfig.get("lookback_days", 10) + self._min_volatility = self._pairlistconfig.get("min_volatility", 0) + self._max_volatility = self._pairlistconfig.get("max_volatility", sys.maxsize) + self._refresh_period = self._pairlistconfig.get("refresh_period", 1440) self._def_candletype = self._config["candle_type_def"] - self._sort_direction: Optional[str] = pairlistconfig.get("sort_direction", None) + self._sort_direction: Optional[str] = self._pairlistconfig.get("sort_direction", None) self._pair_cache: TTLCache = TTLCache(maxsize=1000, ttl=self._refresh_period) - candle_limit = exchange.ohlcv_candle_limit("1d", self._config["candle_type_def"]) + candle_limit = self._exchange.ohlcv_candle_limit("1d", self._config["candle_type_def"]) if self._days < 1: raise OperationalException("VolatilityFilter requires lookback_days to be >= 1") if self._days > candle_limit: diff --git a/freqtrade/plugins/pairlist/VolumePairList.py b/freqtrade/plugins/pairlist/VolumePairList.py index f9a0dd6b1..ea172f140 100644 --- a/freqtrade/plugins/pairlist/VolumePairList.py +++ b/freqtrade/plugins/pairlist/VolumePairList.py @@ -10,7 +10,7 @@ from typing import Any, Dict, List, Literal from cachetools import TTLCache -from freqtrade.constants import Config, ListPairsWithTimeframes +from freqtrade.constants import ListPairsWithTimeframes from freqtrade.exceptions import OperationalException from freqtrade.exchange import timeframe_to_minutes, timeframe_to_prev_date from freqtrade.exchange.types import Tickers @@ -27,15 +27,8 @@ SORT_VALUES = ["quoteVolume"] class VolumePairList(IPairList): is_pairlist_generator = True - def __init__( - self, - exchange, - pairlistmanager, - config: Config, - pairlistconfig: Dict[str, Any], - pairlist_pos: int, - ) -> None: - super().__init__(exchange, pairlistmanager, config, pairlistconfig, pairlist_pos) + def __init__(self, *args, **kwargs) -> None: + super().__init__(*args, **kwargs) if "number_assets" not in self._pairlistconfig: raise OperationalException( @@ -43,7 +36,7 @@ class VolumePairList(IPairList): 'for "pairlist.config.number_assets"' ) - self._stake_currency = config["stake_currency"] + self._stake_currency = self._config["stake_currency"] self._number_pairs = self._pairlistconfig["number_assets"] self._sort_key: Literal["quoteVolume"] = self._pairlistconfig.get("sort_key", "quoteVolume") self._min_value = self._pairlistconfig.get("min_value", 0) @@ -94,7 +87,7 @@ class VolumePairList(IPairList): if not self._validate_keys(self._sort_key): raise OperationalException(f"key {self._sort_key} not in {SORT_VALUES}") - candle_limit = exchange.ohlcv_candle_limit( + candle_limit = self._exchange.ohlcv_candle_limit( self._lookback_timeframe, self._config["candle_type_def"] ) if self._lookback_period < 0: diff --git a/freqtrade/plugins/pairlist/rangestabilityfilter.py b/freqtrade/plugins/pairlist/rangestabilityfilter.py index 54c6a536e..473e003b6 100644 --- a/freqtrade/plugins/pairlist/rangestabilityfilter.py +++ b/freqtrade/plugins/pairlist/rangestabilityfilter.py @@ -4,12 +4,12 @@ Rate of change pairlist filter import logging from datetime import timedelta -from typing import Any, Dict, List, Optional +from typing import Dict, List, Optional from cachetools import TTLCache from pandas import DataFrame -from freqtrade.constants import Config, ListPairsWithTimeframes +from freqtrade.constants import ListPairsWithTimeframes from freqtrade.exceptions import OperationalException from freqtrade.exchange.types import Tickers from freqtrade.misc import plural @@ -21,26 +21,19 @@ logger = logging.getLogger(__name__) class RangeStabilityFilter(IPairList): - def __init__( - self, - exchange, - pairlistmanager, - config: Config, - pairlistconfig: Dict[str, Any], - pairlist_pos: int, - ) -> None: - super().__init__(exchange, pairlistmanager, config, pairlistconfig, pairlist_pos) + def __init__(self, *args, **kwargs) -> None: + super().__init__(*args, **kwargs) - self._days = pairlistconfig.get("lookback_days", 10) - self._min_rate_of_change = pairlistconfig.get("min_rate_of_change", 0.01) - self._max_rate_of_change = pairlistconfig.get("max_rate_of_change") - self._refresh_period = pairlistconfig.get("refresh_period", 86400) + self._days = self._pairlistconfig.get("lookback_days", 10) + self._min_rate_of_change = self._pairlistconfig.get("min_rate_of_change", 0.01) + self._max_rate_of_change = self._pairlistconfig.get("max_rate_of_change") + self._refresh_period = self._pairlistconfig.get("refresh_period", 86400) self._def_candletype = self._config["candle_type_def"] - self._sort_direction: Optional[str] = pairlistconfig.get("sort_direction", None) + self._sort_direction: Optional[str] = self._pairlistconfig.get("sort_direction", None) self._pair_cache: TTLCache = TTLCache(maxsize=1000, ttl=self._refresh_period) - candle_limit = exchange.ohlcv_candle_limit("1d", self._config["candle_type_def"]) + candle_limit = self._exchange.ohlcv_candle_limit("1d", self._config["candle_type_def"]) if self._days < 1: raise OperationalException("RangeStabilityFilter requires lookback_days to be >= 1") if self._days > candle_limit: diff --git a/freqtrade/rpc/api_server/api_auth.py b/freqtrade/rpc/api_server/api_auth.py index 0e054220b..24e04a905 100644 --- a/freqtrade/rpc/api_server/api_auth.py +++ b/freqtrade/rpc/api_server/api_auth.py @@ -31,7 +31,7 @@ security = HTTPBasic() oauth2_scheme = OAuth2PasswordBearer(tokenUrl="token", auto_error=False) -def get_user_from_token(token, secret_key: str, token_type: str = "access") -> str: +def get_user_from_token(token, secret_key: str, token_type: str = "access") -> str: # noqa: S107 credentials_exception = HTTPException( status_code=status.HTTP_401_UNAUTHORIZED, detail="Could not validate credentials", @@ -86,11 +86,11 @@ async def validate_ws_token( await ws.close(code=status.WS_1008_POLICY_VIOLATION) -def create_token(data: dict, secret_key: str, token_type: str = "access") -> str: +def create_token(data: dict, secret_key: str, token_type: str = "access") -> str: # noqa: S107 to_encode = data.copy() - if token_type == "access": + if token_type == "access": # noqa: S105 expire = datetime.now(timezone.utc) + timedelta(minutes=15) - elif token_type == "refresh": + elif token_type == "refresh": # noqa: S105 expire = datetime.now(timezone.utc) + timedelta(days=30) else: raise ValueError() @@ -127,9 +127,15 @@ def token_login( ): if verify_auth(api_config, form_data.username, form_data.password): token_data = {"identity": {"u": form_data.username}} - access_token = create_token(token_data, api_config.get("jwt_secret_key", "super-secret")) + access_token = create_token( + token_data, + api_config.get("jwt_secret_key", "super-secret"), + token_type="access", # noqa: S106 + ) refresh_token = create_token( - token_data, api_config.get("jwt_secret_key", "super-secret"), token_type="refresh" + token_data, + api_config.get("jwt_secret_key", "super-secret"), + token_type="refresh", # noqa: S106 ) return { "access_token": access_token, @@ -148,6 +154,8 @@ def token_refresh(token: str = Depends(oauth2_scheme), api_config=Depends(get_ap u = get_user_from_token(token, api_config.get("jwt_secret_key", "super-secret"), "refresh") token_data = {"identity": {"u": u}} access_token = create_token( - token_data, api_config.get("jwt_secret_key", "super-secret"), token_type="access" + token_data, + api_config.get("jwt_secret_key", "super-secret"), + token_type="access", # noqa: S106 ) return {"access_token": access_token} diff --git a/freqtrade/rpc/rpc.py b/freqtrade/rpc/rpc.py index 4cafa12ad..3513b7207 100644 --- a/freqtrade/rpc/rpc.py +++ b/freqtrade/rpc/rpc.py @@ -1466,6 +1466,8 @@ class RPC: from freqtrade.resolvers.strategy_resolver import StrategyResolver strategy = StrategyResolver.load_strategy(config) + # Manually load hyperparameters, as we don't call the bot-start callback. + strategy.ft_load_hyper_params(False) if strategy.plot_config and "subplots" not in strategy.plot_config: strategy.plot_config["subplots"] = {} diff --git a/ft_client/freqtrade_client/__init__.py b/ft_client/freqtrade_client/__init__.py index 9ede4dd12..60b5d1164 100644 --- a/ft_client/freqtrade_client/__init__.py +++ b/ft_client/freqtrade_client/__init__.py @@ -31,7 +31,7 @@ if "dev" in __version__: versionfile = Path("./freqtrade_commit") if versionfile.is_file(): __version__ = f"docker-{__version__}-{versionfile.read_text()[:8]}" - except Exception: + except Exception: # noqa: S110 pass __all__ = ["FtRestClient"] diff --git a/ft_client/freqtrade_client/ft_client.py b/ft_client/freqtrade_client/ft_client.py index d51858fde..3c450d1df 100644 --- a/ft_client/freqtrade_client/ft_client.py +++ b/ft_client/freqtrade_client/ft_client.py @@ -81,12 +81,12 @@ def print_commands(): print(f"{x}\n\t{doc}\n") -def main_exec(args: Dict[str, Any]): - if args.get("show"): +def main_exec(parsed: Dict[str, Any]): + if parsed.get("show"): print_commands() sys.exit() - config = load_config(args["config"]) + config = load_config(parsed["config"]) url = config.get("api_server", {}).get("listen_ip_address", "127.0.0.1") port = config.get("api_server", {}).get("listen_port", "8080") username = config.get("api_server", {}).get("username") @@ -96,13 +96,24 @@ def main_exec(args: Dict[str, Any]): client = FtRestClient(server_url, username, password) m = [x for x, y in inspect.getmembers(client) if not x.startswith("_")] - command = args["command"] + command = parsed["command"] if command not in m: logger.error(f"Command {command} not defined") print_commands() return - print(json.dumps(getattr(client, command)(*args["command_arguments"]))) + # Split arguments with = into key/value pairs + kwargs = {x.split("=")[0]: x.split("=")[1] for x in parsed["command_arguments"] if "=" in x} + args = [x for x in parsed["command_arguments"] if "=" not in x] + try: + res = getattr(client, command)(*args, **kwargs) + print(json.dumps(res)) + except TypeError as e: + logger.error(f"Error executing command {command}: {e}") + sys.exit(1) + except Exception as e: + logger.error(f"Fatal Error executing command {command}: {e}") + sys.exit(1) def main(): diff --git a/ft_client/freqtrade_client/ft_rest_client.py b/ft_client/freqtrade_client/ft_rest_client.py index 6e5f7e6c5..f7c895901 100755 --- a/ft_client/freqtrade_client/ft_rest_client.py +++ b/ft_client/freqtrade_client/ft_rest_client.py @@ -54,7 +54,7 @@ class FtRestClient: # return resp.text return resp.json() except ConnectionError: - logger.warning("Connection error") + logger.warning(f"Connection error - could not connect to {netloc}.") def _get(self, apipath, params: ParamsT = None): return self._call("GET", apipath, params=params) @@ -312,20 +312,48 @@ class FtRestClient: data = {"pair": pair, "price": price} return self._post("forcebuy", data=data) - def forceenter(self, pair, side, price=None): + def forceenter( + self, + pair, + side, + price=None, + *, + order_type=None, + stake_amount=None, + leverage=None, + enter_tag=None, + ): """Force entering a trade :param pair: Pair to buy (ETH/BTC) :param side: 'long' or 'short' :param price: Optional - price to buy + :param order_type: Optional keyword argument - 'limit' or 'market' + :param stake_amount: Optional keyword argument - stake amount (as float) + :param leverage: Optional keyword argument - leverage (as float) + :param enter_tag: Optional keyword argument - entry tag (as string, default: 'force_enter') :return: json object of the trade """ data = { "pair": pair, "side": side, } + if price: data["price"] = price + + if order_type: + data["ordertype"] = order_type + + if stake_amount: + data["stakeamount"] = stake_amount + + if leverage: + data["leverage"] = leverage + + if enter_tag: + data["entry_tag"] = enter_tag + return self._post("forceenter", data=data) def forceexit(self, tradeid, ordertype=None, amount=None): diff --git a/ft_client/test_client/test_rest_client.py b/ft_client/test_client/test_rest_client.py index 08ccee765..541577e59 100644 --- a/ft_client/test_client/test_rest_client.py +++ b/ft_client/test_client/test_rest_client.py @@ -1,5 +1,5 @@ import re -from unittest.mock import MagicMock +from unittest.mock import ANY, MagicMock import pytest from requests.exceptions import ConnectionError @@ -52,70 +52,89 @@ def test_FtRestClient_call_invalid(caplog): @pytest.mark.parametrize( - "method,args", + "method,args,kwargs", [ - ("start", []), - ("stop", []), - ("stopbuy", []), - ("reload_config", []), - ("balance", []), - ("count", []), - ("entries", []), - ("exits", []), - ("mix_tags", []), - ("locks", []), - ("lock_add", ["XRP/USDT", "2024-01-01 20:00:00Z", "*", "rand"]), - ("delete_lock", [2]), - ("daily", []), - ("daily", [15]), - ("weekly", []), - ("weekly", [15]), - ("monthly", []), - ("monthly", [12]), - ("edge", []), - ("profit", []), - ("stats", []), - ("performance", []), - ("status", []), - ("version", []), - ("show_config", []), - ("ping", []), - ("logs", []), - ("logs", [55]), - ("trades", []), - ("trades", [5]), - ("trades", [5, 5]), # With offset - ("trade", [1]), - ("delete_trade", [1]), - ("cancel_open_order", [1]), - ("whitelist", []), - ("blacklist", []), - ("blacklist", ["XRP/USDT"]), - ("blacklist", ["XRP/USDT", "BTC/USDT"]), - ("forcebuy", ["XRP/USDT"]), - ("forcebuy", ["XRP/USDT", 1.5]), - ("forceenter", ["XRP/USDT", "short"]), - ("forceenter", ["XRP/USDT", "short", 1.5]), - ("forceexit", [1]), - ("forceexit", [1, "limit"]), - ("forceexit", [1, "limit", 100]), - ("strategies", []), - ("strategy", ["sampleStrategy"]), - ("pairlists_available", []), - ("plot_config", []), - ("available_pairs", []), - ("available_pairs", ["5m"]), - ("pair_candles", ["XRP/USDT", "5m"]), - ("pair_candles", ["XRP/USDT", "5m", 500]), - ("pair_history", ["XRP/USDT", "5m", "SampleStrategy"]), - ("sysinfo", []), - ("health", []), + ("start", [], {}), + ("stop", [], {}), + ("stopbuy", [], {}), + ("reload_config", [], {}), + ("balance", [], {}), + ("count", [], {}), + ("entries", [], {}), + ("exits", [], {}), + ("mix_tags", [], {}), + ("locks", [], {}), + ("lock_add", ["XRP/USDT", "2024-01-01 20:00:00Z", "*", "rand"], {}), + ("delete_lock", [2], {}), + ("daily", [], {}), + ("daily", [15], {}), + ("weekly", [], {}), + ("weekly", [15], {}), + ("monthly", [], {}), + ("monthly", [12], {}), + ("edge", [], {}), + ("profit", [], {}), + ("stats", [], {}), + ("performance", [], {}), + ("status", [], {}), + ("version", [], {}), + ("show_config", [], {}), + ("ping", [], {}), + ("logs", [], {}), + ("logs", [55], {}), + ("trades", [], {}), + ("trades", [5], {}), + ("trades", [5, 5], {}), # With offset + ("trade", [1], {}), + ("delete_trade", [1], {}), + ("cancel_open_order", [1], {}), + ("whitelist", [], {}), + ("blacklist", [], {}), + ("blacklist", ["XRP/USDT"], {}), + ("blacklist", ["XRP/USDT", "BTC/USDT"], {}), + ("forcebuy", ["XRP/USDT"], {}), + ("forcebuy", ["XRP/USDT", 1.5], {}), + ("forceenter", ["XRP/USDT", "short"], {}), + ("forceenter", ["XRP/USDT", "short", 1.5], {}), + ("forceenter", ["XRP/USDT", "short", 1.5], {"order_type": "market"}), + ("forceenter", ["XRP/USDT", "short", 1.5], {"order_type": "market", "stake_amount": 100}), + ( + "forceenter", + ["XRP/USDT", "short", 1.5], + {"order_type": "market", "stake_amount": 100, "leverage": 10.0}, + ), + ( + "forceenter", + ["XRP/USDT", "short", 1.5], + { + "order_type": "market", + "stake_amount": 100, + "leverage": 10.0, + "enter_tag": "test_force_enter", + }, + ), + ("forceexit", [1], {}), + ("forceexit", [1, "limit"], {}), + ("forceexit", [1, "limit", 100], {}), + ("strategies", [], {}), + ("strategy", ["sampleStrategy"], {}), + ("pairlists_available", [], {}), + ("plot_config", [], {}), + ("available_pairs", [], {}), + ("available_pairs", ["5m"], {}), + ("pair_candles", ["XRP/USDT", "5m"], {}), + ("pair_candles", ["XRP/USDT", "5m", 500], {}), + ("pair_candles", ["XRP/USDT", "5m", 500], {"columns": ["close_time,close"]}), + ("pair_history", ["XRP/USDT", "5m", "SampleStrategy"], {}), + ("pair_history", ["XRP/USDT", "5m"], {"strategy": "SampleStrategy"}), + ("sysinfo", [], {}), + ("health", [], {}), ], ) -def test_FtRestClient_call_explicit_methods(method, args): +def test_FtRestClient_call_explicit_methods(method, args, kwargs): client, mock = get_rest_client() exec = getattr(client, method) - exec(*args) + exec(*args, **kwargs) assert mock.call_count == 1 @@ -148,3 +167,40 @@ def test_ft_client(mocker, capsys, caplog): ) main_exec(args) assert log_has_re("Command whatever not defined", caplog) + + +@pytest.mark.parametrize( + "params, expected_args, expected_kwargs", + [ + ("forceenter BTC/USDT long", ["BTC/USDT", "long"], {}), + ("forceenter BTC/USDT long limit", ["BTC/USDT", "long", "limit"], {}), + ( + # Skip most parameters, only providing enter_tag + "forceenter BTC/USDT long enter_tag=deadBeef", + ["BTC/USDT", "long"], + {"enter_tag": "deadBeef"}, + ), + ( + "forceenter BTC/USDT long invalid_key=123", + [], + SystemExit, + # {"invalid_key": "deadBeef"}, + ), + ], +) +def test_ft_client_argparsing(mocker, params, expected_args, expected_kwargs, caplog): + mocked_method = params.split(" ")[0] + mocker.patch("freqtrade_client.ft_client.load_config", return_value={}, autospec=True) + mm = mocker.patch( + f"freqtrade_client.ft_client.FtRestClient.{mocked_method}", return_value={}, autospec=True + ) + args = add_arguments(params.split(" ")) + if isinstance(expected_kwargs, dict): + main_exec(args) + mm.assert_called_once_with(ANY, *expected_args, **expected_kwargs) + else: + with pytest.raises(expected_kwargs): + main_exec(args) + + assert log_has_re(f"Error executing command {mocked_method}: got an unexpected .*", caplog) + mm.assert_not_called() diff --git a/pyproject.toml b/pyproject.toml index 0c9222530..55ff28db6 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -138,7 +138,7 @@ extend-select = [ # "EXE", # flake8-executable # "C4", # flake8-comprehensions "YTT", # flake8-2020 - # "S", # flake8-bandit + "S", # flake8-bandit # "DTZ", # flake8-datetimez # "RSE", # flake8-raise # "TCH", # flake8-type-checking @@ -151,13 +151,30 @@ extend-ignore = [ "E272", # Multiple spaces before keyword "E221", # Multiple spaces before operator "B007", # Loop control variable not used + "S603", # `subprocess` call: check for execution of untrusted input + "S607", # Starting a process with a partial executable path + "S608", # Possible SQL injection vector through string-based query construction ] [tool.ruff.lint.mccabe] max-complexity = 12 [tool.ruff.lint.per-file-ignores] -"tests/*" = ["S"] +"freqtrade/freqai/**/*.py" = [ + "S311" # Standard pseudo-random generators are not suitable for cryptographic purposes +] +"tests/**/*.py" = [ + "S101", # allow assert in tests + "S104", # Possible binding to all interfaces + "S311", # Standard pseudo-random generators are not suitable for cryptographic purposes + "S105", # Possible hardcoded password assigned to: "secret" + "S106", # Possible hardcoded password assigned to argument: "token_type" + "S110", # `try`-`except`-`pass` detected, consider logging the exception + ] + +"ft_client/test_client/**/*.py" = [ + "S101", # allow assert in tests +] [tool.ruff.lint.flake8-bugbear] # Allow default arguments like, e.g., `data: List[str] = fastapi.Query(None)`. diff --git a/requirements-dev.txt b/requirements-dev.txt index b4865f067..b7c7cfd07 100644 --- a/requirements-dev.txt +++ b/requirements-dev.txt @@ -7,10 +7,10 @@ -r docs/requirements-docs.txt coveralls==4.0.1 -ruff==0.4.7 +ruff==0.4.9 mypy==1.10.0 pre-commit==3.7.1 -pytest==8.2.1 +pytest==8.2.2 pytest-asyncio==0.23.7 pytest-cov==5.0.0 pytest-mock==3.14.0 diff --git a/requirements-freqai-rl.txt b/requirements-freqai-rl.txt index 546c0b62a..c278716fb 100644 --- a/requirements-freqai-rl.txt +++ b/requirements-freqai-rl.txt @@ -2,7 +2,8 @@ -r requirements-freqai.txt # Required for freqai-rl -torch==2.2.2 +torch==2.3.1; sys_platform != 'darwin' or platform_machine != 'x86_64' +torch==2.2.2; sys_platform == 'darwin' and platform_machine == 'x86_64' gymnasium==0.29.1 stable_baselines3==2.3.2 sb3_contrib>=2.2.1 diff --git a/requirements-freqai.txt b/requirements-freqai.txt index cdc32e11b..d864b45e8 100644 --- a/requirements-freqai.txt +++ b/requirements-freqai.txt @@ -6,7 +6,7 @@ scikit-learn==1.5.0 joblib==1.4.2 catboost==1.2.5; 'arm' not in platform_machine -lightgbm==4.3.0 +lightgbm==4.4.0 xgboost==2.0.3 -tensorboard==2.16.2 +tensorboard==2.17.0 datasieve==0.1.7 diff --git a/requirements-hyperopt.txt b/requirements-hyperopt.txt index 99d4ee5c6..3d367211b 100644 --- a/requirements-hyperopt.txt +++ b/requirements-hyperopt.txt @@ -5,4 +5,4 @@ scipy==1.13.1 scikit-learn==1.5.0 ft-scikit-optimize==0.9.2 -filelock==3.14.0 +filelock==3.15.1 diff --git a/requirements.txt b/requirements.txt index 9f9da2cec..39728b8c2 100644 --- a/requirements.txt +++ b/requirements.txt @@ -4,19 +4,19 @@ bottleneck==1.3.8 numexpr==2.10.0 pandas-ta==0.3.14b -ccxt==4.3.38 -cryptography==42.0.7 +ccxt==4.3.46 +cryptography==42.0.8 aiohttp==3.9.5 SQLAlchemy==2.0.30 -python-telegram-bot==21.2 +python-telegram-bot==21.3 # can't be hard-pinned due to telegram-bot pinning httpx with ~ httpx>=0.24.1 humanize==4.9.0 cachetools==5.3.3 requests==2.32.3 -urllib3==2.2.1 +urllib3==2.2.2 jsonschema==4.22.0 -TA-Lib==0.4.30 +TA-Lib==0.4.31 technical==1.4.3 tabulate==0.9.0 pycoingecko==3.1.0 @@ -32,14 +32,14 @@ py_find_1st==1.1.6 # Load ticker files 30% faster python-rapidjson==1.17 # Properly format api responses -orjson==3.10.3 +orjson==3.10.5 # Notify systemd sdnotify==0.3.2 # API Server fastapi==0.111.0 -pydantic==2.7.2 +pydantic==2.7.4 uvicorn==0.30.1 pyjwt==2.8.0 aiofiles==23.2.1 @@ -62,4 +62,4 @@ websockets==12.0 janus==1.0.0 ast-comments==1.2.2 -packaging==24.0 +packaging==24.1 diff --git a/tests/data/test_entryexitanalysis.py b/tests/data/test_entryexitanalysis.py index 49ef74c0a..1a5309190 100644 --- a/tests/data/test_entryexitanalysis.py +++ b/tests/data/test_entryexitanalysis.py @@ -67,10 +67,10 @@ def test_backtest_analysis_nomock(default_conf, mocker, caplog, testdatadir, use "enter_tag_long_b", ], "exit_reason": [ - ExitType.ROI, - ExitType.EXIT_SIGNAL, - ExitType.STOP_LOSS, - ExitType.TRAILING_STOP_LOSS, + ExitType.ROI.value, + ExitType.EXIT_SIGNAL.value, + ExitType.STOP_LOSS.value, + ExitType.TRAILING_STOP_LOSS.value, ], } ) diff --git a/tests/exchange/test_exchange.py b/tests/exchange/test_exchange.py index b033dcdaa..294c722f3 100644 --- a/tests/exchange/test_exchange.py +++ b/tests/exchange/test_exchange.py @@ -7,7 +7,7 @@ from unittest.mock import MagicMock, Mock, PropertyMock, patch import ccxt import pytest -from numpy import NaN +from numpy import nan from pandas import DataFrame, to_datetime from freqtrade.constants import DEFAULT_DATAFRAME_COLUMNS @@ -5014,7 +5014,7 @@ def test_get_max_leverage_from_margin(default_conf, mocker, pair, nominal_value, (10, 0.0001, 2.0, 1.0, 0.002, 0.002), (10, 0.0002, 2.0, 0.01, 0.004, 0.00004), (10, 0.0002, 2.5, None, 0.005, None), - (10, 0.0002, NaN, None, 0.0, None), + (10, 0.0002, nan, None, 0.0, None), ], ) def test_calculate_funding_fees( diff --git a/tests/optimize/test_backtesting.py b/tests/optimize/test_backtesting.py index 5576b312f..6e182e6e8 100644 --- a/tests/optimize/test_backtesting.py +++ b/tests/optimize/test_backtesting.py @@ -1650,11 +1650,11 @@ def test_backtest_start_multi_strat(default_conf, mocker, caplog, testdatadir): ] args = get_args(args) start_backtesting(args) - # 2 backtests, 4 tables + # 2 backtests, 6 tables (entry, exit, mixed - each 2x) assert backtestmock.call_count == 2 assert text_table_mock.call_count == 4 assert strattable_mock.call_count == 1 - assert tag_metrics_mock.call_count == 4 + assert tag_metrics_mock.call_count == 6 assert strat_summary.call_count == 1 # check the logs, that will contain the backtest result @@ -1709,7 +1709,7 @@ def test_backtest_start_multi_strat_nomock(default_conf, mocker, caplog, testdat "open_rate": [0.104445, 0.10302485], "close_rate": [0.104969, 0.103541], "is_short": [False, False], - "exit_reason": [ExitType.ROI, ExitType.ROI], + "exit_reason": [ExitType.ROI.value, ExitType.ROI.value], } ) result2 = pd.DataFrame( @@ -1729,7 +1729,7 @@ def test_backtest_start_multi_strat_nomock(default_conf, mocker, caplog, testdat "open_rate": [0.104445, 0.10302485, 0.122541], "close_rate": [0.104969, 0.103541, 0.123541], "is_short": [False, False, False], - "exit_reason": [ExitType.ROI, ExitType.ROI, ExitType.STOP_LOSS], + "exit_reason": [ExitType.ROI.value, ExitType.ROI.value, ExitType.STOP_LOSS.value], } ) backtestmock = MagicMock( diff --git a/tests/optimize/test_hyperopt.py b/tests/optimize/test_hyperopt.py index d2ff5b8d3..2e9ae8d35 100644 --- a/tests/optimize/test_hyperopt.py +++ b/tests/optimize/test_hyperopt.py @@ -415,10 +415,10 @@ def test_hyperopt_format_results(hyperopt): "is_short": [False, False, False, False], "stake_amount": [0.01, 0.01, 0.01, 0.01], "exit_reason": [ - ExitType.ROI, - ExitType.STOP_LOSS, - ExitType.ROI, - ExitType.FORCE_EXIT, + ExitType.ROI.value, + ExitType.STOP_LOSS.value, + ExitType.ROI.value, + ExitType.FORCE_EXIT.value, ], } ), @@ -507,10 +507,10 @@ def test_generate_optimizer(mocker, hyperopt_conf) -> None: "is_short": [False, False, False, False], "stake_amount": [0.01, 0.01, 0.01, 0.01], "exit_reason": [ - ExitType.ROI, - ExitType.STOP_LOSS, - ExitType.ROI, - ExitType.FORCE_EXIT, + ExitType.ROI.value, + ExitType.STOP_LOSS.value, + ExitType.ROI.value, + ExitType.FORCE_EXIT.value, ], } ), diff --git a/tests/optimize/test_optimize_reports.py b/tests/optimize/test_optimize_reports.py index 88e846d9d..b26f15be5 100644 --- a/tests/optimize/test_optimize_reports.py +++ b/tests/optimize/test_optimize_reports.py @@ -70,13 +70,13 @@ def test_text_table_bt_results(): ) result_str = ( - "| Pair | Entries | Avg Profit % | Tot Profit BTC | " + "| Pair | Trades | Avg Profit % | Tot Profit BTC | " "Tot Profit % | Avg Duration | Win Draw Loss Win% |\n" - "|---------+-----------+----------------+------------------+" + "|---------+----------+----------------+------------------+" "----------------+----------------+-------------------------|\n" - "| ETH/BTC | 3 | 8.33 | 0.50000000 | " + "| ETH/BTC | 3 | 8.33 | 0.50000000 | " "12.50 | 0:20:00 | 2 0 1 66.7 |\n" - "| TOTAL | 3 | 8.33 | 0.50000000 | " + "| TOTAL | 3 | 8.33 | 0.50000000 | " "12.50 | 0:20:00 | 2 0 1 66.7 |" ) @@ -116,10 +116,10 @@ def test_generate_backtest_stats(default_conf, testdatadir, tmp_path): "is_short": [False, False, False, False], "stake_amount": [0.01, 0.01, 0.01, 0.01], "exit_reason": [ - ExitType.ROI, - ExitType.STOP_LOSS, - ExitType.ROI, - ExitType.FORCE_EXIT, + ExitType.ROI.value, + ExitType.STOP_LOSS.value, + ExitType.ROI.value, + ExitType.FORCE_EXIT.value, ], } ), @@ -183,10 +183,10 @@ def test_generate_backtest_stats(default_conf, testdatadir, tmp_path): "is_short": [False, False, False, False], "stake_amount": [0.01, 0.01, 0.01, 0.01], "exit_reason": [ - ExitType.ROI, - ExitType.ROI, - ExitType.STOP_LOSS, - ExitType.FORCE_EXIT, + ExitType.ROI.value, + ExitType.ROI.value, + ExitType.STOP_LOSS.value, + ExitType.FORCE_EXIT.value, ], } ), @@ -444,7 +444,7 @@ def test_text_table_exit_reason(): "wins": [2, 0, 0], "draws": [0, 0, 0], "losses": [0, 0, 1], - "exit_reason": [ExitType.ROI, ExitType.ROI, ExitType.STOP_LOSS], + "exit_reason": [ExitType.ROI.value, ExitType.ROI.value, ExitType.STOP_LOSS.value], } ) @@ -509,13 +509,13 @@ def test_text_table_strategy(testdatadir): bt_res_data_comparison = bt_res_data.pop("strategy_comparison") result_str = ( - "| Strategy | Entries | Avg Profit % | Tot Profit BTC |" + "| Strategy | Trades | Avg Profit % | Tot Profit BTC |" " Tot Profit % | Avg Duration | Win Draw Loss Win% | Drawdown |\n" - "|----------------+-----------+----------------+------------------+" + "|----------------+----------+----------------+------------------+" "----------------+----------------+-------------------------+-----------------------|\n" - "| StrategyTestV2 | 179 | 0.08 | 0.02608550 |" + "| StrategyTestV2 | 179 | 0.08 | 0.02608550 |" " 260.85 | 3:40:00 | 170 0 9 95.0 | 0.00308222 BTC 8.67% |\n" - "| TestStrategy | 179 | 0.08 | 0.02608550 |" + "| TestStrategy | 179 | 0.08 | 0.02608550 |" " 260.85 | 3:40:00 | 170 0 9 95.0 | 0.00308222 BTC 8.67% |" ) diff --git a/tests/test_configuration.py b/tests/test_configuration.py index 0b4b37b25..e160130c7 100644 --- a/tests/test_configuration.py +++ b/tests/test_configuration.py @@ -401,11 +401,11 @@ def test_load_dry_run(default_conf, mocker, config_value, expected, arglist) -> assert validated_conf["runmode"] == (RunMode.DRY_RUN if expected else RunMode.LIVE) -def test_load_custom_strategy(default_conf, mocker) -> None: +def test_load_custom_strategy(default_conf, mocker, tmp_path) -> None: default_conf.update( { "strategy": "CustomStrategy", - "strategy_path": "/tmp/strategies", + "strategy_path": f"{tmp_path}/strategies", } ) patched_configuration_load_config_file(mocker, default_conf) @@ -415,7 +415,7 @@ def test_load_custom_strategy(default_conf, mocker) -> None: validated_conf = configuration.load_config() assert validated_conf.get("strategy") == "CustomStrategy" - assert validated_conf.get("strategy_path") == "/tmp/strategies" + assert validated_conf.get("strategy_path") == f"{tmp_path}/strategies" def test_show_info(default_conf, mocker, caplog) -> None: @@ -469,7 +469,7 @@ def test_setup_configuration_without_arguments(mocker, default_conf, caplog) -> assert "timerange" not in config -def test_setup_configuration_with_arguments(mocker, default_conf, caplog) -> None: +def test_setup_configuration_with_arguments(mocker, default_conf, caplog, tmp_path) -> None: patched_configuration_load_config_file(mocker, default_conf) mocker.patch("freqtrade.configuration.configuration.create_datadir", lambda c, x: x) mocker.patch( @@ -485,7 +485,7 @@ def test_setup_configuration_with_arguments(mocker, default_conf, caplog) -> Non "--datadir", "/foo/bar", "--userdir", - "/tmp/freqtrade", + f"{tmp_path}/freqtrade", "--timeframe", "1m", "--enable-position-stacking", @@ -509,7 +509,7 @@ def test_setup_configuration_with_arguments(mocker, default_conf, caplog) -> Non assert "pair_whitelist" in config["exchange"] assert "datadir" in config assert log_has("Using data directory: {} ...".format("/foo/bar"), caplog) - assert log_has("Using user-data directory: {} ...".format(Path("/tmp/freqtrade")), caplog) + assert log_has(f"Using user-data directory: {tmp_path / 'freqtrade'} ...", caplog) assert "user_data_dir" in config assert "timeframe" in config diff --git a/tests/test_directory_operations.py b/tests/test_directory_operations.py index 45297fba8..d64ff9c18 100644 --- a/tests/test_directory_operations.py +++ b/tests/test_directory_operations.py @@ -24,16 +24,16 @@ def test_create_datadir(mocker, default_conf, caplog) -> None: assert log_has("Created data directory: /foo/bar", caplog) -def test_create_userdata_dir(mocker, default_conf, caplog) -> None: +def test_create_userdata_dir(mocker, tmp_path, caplog) -> None: mocker.patch.object(Path, "is_dir", MagicMock(return_value=False)) md = mocker.patch.object(Path, "mkdir", MagicMock()) - x = create_userdata_dir("/tmp/bar", create_dir=True) + x = create_userdata_dir(tmp_path / "bar", create_dir=True) assert md.call_count == 10 assert md.call_args[1]["parents"] is False - assert log_has(f'Created user-data directory: {Path("/tmp/bar")}', caplog) + assert log_has(f'Created user-data directory: {tmp_path / "bar"}', caplog) assert isinstance(x, Path) - assert str(x) == str(Path("/tmp/bar")) + assert str(x) == str(tmp_path / "bar") def test_create_userdata_dir_and_chown(mocker, tmp_path, caplog) -> None: @@ -54,63 +54,57 @@ def test_create_userdata_dir_and_chown(mocker, tmp_path, caplog) -> None: del os.environ["FT_APP_ENV"] -def test_create_userdata_dir_exists(mocker, default_conf, caplog) -> None: +def test_create_userdata_dir_exists(mocker, tmp_path) -> None: mocker.patch.object(Path, "is_dir", MagicMock(return_value=True)) md = mocker.patch.object(Path, "mkdir", MagicMock()) - create_userdata_dir("/tmp/bar") + create_userdata_dir(f"{tmp_path}/bar") assert md.call_count == 0 -def test_create_userdata_dir_exists_exception(mocker, default_conf, caplog) -> None: +def test_create_userdata_dir_exists_exception(mocker, tmp_path) -> None: mocker.patch.object(Path, "is_dir", MagicMock(return_value=False)) md = mocker.patch.object(Path, "mkdir", MagicMock()) - with pytest.raises( - OperationalException, match=r"Directory `.{1,2}tmp.{1,2}bar` does not exist.*" - ): - create_userdata_dir("/tmp/bar", create_dir=False) + with pytest.raises(OperationalException, match=r"Directory `.*.{1,2}bar` does not exist.*"): + create_userdata_dir(f"{tmp_path}/bar", create_dir=False) assert md.call_count == 0 -def test_copy_sample_files(mocker, default_conf, caplog) -> None: +def test_copy_sample_files(mocker, tmp_path) -> None: mocker.patch.object(Path, "is_dir", MagicMock(return_value=True)) mocker.patch.object(Path, "exists", MagicMock(return_value=False)) copymock = mocker.patch("shutil.copy", MagicMock()) - copy_sample_files(Path("/tmp/bar")) + copy_sample_files(Path(f"{tmp_path}/bar")) assert copymock.call_count == 3 - assert copymock.call_args_list[0][0][1] == str( - Path("/tmp/bar") / "strategies/sample_strategy.py" - ) + assert copymock.call_args_list[0][0][1] == str(tmp_path / "bar/strategies/sample_strategy.py") assert copymock.call_args_list[1][0][1] == str( - Path("/tmp/bar") / "hyperopts/sample_hyperopt_loss.py" + tmp_path / "bar/hyperopts/sample_hyperopt_loss.py" ) assert copymock.call_args_list[2][0][1] == str( - Path("/tmp/bar") / "notebooks/strategy_analysis_example.ipynb" + tmp_path / "bar/notebooks/strategy_analysis_example.ipynb" ) -def test_copy_sample_files_errors(mocker, default_conf, caplog) -> None: +def test_copy_sample_files_errors(mocker, tmp_path, caplog) -> None: mocker.patch.object(Path, "is_dir", MagicMock(return_value=False)) mocker.patch.object(Path, "exists", MagicMock(return_value=False)) mocker.patch("shutil.copy", MagicMock()) - with pytest.raises( - OperationalException, match=r"Directory `.{1,2}tmp.{1,2}bar` does not exist\." - ): - copy_sample_files(Path("/tmp/bar")) + with pytest.raises(OperationalException, match=r"Directory `.*.{1,2}bar` does not exist\."): + copy_sample_files(Path(f"{tmp_path}/bar")) mocker.patch.object(Path, "is_dir", MagicMock(side_effect=[True, False])) with pytest.raises( OperationalException, - match=r"Directory `.{1,2}tmp.{1,2}bar.{1,2}strategies` does not exist\.", + match=r"Directory `.*.{1,2}bar.{1,2}strategies` does not exist\.", ): - copy_sample_files(Path("/tmp/bar")) + copy_sample_files(Path(f"{tmp_path}/bar")) mocker.patch.object(Path, "is_dir", MagicMock(return_value=True)) mocker.patch.object(Path, "exists", MagicMock(return_value=True)) - copy_sample_files(Path("/tmp/bar")) + copy_sample_files(Path(f"{tmp_path}/bar")) assert log_has_re(r"File `.*` exists already, not deploying sample file\.", caplog) caplog.clear() - copy_sample_files(Path("/tmp/bar"), overwrite=True) + copy_sample_files(Path(f"{tmp_path}/bar"), overwrite=True) assert log_has_re(r"File `.*` exists already, overwriting\.", caplog)