Merge branch 'develop' into feature/hyperliquid-hip3-support

This commit is contained in:
Matthias
2025-12-30 08:46:48 +01:00
47 changed files with 775 additions and 551 deletions

View File

@@ -28,9 +28,9 @@ updates:
exclude:
- ccxt
schedule:
interval: weekly
time: "03:00"
timezone: "Etc/UTC"
interval: "cron"
# Monday at 03:00
cronjob: "0 3 * * 1"
open-pull-requests-limit: 15
target-branch: develop
groups:
@@ -53,6 +53,13 @@ updates:
cooldown:
default-days: 7
schedule:
interval: "weekly"
interval: "cron"
# Monday at 03:00
cronjob: "0 3 * * 1"
open-pull-requests-limit: 10
target-branch: develop
groups:
actions:
patterns:
# Combine updates for github provided actions
- "actions/*"

View File

@@ -34,7 +34,7 @@ jobs:
run: python build_helpers/binance_update_lev_tiers.py
- uses: peter-evans/create-pull-request@84ae59a2cdc2258d6fa0732dd66352dddae2a412 # v7.0.9
- uses: peter-evans/create-pull-request@98357b18bf14b5342f975ff684046ec3b2a07725 # v8.0.0
with:
token: ${{ secrets.REPO_SCOPED_TOKEN }}
add-paths: freqtrade/exchange/binance_leverage_tiers.json

View File

@@ -25,7 +25,7 @@ jobs:
strategy:
matrix:
os: [ "ubuntu-22.04", "ubuntu-24.04", "macos-14", "macos-15" , "windows-2022", "windows-2025" ]
python-version: ["3.11", "3.12", "3.13"]
python-version: ["3.11", "3.12", "3.13", "3.14"]
steps:
- uses: actions/checkout@v6.0.1
@@ -38,7 +38,7 @@ jobs:
python-version: ${{ matrix.python-version }}
- name: Install uv
uses: astral-sh/setup-uv@1e862dfacbd1d6d858c55d9b792c756523627244 # v7.1.4
uses: astral-sh/setup-uv@681c641aba71e4a1c380be3ab5e12ad51f415867 # v7.1.6
with:
activate-environment: true
enable-cache: true
@@ -74,7 +74,7 @@ jobs:
run: |
pytest --random-order --cov=freqtrade --cov=freqtrade_client --cov-config=.coveragerc
- uses: codecov/codecov-action@5a1091511ad55cbe89839c7260b706298ca349f7 # v5.5.1
- uses: codecov/codecov-action@671740ac38dd9b0130fbe1cec585b89eea48d3de # v5.5.2
if: (runner.os == 'Linux' && matrix.python-version == '3.12' && matrix.os == 'ubuntu-24.04')
with:
fail_ci_if_error: true
@@ -87,12 +87,12 @@ jobs:
rm -rf codecov codecov.SHA256SUM codecov.SHA256SUM.sig
- name: Run json schema extract
# This should be kept before the repository check to ensure that the schema is up-to-date
# This must be kept before the repository check to ensure that the schema is up-to-date
run: |
python build_helpers/extract_config_json_schema.py
- name: Run command docs partials extract
# This should be kept before the repository check to ensure that the docs are up-to-date
# This must be kept before the repository check to ensure that the docs are up-to-date
if: ${{ (matrix.python-version == '3.13') }}
run: |
python build_helpers/create_command_partials.py
@@ -110,7 +110,7 @@ jobs:
fi
- name: Check for repository changes - Windows
if: ${{ runner.os == 'Windows' && (matrix.python-version != '3.13') }}
if: ${{ runner.os == 'Windows' }}
run: |
if (git status --porcelain) {
Write-Host "Repository is dirty, changes detected:"
@@ -159,6 +159,7 @@ jobs:
shell: powershell
run: |
$PSVersionTable
Get-PSRepository | Format-List *
Set-PSRepository psgallery -InstallationPolicy trusted
Install-Module -Name Pester -RequiredVersion 5.3.1 -Confirm:$false -Force -SkipPublisherCheck
$Error.clear()
@@ -250,7 +251,7 @@ jobs:
python-version: "3.12"
- name: Install uv
uses: astral-sh/setup-uv@1e862dfacbd1d6d858c55d9b792c756523627244 # v7.1.4
uses: astral-sh/setup-uv@681c641aba71e4a1c380be3ab5e12ad51f415867 # v7.1.6
with:
activate-environment: true
enable-cache: true
@@ -335,7 +336,7 @@ jobs:
python -m build --sdist --wheel
- name: Upload artifacts 📦
uses: actions/upload-artifact@v5
uses: actions/upload-artifact@v6
with:
name: freqtrade-build
path: |
@@ -348,7 +349,7 @@ jobs:
python -m build --sdist --wheel ft_client
- name: Upload artifacts 📦
uses: actions/upload-artifact@v5
uses: actions/upload-artifact@v6
with:
name: freqtrade-client-build
path: |
@@ -372,7 +373,7 @@ jobs:
persist-credentials: false
- name: Download artifact 📦
uses: actions/download-artifact@v6
uses: actions/download-artifact@v7
with:
pattern: freqtrade*-build
path: dist
@@ -401,7 +402,7 @@ jobs:
persist-credentials: false
- name: Download artifact 📦
uses: actions/download-artifact@v6
uses: actions/download-artifact@v7
with:
pattern: freqtrade*-build
path: dist

View File

@@ -40,6 +40,14 @@ jobs:
- name: Visualize disk usage before build
run: df -h
- name: Cleanup some disk space
run: |
docker system prune -a --force || true
docker builder prune -af || true
- name: Visualize disk usage after cleanup
run: df -h
- name: Set docker tag names
id: tags
uses: ./.github/actions/docker-tags
@@ -57,7 +65,7 @@ jobs:
- name: Set up Docker Buildx
id: buildx
uses: docker/setup-buildx-action@e468171a9de216ec08956ac3ada2f0791b6bd435 #v3.11.1
uses: docker/setup-buildx-action@8d2750c68a42422c14e847fe6c8ac0403b4cbd6f #v3.12.0
- name: Available platforms
run: echo ${PLATFORMS}
@@ -282,6 +290,7 @@ jobs:
docker buildx imagetools create \
--tag ${GHCR_IMAGE_NAME}:${TAG} \
--tag ${GHCR_IMAGE_NAME}:latest \
--tag ${IMAGE_NAME}:latest \
${IMAGE_NAME}:${TAG}
- name: Docker images

View File

@@ -28,7 +28,7 @@ jobs:
- name: Run auto-update
run: pre-commit autoupdate
- uses: peter-evans/create-pull-request@84ae59a2cdc2258d6fa0732dd66352dddae2a412 # v7.0.9
- uses: peter-evans/create-pull-request@98357b18bf14b5342f975ff684046ec3b2a07725 # v8.0.0
with:
token: ${{ secrets.REPO_SCOPED_TOKEN }}
add-paths: .pre-commit-config.yaml

View File

@@ -31,8 +31,8 @@ repos:
- types-requests==2.32.4.20250913
- types-tabulate==0.9.0.20241207
- types-python-dateutil==2.9.0.20251115
- scipy-stubs==1.16.3.2
- SQLAlchemy==2.0.44
- scipy-stubs==1.16.3.3
- SQLAlchemy==2.0.45
# stages: [push]
- repo: https://github.com/pycqa/isort
@@ -44,7 +44,7 @@ repos:
- repo: https://github.com/charliermarsh/ruff-pre-commit
# Ruff version.
rev: 'v0.14.9'
rev: 'v0.14.10'
hooks:
- id: ruff
- id: ruff-format
@@ -83,6 +83,6 @@ repos:
# Ensure github actions remain safe
- repo: https://github.com/woodruffw/zizmor-pre-commit
rev: v1.18.0
rev: v1.19.0
hooks:
- id: zizmor

View File

@@ -1,4 +1,4 @@
FROM python:3.13.8-slim-bookworm AS base
FROM python:3.13.11-slim-trixie AS base
# Setup env
ENV LANG=C.UTF-8

View File

@@ -1,5 +1,7 @@
import os
import subprocess # noqa: S404, RUF100
import sys
from io import StringIO
from pathlib import Path
@@ -8,7 +10,20 @@ def _write_partial_file(filename: str, content: str):
f.write(f"``` output\n{content}\n```\n")
def _get_help_output(parser) -> str:
"""Capture the help output from a parser."""
output = StringIO()
parser.print_help(file=output)
return output.getvalue()
def extract_command_partials():
# Set terminal width to 80 columns for consistent output formatting
os.environ["COLUMNS"] = "80"
# Import Arguments here to avoid circular imports and ensure COLUMNS is set
from freqtrade.commands.arguments import Arguments
subcommands = [
"trade",
"create-userdir",
@@ -46,16 +61,35 @@ def extract_command_partials():
"recursive-analysis",
]
result = subprocess.run(["freqtrade", "--help"], capture_output=True, text=True)
# Build the Arguments class to get the parser with all subcommands
args = Arguments(None)
args._build_subcommands()
_write_partial_file("docs/commands/main.md", result.stdout)
# Get main help output
main_help = _get_help_output(args.parser)
_write_partial_file("docs/commands/main.md", main_help)
# Get subparsers from the main parser
# The subparsers are stored in _subparsers._group_actions[0].choices
subparsers_action = None
for action in args.parser._subparsers._group_actions:
if hasattr(action, "choices"):
subparsers_action = action
break
if subparsers_action is None:
raise RuntimeError("Could not find subparsers in the main parser")
for command in subcommands:
print(f"Running for {command}")
result = subprocess.run(["freqtrade", command, "--help"], capture_output=True, text=True)
_write_partial_file(f"docs/commands/{command}.md", result.stdout)
if command in subparsers_action.choices:
subparser = subparsers_action.choices[command]
help_output = _get_help_output(subparser)
_write_partial_file(f"docs/commands/{command}.md", help_output)
else:
print(f" Warning: subcommand '{command}' not found in parser")
# freqtrade-client still uses subprocess as requested
print("Running for freqtrade-client")
result_client = subprocess.run(["freqtrade-client", "--show"], capture_output=True, text=True)

View File

@@ -1,4 +1,4 @@
FROM python:3.11.13-slim-bookworm AS base
FROM python:3.11.14-slim-bookworm AS base
# Setup env
ENV LANG=C.UTF-8

View File

@@ -43,7 +43,9 @@ options:
separated.
--eps, --enable-position-stacking
Allow buying the same pair multiple times (position
stacking).
stacking). Only applicable to backtesting and
hyperopt. Results archived by this cannot be
reproduced in dry/live trading.
--enable-protections, --enableprotections
Enable protections for backtesting. Will slow
backtesting down by a considerable amount, but will

View File

@@ -41,7 +41,9 @@ options:
functions.
--eps, --enable-position-stacking
Allow buying the same pair multiple times (position
stacking).
stacking). Only applicable to backtesting and
hyperopt. Results archived by this cannot be
reproduced in dry/live trading.
--enable-protections, --enableprotections
Enable protections for backtesting. Will slow
backtesting down by a considerable amount, but will

View File

@@ -135,3 +135,13 @@ you can verify this with `freqtrade list-data --exchange <yourexchange> --show`.
Additional arguments to the above commands may be necessary, like configuration files or explicit user_data if they deviate from the default.
**Hyperliquid** is a special case now - which will no longer require 1h mark data - but will use regular candles instead (this data never existed and is identical to 1h futures candles). As we don't support download-data for hyperliquid (they don't provide historic data) - there won't be actions necessary for hyperliquid users.
## Catboost models in freqAI
CatBoost models have been removed with version 2025.12 and are no longer actively supported.
If you have existing bots using CatBoost models, you can still use them in your custom models by copy/pasting them from the git history (as linked below) and installing the Catboost library manually.
We do however recommend switching to other supported model libraries like LightGBM or XGBoost for better support and future compatibility.
* [CatboostRegressor](https://github.com/freqtrade/freqtrade/blob/c6f3b0081927e161a16b116cc47fb663f7831d30/freqtrade/freqai/prediction_models/CatboostRegressor.py)
* [CatboostClassifier](https://github.com/freqtrade/freqtrade/blob/c6f3b0081927e161a16b116cc47fb663f7831d30/freqtrade/freqai/prediction_models/CatboostClassifier.py)
* [CatboostClassifierMultiTarget](https://github.com/freqtrade/freqtrade/blob/c6f3b0081927e161a16b116cc47fb663f7831d30/freqtrade/freqai/prediction_models/CatboostClassifierMultiTarget.py)

View File

@@ -432,7 +432,6 @@ freqtrade download-data --timerange 20250625-20250801 --config tests/testdata/co
freqtrade backtesting --config tests/testdata/config.tests.usdt.json -s SampleStrategy --userdir user_data_bttest/ --cache none --timerange 20250701-20250801
```
## Continuous integration
This documents some decisions taken for the CI Pipeline.
@@ -464,10 +463,10 @@ git checkout -b new_release <commitid>
Determine if crucial bugfixes have been made between this commit and the current state, and eventually cherry-pick these.
* Merge the release branch (stable) into this branch.
* Edit `freqtrade/__init__.py` and add the version matching the current date (for example `2019.7` for July 2019). Minor versions can be `2019.7.1` should we need to do a second release that month. Version numbers must follow allowed versions from PEP0440 to avoid failures pushing to pypi.
* Edit `freqtrade/__init__.py` and add the version matching the current date (for example `2025.7` for July 2025). Minor versions can be `2025.7.1` should we need to do a second release that month. Version numbers must follow allowed versions from PEP0440 to avoid failures pushing to pypi.
* Commit this part.
* Push that branch to the remote and create a PR against the **stable branch**.
* Update develop version to next version following the pattern `2019.8-dev`.
* Update develop version to next version following the pattern `2025.8-dev`.
### Create changelog from git commits

View File

@@ -200,15 +200,15 @@ If this value is set, FreqAI will initially use the predictions from the trainin
## Using different prediction models
FreqAI has multiple example prediction model libraries that are ready to be used as is via the flag `--freqaimodel`. These libraries include `CatBoost`, `LightGBM`, and `XGBoost` regression, classification, and multi-target models, and can be found in `freqai/prediction_models/`.
FreqAI has multiple example prediction model libraries that are ready to be used as is via the flag `--freqaimodel`. These libraries include `LightGBM`, and `XGBoost` regression, classification, and multi-target models, and can be found in `freqai/prediction_models/`.
Regression and classification models differ in what targets they predict - a regression model will predict a target of continuous values, for example what price BTC will be at tomorrow, whilst a classifier will predict a target of discrete values, for example if the price of BTC will go up tomorrow or not. This means that you have to specify your targets differently depending on which model type you are using (see details [below](#setting-model-targets)).
All of the aforementioned model libraries implement gradient boosted decision tree algorithms. They all work on the principle of ensemble learning, where predictions from multiple simple learners are combined to get a final prediction that is more stable and generalized. The simple learners in this case are decision trees. Gradient boosting refers to the method of learning, where each simple learner is built in sequence - the subsequent learner is used to improve on the error from the previous learner. If you want to learn more about the different model libraries you can find the information in their respective docs:
* CatBoost: https://catboost.ai/en/docs/
* LightGBM: https://lightgbm.readthedocs.io/en/v3.3.2/#
* XGBoost: https://xgboost.readthedocs.io/en/stable/#
* LightGBM: <https://lightgbm.readthedocs.io/en/v3.3.2/#>
* XGBoost: <https://xgboost.readthedocs.io/en/stable/#>
* CatBoost: <https://catboost.ai/en/docs/> (No longer actively supported since 2025.12)
There are also numerous online articles describing and comparing the algorithms. Some relatively lightweight examples would be [CatBoost vs. LightGBM vs. XGBoost — Which is the best algorithm?](https://towardsdatascience.com/catboost-vs-lightgbm-vs-xgboost-c80f40662924#:~:text=In%20CatBoost%2C%20symmetric%20trees%2C%20or,the%20same%20depth%20can%20differ.) and [XGBoost, LightGBM or CatBoost — which boosting algorithm should I use?](https://medium.com/riskified-technology/xgboost-lightgbm-or-catboost-which-boosting-algorithm-should-i-use-e7fda7bb36bc). Keep in mind that the performance of each model is highly dependent on the application and so any reported metrics might not be true for your particular use of the model.
@@ -219,7 +219,7 @@ Make sure to use unique names to avoid overriding built-in models.
#### Regressors
If you are using a regressor, you need to specify a target that has continuous values. FreqAI includes a variety of regressors, such as the `CatboostRegressor`via the flag `--freqaimodel CatboostRegressor`. An example of how you could set a regression target for predicting the price 100 candles into the future would be
If you are using a regressor, you need to specify a target that has continuous values. FreqAI includes a variety of regressors, such as the `LightGBMRegressor`via the flag `--freqaimodel LightGBMRegressor`. An example of how you could set a regression target for predicting the price 100 candles into the future would be
```python
df['&s-close_price'] = df['close'].shift(-100)
@@ -229,7 +229,7 @@ If you want to predict multiple targets, you need to define multiple labels usin
#### Classifiers
If you are using a classifier, you need to specify a target that has discrete values. FreqAI includes a variety of classifiers, such as the `CatboostClassifier` via the flag `--freqaimodel CatboostClassifier`. If you elects to use a classifier, the classes need to be set using strings. For example, if you want to predict if the price 100 candles into the future goes up or down you would set
If you are using a classifier, you need to specify a target that has discrete values. FreqAI includes a variety of classifiers, such as the `LightGBMClassifier` via the flag `--freqaimodel LightGBMClassifier`. If you elects to use a classifier, the classes need to be set using strings. For example, if you want to predict if the price 100 candles into the future goes up or down you would set
```python
df['&s-up_or_down'] = np.where( df["close"].shift(-100) > df["close"], 'up', 'down')

View File

@@ -107,7 +107,6 @@ Mandatory parameters are marked as **Required** and have to be set in one of the
| `n_steps` | An alternative way of setting `n_epochs` - the number of training iterations to run. Iteration here refer to the number of times we call `optimizer.step()`. Ignored if `n_epochs` is set. A simplified version of the function: <br><br> n_epochs = n_steps / (n_obs / batch_size) <br><br> The motivation here is that `n_steps` is easier to optimize and keep stable across different n_obs - the number of data points. <br> <br> **Datatype:** int. optional. <br> Default: `None`.
| `batch_size` | The size of the batches to use during training. <br><br> **Datatype:** int. <br> Default: `64`.
### Additional parameters
| Parameter | Description |
@@ -116,3 +115,4 @@ Mandatory parameters are marked as **Required** and have to be set in one of the
| `freqai.keras` | If the selected model makes use of Keras (typical for TensorFlow-based prediction models), this flag needs to be activated so that the model save/loading follows Keras standards. <br> **Datatype:** Boolean. <br> Default: `False`.
| `freqai.conv_width` | The width of a neural network input tensor. This replaces the need for shifting candles (`include_shifted_candles`) by feeding in historical data points as the second dimension of the tensor. Technically, this parameter can also be used for regressors, but it only adds computational overhead and does not change the model training/prediction. <br> **Datatype:** Integer. <br> Default: `2`.
| `freqai.reduce_df_footprint` | Recast all numeric columns to float32/int32, with the objective of reducing ram/disk usage and decreasing train/inference timing. This parameter is set in the main level of the Freqtrade configuration file (not inside FreqAI). <br> **Datatype:** Boolean. <br> Default: `False`.
| `freqai.override_exchange_check` | Override the exchange check to force FreqAI to use exchanges that may not have enough historic data. Turn this to True if you know your FreqAI model and strategy do not require historical data. <br> **Datatype:** Boolean. <br> Default: `False`.

View File

@@ -1,28 +1,28 @@
## Highlighted changes
# Highlighted changes
- ...
### How to update
## How to update
As always, you can update your bot using one of the following commands:
#### docker-compose
### docker-compose
```bash
docker-compose pull
docker-compose up -d
```
#### Installation via setup script
### Installation via setup script
```
``` bash
# Deactivate venv and run
./setup.sh --update
```
#### Plain native installation
### Plain native installation
```
``` bash
git pull
pip install -U -r requirements.txt
```

View File

@@ -1,7 +1,7 @@
markdown==3.10
mkdocs==1.6.1
mkdocs-material==9.7.0
mkdocs-material==9.7.1
mdx_truly_sane_lists==1.3
pymdown-extensions==10.18
pymdown-extensions==10.19.1
jinja2==3.1.6
mike==2.1.3

View File

@@ -1,6 +1,6 @@
"""Freqtrade bot"""
__version__ = "2025.12-dev"
__version__ = "2026.1-dev"
if "dev" in __version__:
from pathlib import Path

View File

@@ -180,7 +180,11 @@ AVAILABLE_CLI_OPTIONS = {
"position_stacking": Arg(
"--eps",
"--enable-position-stacking",
help="Allow buying the same pair multiple times (position stacking).",
help=(
"Allow buying the same pair multiple times (position stacking). "
"Only applicable to backtesting and hyperopt. "
"Results archived by this cannot be reproduced in dry/live trading."
),
action="store_true",
default=False,
),

View File

@@ -36764,15 +36764,15 @@
"symbol": "FXS/USDT:USDT",
"currency": "USDT",
"minNotional": 0.0,
"maxNotional": 5000.0,
"maintenanceMarginRate": 0.01,
"maxLeverage": 75.0,
"maxNotional": 10000.0,
"maintenanceMarginRate": 0.025,
"maxLeverage": 20.0,
"info": {
"bracket": "1",
"initialLeverage": "75",
"notionalCap": "5000",
"initialLeverage": "20",
"notionalCap": "10000",
"notionalFloor": "0",
"maintMarginRatio": "0.01",
"maintMarginRatio": "0.025",
"cum": "0.0"
}
},
@@ -36780,153 +36780,102 @@
"tier": 2.0,
"symbol": "FXS/USDT:USDT",
"currency": "USDT",
"minNotional": 5000.0,
"maxNotional": 10000.0,
"maintenanceMarginRate": 0.015,
"maxLeverage": 50.0,
"minNotional": 10000.0,
"maxNotional": 20000.0,
"maintenanceMarginRate": 0.05,
"maxLeverage": 10.0,
"info": {
"bracket": "2",
"initialLeverage": "50",
"notionalCap": "10000",
"notionalFloor": "5000",
"maintMarginRatio": "0.015",
"cum": "25.0"
"initialLeverage": "10",
"notionalCap": "20000",
"notionalFloor": "10000",
"maintMarginRatio": "0.05",
"cum": "250.0"
}
},
{
"tier": 3.0,
"symbol": "FXS/USDT:USDT",
"currency": "USDT",
"minNotional": 10000.0,
"maxNotional": 25000.0,
"maintenanceMarginRate": 0.02,
"maxLeverage": 25.0,
"minNotional": 20000.0,
"maxNotional": 60000.0,
"maintenanceMarginRate": 0.1,
"maxLeverage": 5.0,
"info": {
"bracket": "3",
"initialLeverage": "25",
"notionalCap": "25000",
"notionalFloor": "10000",
"maintMarginRatio": "0.02",
"cum": "75.0"
"initialLeverage": "5",
"notionalCap": "60000",
"notionalFloor": "20000",
"maintMarginRatio": "0.1",
"cum": "1250.0"
}
},
{
"tier": 4.0,
"symbol": "FXS/USDT:USDT",
"currency": "USDT",
"minNotional": 25000.0,
"maxNotional": 50000.0,
"maintenanceMarginRate": 0.025,
"maxLeverage": 20.0,
"minNotional": 60000.0,
"maxNotional": 150000.0,
"maintenanceMarginRate": 0.125,
"maxLeverage": 4.0,
"info": {
"bracket": "4",
"initialLeverage": "20",
"notionalCap": "50000",
"notionalFloor": "25000",
"maintMarginRatio": "0.025",
"cum": "200.0"
"initialLeverage": "4",
"notionalCap": "150000",
"notionalFloor": "60000",
"maintMarginRatio": "0.125",
"cum": "2750.0"
}
},
{
"tier": 5.0,
"symbol": "FXS/USDT:USDT",
"currency": "USDT",
"minNotional": 50000.0,
"maxNotional": 125000.0,
"maintenanceMarginRate": 0.05,
"maxLeverage": 10.0,
"minNotional": 150000.0,
"maxNotional": 250000.0,
"maintenanceMarginRate": 0.1667,
"maxLeverage": 3.0,
"info": {
"bracket": "5",
"initialLeverage": "10",
"notionalCap": "125000",
"notionalFloor": "50000",
"maintMarginRatio": "0.05",
"cum": "1450.0"
"initialLeverage": "3",
"notionalCap": "250000",
"notionalFloor": "150000",
"maintMarginRatio": "0.1667",
"cum": "9005.0"
}
},
{
"tier": 6.0,
"symbol": "FXS/USDT:USDT",
"currency": "USDT",
"minNotional": 125000.0,
"maxNotional": 250000.0,
"maintenanceMarginRate": 0.1,
"maxLeverage": 5.0,
"minNotional": 250000.0,
"maxNotional": 2500000.0,
"maintenanceMarginRate": 0.25,
"maxLeverage": 2.0,
"info": {
"bracket": "6",
"initialLeverage": "5",
"notionalCap": "250000",
"notionalFloor": "125000",
"maintMarginRatio": "0.1",
"cum": "7700.0"
"initialLeverage": "2",
"notionalCap": "2500000",
"notionalFloor": "250000",
"maintMarginRatio": "0.25",
"cum": "29830.0"
}
},
{
"tier": 7.0,
"symbol": "FXS/USDT:USDT",
"currency": "USDT",
"minNotional": 250000.0,
"maxNotional": 500000.0,
"maintenanceMarginRate": 0.125,
"maxLeverage": 4.0,
"info": {
"bracket": "7",
"initialLeverage": "4",
"notionalCap": "500000",
"notionalFloor": "250000",
"maintMarginRatio": "0.125",
"cum": "13950.0"
}
},
{
"tier": 8.0,
"symbol": "FXS/USDT:USDT",
"currency": "USDT",
"minNotional": 500000.0,
"maxNotional": 1000000.0,
"maintenanceMarginRate": 0.1667,
"maxLeverage": 3.0,
"info": {
"bracket": "8",
"initialLeverage": "3",
"notionalCap": "1000000",
"notionalFloor": "500000",
"maintMarginRatio": "0.1667",
"cum": "34800.0"
}
},
{
"tier": 9.0,
"symbol": "FXS/USDT:USDT",
"currency": "USDT",
"minNotional": 1000000.0,
"maxNotional": 7500000.0,
"maintenanceMarginRate": 0.25,
"maxLeverage": 2.0,
"info": {
"bracket": "9",
"initialLeverage": "2",
"notionalCap": "7500000",
"notionalFloor": "1000000",
"maintMarginRatio": "0.25",
"cum": "118100.0"
}
},
{
"tier": 10.0,
"symbol": "FXS/USDT:USDT",
"currency": "USDT",
"minNotional": 7500000.0,
"maxNotional": 12500000.0,
"minNotional": 2500000.0,
"maxNotional": 5000000.0,
"maintenanceMarginRate": 0.5,
"maxLeverage": 1.0,
"info": {
"bracket": "10",
"bracket": "7",
"initialLeverage": "1",
"notionalCap": "12500000",
"notionalFloor": "7500000",
"notionalCap": "5000000",
"notionalFloor": "2500000",
"maintMarginRatio": "0.5",
"cum": "1993100.0"
"cum": "654830.0"
}
}
],
@@ -39393,6 +39342,127 @@
}
}
],
"GUA/USDT:USDT": [
{
"tier": 1.0,
"symbol": "GUA/USDT:USDT",
"currency": "USDT",
"minNotional": 0.0,
"maxNotional": 5000.0,
"maintenanceMarginRate": 0.025,
"maxLeverage": 20.0,
"info": {
"bracket": "1",
"initialLeverage": "20",
"notionalCap": "5000",
"notionalFloor": "0",
"maintMarginRatio": "0.025",
"cum": "0.0"
}
},
{
"tier": 2.0,
"symbol": "GUA/USDT:USDT",
"currency": "USDT",
"minNotional": 5000.0,
"maxNotional": 10000.0,
"maintenanceMarginRate": 0.05,
"maxLeverage": 10.0,
"info": {
"bracket": "2",
"initialLeverage": "10",
"notionalCap": "10000",
"notionalFloor": "5000",
"maintMarginRatio": "0.05",
"cum": "125.0"
}
},
{
"tier": 3.0,
"symbol": "GUA/USDT:USDT",
"currency": "USDT",
"minNotional": 10000.0,
"maxNotional": 50000.0,
"maintenanceMarginRate": 0.1,
"maxLeverage": 5.0,
"info": {
"bracket": "3",
"initialLeverage": "5",
"notionalCap": "50000",
"notionalFloor": "10000",
"maintMarginRatio": "0.1",
"cum": "625.0"
}
},
{
"tier": 4.0,
"symbol": "GUA/USDT:USDT",
"currency": "USDT",
"minNotional": 50000.0,
"maxNotional": 100000.0,
"maintenanceMarginRate": 0.125,
"maxLeverage": 4.0,
"info": {
"bracket": "4",
"initialLeverage": "4",
"notionalCap": "100000",
"notionalFloor": "50000",
"maintMarginRatio": "0.125",
"cum": "1875.0"
}
},
{
"tier": 5.0,
"symbol": "GUA/USDT:USDT",
"currency": "USDT",
"minNotional": 100000.0,
"maxNotional": 250000.0,
"maintenanceMarginRate": 0.1667,
"maxLeverage": 3.0,
"info": {
"bracket": "5",
"initialLeverage": "3",
"notionalCap": "250000",
"notionalFloor": "100000",
"maintMarginRatio": "0.1667",
"cum": "6045.0"
}
},
{
"tier": 6.0,
"symbol": "GUA/USDT:USDT",
"currency": "USDT",
"minNotional": 250000.0,
"maxNotional": 500000.0,
"maintenanceMarginRate": 0.25,
"maxLeverage": 2.0,
"info": {
"bracket": "6",
"initialLeverage": "2",
"notionalCap": "500000",
"notionalFloor": "250000",
"maintMarginRatio": "0.25",
"cum": "26870.0"
}
},
{
"tier": 7.0,
"symbol": "GUA/USDT:USDT",
"currency": "USDT",
"minNotional": 500000.0,
"maxNotional": 800000.0,
"maintenanceMarginRate": 0.5,
"maxLeverage": 1.0,
"info": {
"bracket": "7",
"initialLeverage": "1",
"notionalCap": "800000",
"notionalFloor": "500000",
"maintMarginRatio": "0.5",
"cum": "151870.0"
}
}
],
"GUN/USDT:USDT": [
{
"tier": 1.0,
@@ -45062,6 +45132,144 @@
}
}
],
"IR/USDT:USDT": [
{
"tier": 1.0,
"symbol": "IR/USDT:USDT",
"currency": "USDT",
"minNotional": 0.0,
"maxNotional": 5000.0,
"maintenanceMarginRate": 0.02,
"maxLeverage": 40.0,
"info": {
"bracket": "1",
"initialLeverage": "40",
"notionalCap": "5000",
"notionalFloor": "0",
"maintMarginRatio": "0.02",
"cum": "0.0"
}
},
{
"tier": 2.0,
"symbol": "IR/USDT:USDT",
"currency": "USDT",
"minNotional": 5000.0,
"maxNotional": 10000.0,
"maintenanceMarginRate": 0.025,
"maxLeverage": 20.0,
"info": {
"bracket": "2",
"initialLeverage": "20",
"notionalCap": "10000",
"notionalFloor": "5000",
"maintMarginRatio": "0.025",
"cum": "25.0"
}
},
{
"tier": 3.0,
"symbol": "IR/USDT:USDT",
"currency": "USDT",
"minNotional": 10000.0,
"maxNotional": 20000.0,
"maintenanceMarginRate": 0.05,
"maxLeverage": 10.0,
"info": {
"bracket": "3",
"initialLeverage": "10",
"notionalCap": "20000",
"notionalFloor": "10000",
"maintMarginRatio": "0.05",
"cum": "275.0"
}
},
{
"tier": 4.0,
"symbol": "IR/USDT:USDT",
"currency": "USDT",
"minNotional": 20000.0,
"maxNotional": 50000.0,
"maintenanceMarginRate": 0.1,
"maxLeverage": 5.0,
"info": {
"bracket": "4",
"initialLeverage": "5",
"notionalCap": "50000",
"notionalFloor": "20000",
"maintMarginRatio": "0.1",
"cum": "1275.0"
}
},
{
"tier": 5.0,
"symbol": "IR/USDT:USDT",
"currency": "USDT",
"minNotional": 50000.0,
"maxNotional": 100000.0,
"maintenanceMarginRate": 0.125,
"maxLeverage": 4.0,
"info": {
"bracket": "5",
"initialLeverage": "4",
"notionalCap": "100000",
"notionalFloor": "50000",
"maintMarginRatio": "0.125",
"cum": "2525.0"
}
},
{
"tier": 6.0,
"symbol": "IR/USDT:USDT",
"currency": "USDT",
"minNotional": 100000.0,
"maxNotional": 250000.0,
"maintenanceMarginRate": 0.1667,
"maxLeverage": 3.0,
"info": {
"bracket": "6",
"initialLeverage": "3",
"notionalCap": "250000",
"notionalFloor": "100000",
"maintMarginRatio": "0.1667",
"cum": "6695.0"
}
},
{
"tier": 7.0,
"symbol": "IR/USDT:USDT",
"currency": "USDT",
"minNotional": 250000.0,
"maxNotional": 2500000.0,
"maintenanceMarginRate": 0.25,
"maxLeverage": 2.0,
"info": {
"bracket": "7",
"initialLeverage": "2",
"notionalCap": "2500000",
"notionalFloor": "250000",
"maintMarginRatio": "0.25",
"cum": "27520.0"
}
},
{
"tier": 8.0,
"symbol": "IR/USDT:USDT",
"currency": "USDT",
"minNotional": 2500000.0,
"maxNotional": 5000000.0,
"maintenanceMarginRate": 0.5,
"maxLeverage": 1.0,
"info": {
"bracket": "8",
"initialLeverage": "1",
"notionalCap": "5000000",
"notionalFloor": "2500000",
"maintMarginRatio": "0.5",
"cum": "652520.0"
}
}
],
"IRYS/USDT:USDT": [
{
"tier": 1.0,
@@ -50238,10 +50446,10 @@
"minNotional": 0.0,
"maxNotional": 5000.0,
"maintenanceMarginRate": 0.015,
"maxLeverage": 10.0,
"maxLeverage": 50.0,
"info": {
"bracket": "1",
"initialLeverage": "10",
"initialLeverage": "50",
"notionalCap": "5000",
"notionalFloor": "0",
"maintMarginRatio": "0.015",
@@ -50253,13 +50461,13 @@
"symbol": "LIT/USDT:USDT",
"currency": "USDT",
"minNotional": 5000.0,
"maxNotional": 20000.0,
"maxNotional": 10000.0,
"maintenanceMarginRate": 0.02,
"maxLeverage": 8.0,
"maxLeverage": 25.0,
"info": {
"bracket": "2",
"initialLeverage": "8",
"notionalCap": "20000",
"initialLeverage": "25",
"notionalCap": "10000",
"notionalFloor": "5000",
"maintMarginRatio": "0.02",
"cum": "25.0"
@@ -50269,17 +50477,17 @@
"tier": 3.0,
"symbol": "LIT/USDT:USDT",
"currency": "USDT",
"minNotional": 20000.0,
"minNotional": 10000.0,
"maxNotional": 25000.0,
"maintenanceMarginRate": 0.025,
"maxLeverage": 6.0,
"maxLeverage": 20.0,
"info": {
"bracket": "3",
"initialLeverage": "6",
"initialLeverage": "20",
"notionalCap": "25000",
"notionalFloor": "20000",
"notionalFloor": "10000",
"maintMarginRatio": "0.025",
"cum": "125.0"
"cum": "75.0"
}
},
{
@@ -50287,84 +50495,101 @@
"symbol": "LIT/USDT:USDT",
"currency": "USDT",
"minNotional": 25000.0,
"maxNotional": 200000.0,
"maxNotional": 62500.0,
"maintenanceMarginRate": 0.05,
"maxLeverage": 5.0,
"maxLeverage": 10.0,
"info": {
"bracket": "4",
"initialLeverage": "5",
"notionalCap": "200000",
"initialLeverage": "10",
"notionalCap": "62500",
"notionalFloor": "25000",
"maintMarginRatio": "0.05",
"cum": "750.0"
"cum": "700.0"
}
},
{
"tier": 5.0,
"symbol": "LIT/USDT:USDT",
"currency": "USDT",
"minNotional": 200000.0,
"maxNotional": 400000.0,
"minNotional": 62500.0,
"maxNotional": 125000.0,
"maintenanceMarginRate": 0.1,
"maxLeverage": 4.0,
"maxLeverage": 5.0,
"info": {
"bracket": "5",
"initialLeverage": "4",
"notionalCap": "400000",
"notionalFloor": "200000",
"initialLeverage": "5",
"notionalCap": "125000",
"notionalFloor": "62500",
"maintMarginRatio": "0.1",
"cum": "10750.0"
"cum": "3825.0"
}
},
{
"tier": 6.0,
"symbol": "LIT/USDT:USDT",
"currency": "USDT",
"minNotional": 400000.0,
"maxNotional": 500000.0,
"minNotional": 125000.0,
"maxNotional": 250000.0,
"maintenanceMarginRate": 0.125,
"maxLeverage": 3.0,
"maxLeverage": 4.0,
"info": {
"bracket": "6",
"initialLeverage": "3",
"notionalCap": "500000",
"notionalFloor": "400000",
"initialLeverage": "4",
"notionalCap": "250000",
"notionalFloor": "125000",
"maintMarginRatio": "0.125",
"cum": "20750.0"
"cum": "6950.0"
}
},
{
"tier": 7.0,
"symbol": "LIT/USDT:USDT",
"currency": "USDT",
"minNotional": 500000.0,
"maxNotional": 520000.0,
"maintenanceMarginRate": 0.25,
"maxLeverage": 2.0,
"minNotional": 250000.0,
"maxNotional": 500000.0,
"maintenanceMarginRate": 0.1667,
"maxLeverage": 3.0,
"info": {
"bracket": "7",
"initialLeverage": "2",
"notionalCap": "520000",
"notionalFloor": "500000",
"maintMarginRatio": "0.25",
"cum": "83250.0"
"initialLeverage": "3",
"notionalCap": "500000",
"notionalFloor": "250000",
"maintMarginRatio": "0.1667",
"cum": "17375.0"
}
},
{
"tier": 8.0,
"symbol": "LIT/USDT:USDT",
"currency": "USDT",
"minNotional": 520000.0,
"maxNotional": 550000.0,
"minNotional": 500000.0,
"maxNotional": 7500000.0,
"maintenanceMarginRate": 0.25,
"maxLeverage": 2.0,
"info": {
"bracket": "8",
"initialLeverage": "2",
"notionalCap": "7500000",
"notionalFloor": "500000",
"maintMarginRatio": "0.25",
"cum": "59025.0"
}
},
{
"tier": 9.0,
"symbol": "LIT/USDT:USDT",
"currency": "USDT",
"minNotional": 7500000.0,
"maxNotional": 12500000.0,
"maintenanceMarginRate": 0.5,
"maxLeverage": 1.0,
"info": {
"bracket": "8",
"bracket": "9",
"initialLeverage": "1",
"notionalCap": "550000",
"notionalFloor": "520000",
"notionalCap": "12500000",
"notionalFloor": "7500000",
"maintMarginRatio": "0.5",
"cum": "213250.0"
"cum": "1934025.0"
}
}
],
@@ -95892,6 +96117,144 @@
}
}
],
"ZKP/USDT:USDT": [
{
"tier": 1.0,
"symbol": "ZKP/USDT:USDT",
"currency": "USDT",
"minNotional": 0.0,
"maxNotional": 5000.0,
"maintenanceMarginRate": 0.02,
"maxLeverage": 40.0,
"info": {
"bracket": "1",
"initialLeverage": "40",
"notionalCap": "5000",
"notionalFloor": "0",
"maintMarginRatio": "0.02",
"cum": "0.0"
}
},
{
"tier": 2.0,
"symbol": "ZKP/USDT:USDT",
"currency": "USDT",
"minNotional": 5000.0,
"maxNotional": 10000.0,
"maintenanceMarginRate": 0.025,
"maxLeverage": 20.0,
"info": {
"bracket": "2",
"initialLeverage": "20",
"notionalCap": "10000",
"notionalFloor": "5000",
"maintMarginRatio": "0.025",
"cum": "25.0"
}
},
{
"tier": 3.0,
"symbol": "ZKP/USDT:USDT",
"currency": "USDT",
"minNotional": 10000.0,
"maxNotional": 20000.0,
"maintenanceMarginRate": 0.05,
"maxLeverage": 10.0,
"info": {
"bracket": "3",
"initialLeverage": "10",
"notionalCap": "20000",
"notionalFloor": "10000",
"maintMarginRatio": "0.05",
"cum": "275.0"
}
},
{
"tier": 4.0,
"symbol": "ZKP/USDT:USDT",
"currency": "USDT",
"minNotional": 20000.0,
"maxNotional": 50000.0,
"maintenanceMarginRate": 0.1,
"maxLeverage": 5.0,
"info": {
"bracket": "4",
"initialLeverage": "5",
"notionalCap": "50000",
"notionalFloor": "20000",
"maintMarginRatio": "0.1",
"cum": "1275.0"
}
},
{
"tier": 5.0,
"symbol": "ZKP/USDT:USDT",
"currency": "USDT",
"minNotional": 50000.0,
"maxNotional": 100000.0,
"maintenanceMarginRate": 0.125,
"maxLeverage": 4.0,
"info": {
"bracket": "5",
"initialLeverage": "4",
"notionalCap": "100000",
"notionalFloor": "50000",
"maintMarginRatio": "0.125",
"cum": "2525.0"
}
},
{
"tier": 6.0,
"symbol": "ZKP/USDT:USDT",
"currency": "USDT",
"minNotional": 100000.0,
"maxNotional": 250000.0,
"maintenanceMarginRate": 0.1667,
"maxLeverage": 3.0,
"info": {
"bracket": "6",
"initialLeverage": "3",
"notionalCap": "250000",
"notionalFloor": "100000",
"maintMarginRatio": "0.1667",
"cum": "6695.0"
}
},
{
"tier": 7.0,
"symbol": "ZKP/USDT:USDT",
"currency": "USDT",
"minNotional": 250000.0,
"maxNotional": 2500000.0,
"maintenanceMarginRate": 0.25,
"maxLeverage": 2.0,
"info": {
"bracket": "7",
"initialLeverage": "2",
"notionalCap": "2500000",
"notionalFloor": "250000",
"maintMarginRatio": "0.25",
"cum": "27520.0"
}
},
{
"tier": 8.0,
"symbol": "ZKP/USDT:USDT",
"currency": "USDT",
"minNotional": 2500000.0,
"maxNotional": 5000000.0,
"maintenanceMarginRate": 0.5,
"maxLeverage": 1.0,
"info": {
"bracket": "8",
"initialLeverage": "1",
"notionalCap": "5000000",
"notionalFloor": "2500000",
"maintMarginRatio": "0.5",
"cum": "652520.0"
}
}
],
"ZORA/USDT:USDT": [
{
"tier": 1.0,

View File

@@ -709,7 +709,7 @@ class Exchange:
self._markets = self._api_async.markets
self._api.set_markets_from_exchange(self._api_async)
# Assign options array, as it contains some temporary information from the exchange.
# TODO: investigate with ccxt if it's safe to remove `.options`
# ccxt does not implicitly copy options over in set_markets_from_exchange
self._api.options = self._api_async.options
if self._exchange_ws:
# Set markets to avoid reloading on websocket api
@@ -879,19 +879,20 @@ class Exchange:
# Only allow 5 calls per pair to somewhat limit the impact
raise ConfigurationError(
f"This strategy requires {startup_candles} candles to start, "
"which is more than 5x "
f"which is more than 5x ({candle_limit * 5 - 1} candles) "
f"the amount of candles {self.name} provides for {timeframe}."
)
elif required_candle_call_count > 1:
raise ConfigurationError(
f"This strategy requires {startup_candles} candles to start, which is more than "
f"This strategy requires {startup_candles} candles to start, "
f"which is more than ({candle_limit - 1} candles) "
f"the amount of candles {self.name} provides for {timeframe}."
)
if required_candle_call_count > 1:
logger.warning(
f"Using {required_candle_call_count} calls to get OHLCV. "
f"This can result in slower operations for the bot. Please check "
f"if you really need {startup_candles} candles for your strategy"
f"if you really need {startup_candles} candles for your strategy."
)
return required_candle_call_count

View File

@@ -18,7 +18,7 @@ class BaseClassifierModel(IFreqaiModel):
"""
Base class for regression type models (e.g. Catboost, LightGBM, XGboost etc.).
User *must* inherit from this class and set fit(). See example scripts
such as prediction_models/CatboostClassifier.py for guidance.
such as prediction_models/XGBoostClassifier.py for guidance.
"""
def train(self, unfiltered_df: DataFrame, pair: str, dk: FreqaiDataKitchen, **kwargs) -> Any:

View File

@@ -18,7 +18,7 @@ class BaseRegressionModel(IFreqaiModel):
"""
Base class for regression type models (e.g. Catboost, LightGBM, XGboost etc.).
User *must* inherit from this class and set fit(). See example scripts
such as prediction_models/CatboostRegressor.py for guidance.
such as prediction_models/XGBoostRegressor.py for guidance.
"""
def train(self, unfiltered_df: DataFrame, pair: str, dk: FreqaiDataKitchen, **kwargs) -> Any:

View File

@@ -948,7 +948,7 @@ class IFreqaiModel(ABC):
return dk
# Following methods which are overridden by user made prediction models.
# See freqai/prediction_models/CatboostPredictionModel.py for an example.
# See freqai/prediction_models/XGBoostRegressor.py for an example.
@abstractmethod
def train(self, unfiltered_df: DataFrame, pair: str, dk: FreqaiDataKitchen, **kwargs) -> Any:
@@ -964,7 +964,7 @@ class IFreqaiModel(ABC):
def fit(self, data_dictionary: dict[str, Any], dk: FreqaiDataKitchen, **kwargs) -> Any:
"""
Most regressors use the same function names and arguments e.g. user
can drop in LGBMRegressor in place of CatBoostRegressor and all data
can drop in LGBMRegressor in place of XGBoostRegressor and all data
management will be properly handled by Freqai.
:param data_dictionary: Dict = the dictionary constructed by DataHandler to hold
all the training and test data/labels.

View File

@@ -1,61 +0,0 @@
import logging
from pathlib import Path
from typing import Any
from catboost import CatBoostClassifier, Pool
from freqtrade.freqai.base_models.BaseClassifierModel import BaseClassifierModel
from freqtrade.freqai.data_kitchen import FreqaiDataKitchen
logger = logging.getLogger(__name__)
class CatboostClassifier(BaseClassifierModel):
"""
User created prediction model. The class inherits IFreqaiModel, which
means it has full access to all Frequency AI functionality. Typically,
users would use this to override the common `fit()`, `train()`, or
`predict()` methods to add their custom data handling tools or change
various aspects of the training that cannot be configured via the
top level config.json file.
"""
def fit(self, data_dictionary: dict, dk: FreqaiDataKitchen, **kwargs) -> Any:
"""
User sets up the training and test data to fit their desired model here
:param data_dictionary: the dictionary holding all data for train, test,
labels, weights
:param dk: The datakitchen object for the current coin/model
"""
train_data = Pool(
data=data_dictionary["train_features"],
label=data_dictionary["train_labels"],
weight=data_dictionary["train_weights"],
)
if self.freqai_info.get("data_split_parameters", {}).get("test_size", 0.1) == 0:
test_data = None
else:
test_data = Pool(
data=data_dictionary["test_features"],
label=data_dictionary["test_labels"],
weight=data_dictionary["test_weights"],
)
cbr = CatBoostClassifier(
allow_writing_files=True,
loss_function="MultiClass",
train_dir=Path(dk.data_path),
**self.model_training_parameters,
)
init_model = self.get_init_model(dk.pair)
cbr.fit(
X=train_data,
eval_set=test_data,
init_model=init_model,
)
return cbr

View File

@@ -1,79 +0,0 @@
import logging
from pathlib import Path
from typing import Any
from catboost import CatBoostClassifier, Pool
from freqtrade.freqai.base_models.BaseClassifierModel import BaseClassifierModel
from freqtrade.freqai.base_models.FreqaiMultiOutputClassifier import FreqaiMultiOutputClassifier
from freqtrade.freqai.data_kitchen import FreqaiDataKitchen
logger = logging.getLogger(__name__)
class CatboostClassifierMultiTarget(BaseClassifierModel):
"""
User created prediction model. The class inherits IFreqaiModel, which
means it has full access to all Frequency AI functionality. Typically,
users would use this to override the common `fit()`, `train()`, or
`predict()` methods to add their custom data handling tools or change
various aspects of the training that cannot be configured via the
top level config.json file.
"""
def fit(self, data_dictionary: dict, dk: FreqaiDataKitchen, **kwargs) -> Any:
"""
User sets up the training and test data to fit their desired model here
:param data_dictionary: the dictionary holding all data for train, test,
labels, weights
:param dk: The datakitchen object for the current coin/model
"""
cbc = CatBoostClassifier(
allow_writing_files=True,
loss_function="MultiClass",
train_dir=Path(dk.data_path),
**self.model_training_parameters,
)
X = data_dictionary["train_features"]
y = data_dictionary["train_labels"]
sample_weight = data_dictionary["train_weights"]
eval_sets = [None] * y.shape[1]
if self.freqai_info.get("data_split_parameters", {}).get("test_size", 0.1) != 0:
eval_sets = [None] * data_dictionary["test_labels"].shape[1]
for i in range(data_dictionary["test_labels"].shape[1]):
eval_sets[i] = Pool(
data=data_dictionary["test_features"],
label=data_dictionary["test_labels"].iloc[:, i],
weight=data_dictionary["test_weights"],
)
init_model = self.get_init_model(dk.pair)
if init_model:
init_models = init_model.estimators_
else:
init_models = [None] * y.shape[1]
fit_params = []
for i in range(len(eval_sets)):
fit_params.append(
{
"eval_set": eval_sets[i],
"init_model": init_models[i],
}
)
model = FreqaiMultiOutputClassifier(estimator=cbc)
thread_training = self.freqai_info.get("multitarget_parallel_training", False)
if thread_training:
model.n_jobs = y.shape[1]
model.fit(X=X, y=y, sample_weight=sample_weight, fit_params=fit_params)
return model

View File

@@ -1,60 +0,0 @@
import logging
from pathlib import Path
from typing import Any
from catboost import CatBoostRegressor, Pool
from freqtrade.freqai.base_models.BaseRegressionModel import BaseRegressionModel
from freqtrade.freqai.data_kitchen import FreqaiDataKitchen
logger = logging.getLogger(__name__)
class CatboostRegressor(BaseRegressionModel):
"""
User created prediction model. The class inherits IFreqaiModel, which
means it has full access to all Frequency AI functionality. Typically,
users would use this to override the common `fit()`, `train()`, or
`predict()` methods to add their custom data handling tools or change
various aspects of the training that cannot be configured via the
top level config.json file.
"""
def fit(self, data_dictionary: dict, dk: FreqaiDataKitchen, **kwargs) -> Any:
"""
User sets up the training and test data to fit their desired model here
:param data_dictionary: the dictionary holding all data for train, test,
labels, weights
:param dk: The datakitchen object for the current coin/model
"""
train_data = Pool(
data=data_dictionary["train_features"],
label=data_dictionary["train_labels"],
weight=data_dictionary["train_weights"],
)
if self.freqai_info.get("data_split_parameters", {}).get("test_size", 0.1) == 0:
test_data = None
else:
test_data = Pool(
data=data_dictionary["test_features"],
label=data_dictionary["test_labels"],
weight=data_dictionary["test_weights"],
)
init_model = self.get_init_model(dk.pair)
model = CatBoostRegressor(
allow_writing_files=True,
train_dir=Path(dk.data_path),
**self.model_training_parameters,
)
model.fit(
X=train_data,
eval_set=test_data,
init_model=init_model,
)
return model

View File

@@ -1,78 +0,0 @@
import logging
from pathlib import Path
from typing import Any
from catboost import CatBoostRegressor, Pool
from freqtrade.freqai.base_models.BaseRegressionModel import BaseRegressionModel
from freqtrade.freqai.base_models.FreqaiMultiOutputRegressor import FreqaiMultiOutputRegressor
from freqtrade.freqai.data_kitchen import FreqaiDataKitchen
logger = logging.getLogger(__name__)
class CatboostRegressorMultiTarget(BaseRegressionModel):
"""
User created prediction model. The class inherits IFreqaiModel, which
means it has full access to all Frequency AI functionality. Typically,
users would use this to override the common `fit()`, `train()`, or
`predict()` methods to add their custom data handling tools or change
various aspects of the training that cannot be configured via the
top level config.json file.
"""
def fit(self, data_dictionary: dict, dk: FreqaiDataKitchen, **kwargs) -> Any:
"""
User sets up the training and test data to fit their desired model here
:param data_dictionary: the dictionary holding all data for train, test,
labels, weights
:param dk: The datakitchen object for the current coin/model
"""
cbr = CatBoostRegressor(
allow_writing_files=True,
train_dir=Path(dk.data_path),
**self.model_training_parameters,
)
X = data_dictionary["train_features"]
y = data_dictionary["train_labels"]
sample_weight = data_dictionary["train_weights"]
eval_sets = [None] * y.shape[1]
if self.freqai_info.get("data_split_parameters", {}).get("test_size", 0.1) != 0:
eval_sets = [None] * data_dictionary["test_labels"].shape[1]
for i in range(data_dictionary["test_labels"].shape[1]):
eval_sets[i] = Pool(
data=data_dictionary["test_features"],
label=data_dictionary["test_labels"].iloc[:, i],
weight=data_dictionary["test_weights"],
)
init_model = self.get_init_model(dk.pair)
if init_model:
init_models = init_model.estimators_
else:
init_models = [None] * y.shape[1]
fit_params = []
for i in range(len(eval_sets)):
fit_params.append(
{
"eval_set": eval_sets[i],
"init_model": init_models[i],
}
)
model = FreqaiMultiOutputRegressor(estimator=cbr)
thread_training = self.freqai_info.get("multitarget_parallel_training", False)
if thread_training:
model.n_jobs = y.shape[1]
model.fit(X=X, y=y, sample_weight=sample_weight, fit_params=fit_params)
return model

View File

@@ -97,7 +97,7 @@ def plot_feature_importance(
"""
Plot Best and worst features by importance for a single sub-train.
:param model: Any = A model which was `fit` using a common library
such as catboost or lightgbm
such as XGBoost or lightgbm
:param pair: str = pair e.g. BTC/USD
:param dk: FreqaiDataKitchen = non-persistent data container for current coin/loop
:param count_max: int = the amount of features to be loaded per column
@@ -115,6 +115,8 @@ def plot_feature_importance(
for label in models:
mdl = models[label]
if "catboost.core" in str(mdl.__class__):
# CatBoost is no longer actively supported since 2025.12
# However users can still use it in their custom models
feature_importance = mdl.get_feature_importance()
elif "lightgbm.sklearn" in str(mdl.__class__):
feature_importance = mdl.feature_importances_

View File

@@ -52,29 +52,25 @@ def __run_backtest_bg(btconfig: Config):
lastconfig = ApiBG.bt["last_config"]
strat = StrategyResolver.load_strategy(btconfig)
validate_config_consistency(btconfig)
if (
not ApiBG.bt["bt"]
or lastconfig.get("timeframe") != strat.timeframe
time_settings_changed = (
lastconfig.get("timeframe") != strat.timeframe
or lastconfig.get("timeframe_detail") != btconfig.get("timeframe_detail")
or lastconfig.get("timerange") != btconfig["timerange"]
):
)
if not ApiBG.bt["bt"] or time_settings_changed:
from freqtrade.optimize.backtesting import Backtesting
ApiBG.bt["bt"] = Backtesting(btconfig)
else:
ApiBG.bt["bt"].config = deep_merge_dicts(btconfig, ApiBG.bt["bt"].config)
ApiBG.bt["bt"].init_backtest()
# Only reload data if timeframe changed.
if (
not ApiBG.bt["data"]
or not ApiBG.bt["timerange"]
or lastconfig.get("timeframe") != strat.timeframe
or lastconfig.get("timerange") != btconfig["timerange"]
):
# Only reload data if timerange is open or settings changed
if not ApiBG.bt["data"] or not ApiBG.bt["timerange"] or time_settings_changed:
ApiBG.bt["data"], ApiBG.bt["timerange"] = ApiBG.bt["bt"].load_bt_data()
lastconfig["timerange"] = btconfig["timerange"]
lastconfig["timeframe_detail"] = btconfig.get("timeframe_detail")
lastconfig["timeframe"] = strat.timeframe
lastconfig["enable_protections"] = btconfig.get("enable_protections")
lastconfig["dry_run_wallet"] = btconfig.get("dry_run_wallet")

View File

@@ -20,7 +20,7 @@ class FreqaiExampleHybridStrategy(IStrategy):
Launching this strategy would be:
freqtrade trade --strategy FreqaiExampleHybridStrategy --strategy-path freqtrade/templates
--freqaimodel CatboostClassifier --config config_examples/config_freqai.example.json
--freqaimodel XGBoostClassifier --config config_examples/config_freqai.example.json
or the user simply adds this to their config:

View File

@@ -205,8 +205,8 @@ class FreqaiExampleStrategy(IStrategy):
# If user wishes to use multiple targets, they can add more by
# appending more columns with '&'. User should keep in mind that multi targets
# requires a multioutput prediction model such as
# freqai/prediction_models/CatboostRegressorMultiTarget.py,
# freqtrade trade --freqaimodel CatboostRegressorMultiTarget
# freqai/prediction_models/LightGBMClassifierMultiTarget.py,
# freqtrade trade --freqaimodel LightGBMClassifierMultiTarget
# df["&-s_range"] = (
# df["close"]

View File

@@ -1,7 +1,7 @@
from freqtrade_client.ft_rest_client import FtRestClient
__version__ = "2025.12-dev"
__version__ = "2026.1-dev"
if "dev" in __version__:
from pathlib import Path

View File

@@ -22,6 +22,7 @@ classifiers = [
"Programming Language :: Python :: 3.11",
"Programming Language :: Python :: 3.12",
"Programming Language :: Python :: 3.13",
"Programming Language :: Python :: 3.14",
"Operating System :: MacOS",
"Operating System :: Unix",
"Topic :: Office/Business :: Financial :: Investment",
@@ -85,7 +86,6 @@ hyperopt = [
freqai = [
"scikit-learn",
"joblib",
"catboost; platform_machine != 'arm'",
"lightgbm",
"xgboost",
"tensorboard",

View File

@@ -6,9 +6,9 @@
-r requirements-freqai-rl.txt
-r docs/requirements-docs.txt
ruff==0.14.8
mypy==1.19.0
pre-commit==4.5.0
ruff==0.14.10
mypy==1.19.1
pre-commit==4.5.1
pytest==9.0.2
pytest-asyncio==1.3.0
pytest-cov==7.0.0
@@ -18,15 +18,16 @@ pytest-timeout==2.4.0
pytest-xdist==3.8.0
isort==7.0.0
# For datetime mocking
time-machine==3.1.0
time-machine==3.2.0
# Convert jupyter notebooks to markdown documents
nbconvert==7.16.6
# mypy types
scipy-stubs==1.16.3.2 # keep in sync with `scipy` in `requirements-hyperopt.txt`
scipy-stubs==1.16.3.3 # keep in sync with `scipy` in `requirements-hyperopt.txt`
types-cachetools==6.2.0.20251022
types-filelock==3.2.7
types-requests==2.32.4.20250913
types-tabulate==0.9.0.20241207
types-python-dateutil==2.9.0.20251115
pip-audit==2.10.0

View File

@@ -3,7 +3,7 @@
# Required for freqai-rl
torch==2.9.1; sys_platform != 'darwin' or platform_machine != 'x86_64'
gymnasium==1.2.2
gymnasium==1.2.3
# SB3 >=2.5.0 depends on torch 2.3.0 - which implies it dropped support x86 macos
stable_baselines3==2.7.1; sys_platform != 'darwin' or platform_machine != 'x86_64'
sb3_contrib>=2.2.1; sys_platform != 'darwin' or platform_machine != 'x86_64'

View File

@@ -3,9 +3,8 @@
-r requirements-plot.txt
# Required for freqai
scikit-learn==1.7.2
joblib==1.5.2
catboost==1.2.8; 'arm' not in platform_machine
scikit-learn==1.8.0
joblib==1.5.3
lightgbm==4.6.0
xgboost==3.1.2
tensorboard==2.20.0

View File

@@ -3,7 +3,7 @@
# Required for hyperopt
scipy==1.16.3
scikit-learn==1.7.2
scikit-learn==1.8.0
filelock==3.20.1
optuna==4.6.0
cmaes==0.12.0

View File

@@ -1,4 +1,4 @@
numpy==2.3.5
numpy==2.4.0
pandas==2.3.3
bottleneck==1.6.0
numexpr==2.14.1
@@ -7,23 +7,23 @@ ft-pandas-ta==0.3.16
ta-lib==0.6.8
technical==1.5.3
ccxt==4.5.27
ccxt==4.5.30
cryptography==46.0.3
aiohttp==3.13.2
SQLAlchemy==2.0.44
SQLAlchemy==2.0.45
python-telegram-bot==22.5
# can't be hard-pinned due to telegram-bot pinning httpx with ~
httpx>=0.24.1
humanize==4.14.0
cachetools==6.2.2
humanize==4.15.0
cachetools==6.2.4
requests==2.32.5
urllib3==2.6.0
urllib3==2.6.2
certifi==2025.11.12
jsonschema==4.25.1
tabulate==0.9.0
pycoingecko==3.2.0
jinja2==3.1.6
joblib==1.5.2
joblib==1.5.3
rich==14.2.0
pyarrow==22.0.0; platform_machine != 'armv7l'
@@ -37,9 +37,9 @@ orjson==3.11.5
sdnotify==0.3.2
# API Server
fastapi==0.124.0
fastapi==0.127.0
pydantic==2.12.5
uvicorn==0.38.0
uvicorn==0.40.0
pyjwt==2.10.1
aiofiles==25.1.0
psutil==7.1.3

View File

@@ -39,12 +39,6 @@ def populate_dataframe_with_trades_trades(testdatadir):
return pd.read_feather(testdatadir / "orderflow/populate_dataframe_with_trades_TRADES.feather")
@pytest.fixture
def candles(testdatadir):
# TODO: this fixture isn't really necessary and could be removed
return pd.read_json(testdatadir / "orderflow/candles.json").copy()
@pytest.fixture
def public_trades_list(testdatadir):
return read_csv(testdatadir / "orderflow/public_trades_list.csv").copy()
@@ -293,7 +287,7 @@ def test_public_trades_trades_mock_populate_dataframe_with_trades__check_trades(
assert t["price"] == 234.72
def test_public_trades_put_volume_profile_into_ohlcv_candles(public_trades_list_simple, candles):
def test_public_trades_put_volume_profile_into_ohlcv_candles(public_trades_list_simple):
"""
Tests the integration of volume profile data into OHLCV candles.
@@ -412,13 +406,11 @@ def test_public_trades_config_max_trades(
def test_public_trades_testdata_sanity(
candles,
public_trades_list,
public_trades_list_simple,
populate_dataframe_with_trades_dataframe,
populate_dataframe_with_trades_trades,
):
assert 10999 == len(candles)
assert 1000 == len(public_trades_list)
assert 999 == len(populate_dataframe_with_trades_dataframe)
assert 293532 == len(populate_dataframe_with_trades_trades)

View File

@@ -1012,7 +1012,7 @@ def test_validate_required_startup_candles(default_conf, mocker, caplog):
ex._ft_has["ohlcv_has_history"] = False
with pytest.raises(
OperationalException,
match=r"This strategy requires 2500.*, " r"which is more than the amount.*",
match=r"This strategy requires 2500.*, " r"which is more than .* the amount",
):
ex.validate_required_startup_candles(2500, "5m")

View File

@@ -515,7 +515,6 @@ EXCHANGES = {
],
},
"hyperliquid": {
# TODO: Should be UBTC/USDC - probably needs a fix in ccxt
"pair": "BTC/USDC",
"stake_currency": "USDC",
"hasQuoteVolume": False,

View File

@@ -31,9 +31,6 @@ from tests.freqai.conftest import (
def can_run_model(model: str) -> None:
is_pytorch_model = "Reinforcement" in model or "PyTorch" in model
if is_arm() and "Catboost" in model:
pytest.skip("CatBoost is not supported on ARM.")
if is_pytorch_model and is_mac():
pytest.skip("Reinforcement learning / PyTorch module not available on intel based Mac OS.")
@@ -44,7 +41,6 @@ def can_run_model(model: str) -> None:
("LightGBMRegressor", True, False, True, True, False, 0, 0),
("XGBoostRegressor", False, True, False, True, False, 10, 0.05),
("XGBoostRFRegressor", False, False, False, True, False, 0, 0),
("CatboostRegressor", False, False, False, True, True, 0, 0),
("PyTorchMLPRegressor", False, False, False, False, False, 0, 0),
("PyTorchTransformerRegressor", False, False, False, False, False, 0, 0),
("ReinforcementLearner", False, True, False, True, False, 0, 0),
@@ -138,9 +134,7 @@ def test_extract_data_and_train_model_Standard(
[
("LightGBMRegressorMultiTarget", "freqai_test_multimodel_strat"),
("XGBoostRegressorMultiTarget", "freqai_test_multimodel_strat"),
("CatboostRegressorMultiTarget", "freqai_test_multimodel_strat"),
("LightGBMClassifierMultiTarget", "freqai_test_multimodel_classifier_strat"),
("CatboostClassifierMultiTarget", "freqai_test_multimodel_classifier_strat"),
],
)
@pytest.mark.filterwarnings(r"ignore:.*__sklearn_tags__.*:DeprecationWarning")
@@ -184,7 +178,6 @@ def test_extract_data_and_train_model_MultiTargets(mocker, freqai_conf, model, s
"model",
[
"LightGBMClassifier",
"CatboostClassifier",
"XGBoostClassifier",
"XGBoostRFClassifier",
"SKLearnRandomForestClassifier",
@@ -246,13 +239,11 @@ def test_extract_data_and_train_model_Classifiers(mocker, freqai_conf, model):
[
("LightGBMRegressor", 2, "freqai_test_strat"),
("XGBoostRegressor", 2, "freqai_test_strat"),
("CatboostRegressor", 2, "freqai_test_strat"),
("PyTorchMLPRegressor", 2, "freqai_test_strat"),
("PyTorchTransformerRegressor", 2, "freqai_test_strat"),
("ReinforcementLearner", 3, "freqai_rl_test_strat"),
("XGBoostClassifier", 2, "freqai_test_classifier"),
("LightGBMClassifier", 2, "freqai_test_classifier"),
("CatboostClassifier", 2, "freqai_test_classifier"),
("PyTorchMLPClassifier", 2, "freqai_test_classifier"),
],
)

View File

@@ -64,7 +64,6 @@ def test_hyperopt_real_parameter():
def test_hyperopt_decimal_parameter():
HyperoptStateContainer.set_state(HyperoptState.INDICATORS)
# TODO: Check for get_space??
from freqtrade.optimize.space import SKDecimal
with pytest.raises(OperationalException, match=r"DecimalParameter space must be.*"):

92
tests/test_pip_audit.py Normal file
View File

@@ -0,0 +1,92 @@
"""
Run pip audit to check for known security vulnerabilities in installed packages.
Original Idea and base for this implementation by Michael Kennedy's blog:
https://mkennedy.codes/posts/python-supply-chain-security-made-easy/
"""
import subprocess
import sys
from pathlib import Path
import pytest
def test_pip_audit_no_vulnerabilities():
"""
Run pip-audit to check for known security vulnerabilities.
This test will fail if any vulnerabilities are detected in the installed packages.
Note: CVE-2025-53000 (nbconvert Windows vulnerability) is ignored as it only affects
Windows platforms and is a known acceptable risk for this project.
"""
# Get the project root directory
project_root = Path(__file__).parent.parent
command = [
sys.executable,
"-m",
"pip_audit",
# "--format=json",
"--progress-spinner=off",
"--ignore-vuln",
"CVE-2025-53000",
"--skip-editable",
]
# Run pip-audit with JSON output for easier parsing
try:
result = subprocess.run(
command,
cwd=project_root,
capture_output=True,
text=True,
timeout=120, # 2 minute timeout
)
except subprocess.TimeoutExpired:
pytest.fail("pip-audit command timed out after 120 seconds")
except FileNotFoundError:
pytest.fail("pip-audit not installed or not accessible")
# Check if pip-audit found any vulnerabilities
if result.returncode != 0:
# pip-audit returns non-zero when vulnerabilities are found
error_output = result.stdout + "\n" + result.stderr
# Check if it's an actual vulnerability vs an error
if "vulnerabilities found" in error_output.lower() or '"dependencies"' in result.stdout:
pytest.fail(
f"pip-audit detected security vulnerabilities!\n\n"
f"Output:\n{result.stdout}\n\n"
f"Please review and update vulnerable packages.\n"
f"Run manually with: {' '.join(command)}"
)
else:
# Some other error occurred
pytest.fail(
f"pip-audit failed to run properly:\n\nReturn code: {result.returncode}\n"
f"Output: {error_output}\n"
)
# Success - no vulnerabilities found
assert result.returncode == 0, "pip-audit should return 0 when no vulnerabilities are found"
def test_pip_audit_runs_successfully():
"""
Verify that pip-audit can run successfully (even if vulnerabilities are found).
This is a smoke test to ensure pip-audit is properly installed and functional.
"""
try:
result = subprocess.run(
[sys.executable, "-m", "pip_audit", "--version"],
capture_output=True,
text=True,
timeout=10,
)
assert result.returncode == 0, f"pip-audit --version failed: {result.stderr}"
assert "pip-audit" in result.stdout.lower(), "pip-audit version output unexpected"
except FileNotFoundError:
pytest.fail("pip-audit not installed")
except subprocess.TimeoutExpired:
pytest.fail("pip-audit --version timed out")

File diff suppressed because one or more lines are too long

View File

@@ -82,7 +82,7 @@ def test_dt_humanize() -> None:
assert dt_humanize_delta(dt_now() - timedelta(minutes=50)) == "50 minutes ago"
assert dt_humanize_delta(dt_now() - timedelta(hours=16)) == "16 hours ago"
assert dt_humanize_delta(dt_now() - timedelta(hours=16, minutes=30)) == "16 hours ago"
assert dt_humanize_delta(dt_now() - timedelta(days=16, hours=10, minutes=25)) == "16 days ago"
assert dt_humanize_delta(dt_now() - timedelta(days=16, hours=10, minutes=25)) == "a month ago"
assert dt_humanize_delta(dt_now() - timedelta(minutes=50)) == "50 minutes ago"