Compare commits

..

15 Commits

Author SHA1 Message Date
Matthias 281ba605da Merge pull request #13106 from freqtrade/new_release
New release 2026.4
2026-04-30 19:49:02 +02:00
Matthias d460eef576 chore: bump version to 2026.4 2026-04-30 07:08:05 +02:00
Matthias 6c2cd96622 Merge branch 'stable' into new_release 2026-04-30 07:07:49 +02:00
Matthias dc2abe32a3 Merge pull request #13003 from freqtrade/new_release
New release 2026.3
2026-03-30 19:11:38 +02:00
Matthias 43bb0f7cb1 chore: bump version to 2026.3 2026-03-30 06:38:56 +02:00
Matthias 9ee87cb6ba Merge branch 'stable' into new_release 2026-03-30 06:38:42 +02:00
Matthias 0e2313be7b Merge pull request #12875 from freqtrade/new_release
New release 2026.2
2026-02-28 12:25:06 +01:00
Matthias f535c4cff4 chore: bump version to 2026.2 2026-02-28 08:02:58 +01:00
Matthias 5f901d837c Merge branch 'stable' into new_release 2026-02-28 08:02:41 +01:00
Matthias c86484b152 Merge pull request #12758 from freqtrade/new_release
New release 2026.1
2026-01-31 13:06:44 +01:00
Matthias a33eb51f36 chore: bump version to 2026.1 2026-01-31 08:23:44 +01:00
Matthias 373cd8141c Merge branch 'stable' into new_release 2026-01-31 08:23:11 +01:00
Matthias 9f00a1d0d2 Merge pull request #12673 from freqtrade/new_release
New release 2025.12
2025-12-30 08:19:19 +01:00
Matthias 9a37d7bfbb chore: bump version to 2025.12 2025-12-29 13:19:31 +01:00
Matthias c9c08906e5 Merge branch 'stable' into new_release 2025-12-29 13:17:12 +01:00
35 changed files with 3928 additions and 7367 deletions
+1 -5
View File
@@ -49,11 +49,7 @@ updates:
patterns:
- "scipy"
- "scipy-stubs"
gymnasium:
patterns:
- "gymnasium"
- "stable-baselines3"
- "sb3-contrib"
- package-ecosystem: "github-actions"
directory: "/"
cooldown:
+2 -5
View File
@@ -68,7 +68,7 @@ jobs:
run: |
pytest --random-order --cov=freqtrade --cov=freqtrade_client --cov-config=.coveragerc
- uses: codecov/codecov-action@e79a6962e0d4c0c17b229090214935d2e33f8354 # v6.0.1
- uses: codecov/codecov-action@57e3a136b779b570ffcdbf80b3bdc90e7fab3de2 # v6.0.0
if: (runner.os == 'Linux' && matrix.python-version == '3.12' && matrix.os == 'ubuntu-24.04')
with:
fail_ci_if_error: true
@@ -150,10 +150,7 @@ jobs:
run: |
$PSVersionTable
Get-PSRepository | Format-List *
if (-not (Get-PSRepository -Name PSGallery -ErrorAction SilentlyContinue)) {
Register-PSRepository -Default
}
Set-PSRepository PSGallery -InstallationPolicy Trusted
Set-PSRepository psgallery -InstallationPolicy trusted
Install-Module -Name Pester -RequiredVersion 5.7.1 -Confirm:$false -Force -SkipPublisherCheck
$Error.clear()
Invoke-Pester -Path "tests" -CI
@@ -1,53 +0,0 @@
name: Pre-commit Types update
on:
pull_request:
branches:
- "develop"
concurrency:
group: "${{ github.workflow }}-${{ github.ref }}-${{ github.event_name }}"
cancel-in-progress: true
permissions: {}
jobs:
mypy-version-update:
name: "Pre-commit mypy type versions update"
runs-on: ubuntu-24.04
# Only run this job for pull requests created by dependabot[bot]
if: >
github.event.pull_request.user.login == 'dependabot[bot]' &&
github.repository == github.event.pull_request.head.repo.full_name &&
github.event_name == 'pull_request' &&
startsWith(github.head_ref, 'dependabot/')
environment:
name: dependabot-pulls
steps:
- uses: actions/checkout@de0fac2e4500dabe0009e67214ff5f5447ce83dd # v6.0.2
with:
persist-credentials: true
token: ${{ secrets.REPO_SCOPED_TOKEN_DEP }}
ref: ${{ github.head_ref || github.ref }}
- name: Install uv and Python 🐍
uses: astral-sh/setup-uv@08807647e7069bb48b6ef5acd8ec9567f424441b # v8.1.0
with:
activate-environment: true
python-version: "3.13"
- name: Install PyYAML
run: |
- name: pre-commit dependencies
run: |
uv pip install $(grep -E "^pyyaml==" requirements-dev.txt)
python build_helpers/pre_commit_update.py --update
- uses: stefanzweifel/git-auto-commit-action@04702edda442b2e678b25b537cec683a1493fcb9 # v7
with:
commit_message: "chore(deps): Apply pre-commit types update"
commit_user_name: Freqtrade Bot
commit_user_email: 154552126+freqtrade-bot@users.noreply.github.com
commit_author: Freqtrade Bot <154552126+freqtrade-bot@users.noreply.github.com>
+1 -1
View File
@@ -2,7 +2,7 @@ name: Pre-commit auto-update
on:
schedule:
- cron: "13 1 * * 2"
- cron: "0 3 * * 2"
# on demand
workflow_dispatch:
+6 -6
View File
@@ -15,23 +15,23 @@ repos:
- repo: https://github.com/pre-commit/mirrors-mypy
rev: "v2.1.0"
rev: "v1.20.2"
hooks:
- id: mypy
exclude: build_helpers
additional_dependencies:
- types-cachetools==7.0.0.20260503
- types-cachetools==6.2.0.20260408
- types-filelock==3.2.7
- types-requests==2.33.0.20260508
- types-tabulate==0.10.0.20260508
- types-python-dateutil==2.9.0.20260508
- types-requests==2.33.0.20260408
- types-tabulate==0.10.0.20260408
- types-python-dateutil==2.9.0.20260408
- scipy-stubs==1.17.1.4
- SQLAlchemy==2.0.49
# stages: [push]
- repo: https://github.com/charliermarsh/ruff-pre-commit
# Ruff version.
rev: 'v0.15.13'
rev: 'v0.15.12'
hooks:
- id: ruff
- id: ruff-format
+1 -1
View File
@@ -1,4 +1,4 @@
FROM python:3.14.5-slim-trixie AS base
FROM python:3.14.3-slim-trixie AS base
# Setup env
ENV LANG=C.UTF-8
+2 -42
View File
@@ -1,7 +1,5 @@
# File used in CI to ensure pre-commit dependencies are kept up-to-date.
import argparse
import re
import sys
from pathlib import Path
@@ -12,24 +10,6 @@ pre_commit_file = Path(".pre-commit-config.yaml")
require_dev = Path("requirements-dev.txt")
require = Path("requirements.txt")
parser = argparse.ArgumentParser()
parser.add_argument("--update", action="store_true")
args = parser.parse_args()
def replace_dependency_version(pre_commit_text: str, dependency: str) -> tuple[str, bool]:
"""
Regex-based replacement of a dependency version in the pre-commit config file.
using regex here ensures we only replace the version of the dependency while
keeping the overall file intact.
"""
package_name = dependency.split("==", 1)[0]
pattern = re.compile(rf"^(\s*-\s+){re.escape(package_name)}==.*$", re.MULTILINE)
updated_text, replacements = pattern.subn(rf"\1{dependency}", pre_commit_text, count=1)
return updated_text, replacements > 0 and updated_text != pre_commit_text
with require_dev.open("r") as rfile:
requirements = rfile.readlines()
@@ -43,18 +23,6 @@ supported = ("types-", "SQLAlchemy", "scipy-stubs")
# Only keep the first part of the line up to the first space
type_reqs = [r.strip("\n").split()[0] for r in requirements if r.startswith(supported)]
with pre_commit_file.open("r") as file:
pre_commit_text = file.read()
updated = False
for req in type_reqs:
pre_commit_text, req_updated = replace_dependency_version(pre_commit_text, req)
updated = updated or req_updated
if args.update and updated:
with pre_commit_file.open("w") as file:
file.write(pre_commit_text)
with pre_commit_file.open("r") as file:
f = yaml.load(file, Loader=yaml.SafeLoader)
@@ -72,20 +40,12 @@ for hook in hooks:
for req in type_reqs:
if req not in hooks:
errors.append(f"{req} is missing in pre-commit config file.")
if updated:
if args.update:
errors.append(".pre-commit-config.yaml was updated to match the requirements files.")
else:
errors.append(
".pre-commit-config.yaml is outdated. Run build_helpers/pre_commit_update.py --update."
)
errors.append(f"{req} is missing in pre-config file.")
if errors:
for e in errors:
print(e)
sys.exit(1 if not (args.update and updated) else 0)
sys.exit(1)
sys.exit(0)
+1 -2
View File
@@ -1063,8 +1063,7 @@
"jwt_secret_key": {
"description": "Secret key for JWT authentication.",
"type": "string",
"default": "somethingRandomSomethingRandom123",
"minLength": 32
"default": "somethingRandomSomethingRandom123"
},
"CORS_origins": {
"description": "List of allowed CORS origins.",
+1 -1
View File
@@ -166,7 +166,7 @@ Below are the values you can expect to include/use inside a typical strategy dat
| `df['do_predict']` | Indication of an outlier data point. The return value is integer between -2 and 2, which lets you know if the prediction is trustworthy or not. `do_predict==1` means that the prediction is trustworthy. If the Dissimilarity Index (DI, see details [here](freqai-feature-engineering.md#identifying-outliers-with-the-dissimilarity-index-di)) of the input data point is above the threshold defined in the config, FreqAI will subtract 1 from `do_predict`, resulting in `do_predict==0`. If `use_SVM_to_remove_outliers` is active, the Support Vector Machine (SVM, see details [here](freqai-feature-engineering.md#identifying-outliers-using-a-support-vector-machine-svm)) may also detect outliers in training and prediction data. In this case, the SVM will also subtract 1 from `do_predict`. If the input data point was considered an outlier by the SVM but not by the DI, or vice versa, the result will be `do_predict==0`. If both the DI and the SVM considers the input data point to be an outlier, the result will be `do_predict==-1`. As with the SVM, if `use_DBSCAN_to_remove_outliers` is active, DBSCAN (see details [here](freqai-feature-engineering.md#identifying-outliers-with-dbscan)) may also detect outliers and subtract 1 from `do_predict`. Hence, if both the SVM and DBSCAN are active and identify a datapoint that was above the DI threshold as an outlier, the result will be `do_predict==-2`. A particular case is when `do_predict == 2`, which means that the model has expired due to exceeding `expired_hours`. <br> **Datatype:** Integer between -2 and 2.
| `df['DI_values']` | Dissimilarity Index (DI) values are proxies for the level of confidence FreqAI has in the prediction. A lower DI means the prediction is close to the training data, i.e., higher prediction confidence. See details about the DI [here](freqai-feature-engineering.md#identifying-outliers-with-the-dissimilarity-index-di). <br> **Datatype:** Float.
| `df['%*']` | Any dataframe column prepended with `%` in `feature_engineering_*()` is treated as a training feature. For example, you can include the RSI in the training feature set (similar to in `templates/FreqaiExampleStrategy.py`) by setting `df['%-rsi']`. See more details on how this is done [here](freqai-feature-engineering.md). <br> **Note:** Since the number of features prepended with `%` can multiply very quickly (10s of thousands of features are easily engineered using the multiplictative functionality of, e.g., `include_shifted_candles` and `include_timeframes` as described in the [parameter table](freqai-parameter-table.md)), these features are removed from the dataframe that is returned from FreqAI to the strategy. To keep a particular type of feature for plotting purposes, you would prepend it with `%%` (see details below). <br> **Datatype:** Depends on the feature created by the user.
| `df['%%*']` | Any dataframe column prepended with `%%` in `feature_engineering_*()` is treated as a training feature, just the same as the above `%` prepend. However, in this case, the features are returned back to the strategy for FreqUI/plot-dataframe plotting and monitoring in Dry/Live/Backtesting <br> **Datatype:** Depends on the feature created by the user. <br>*Please note* that features created in `feature_engineering_expand()` will have automatic FreqAI naming schemas depending on the expansions that you configured (i.e. `include_timeframes`, `include_corr_pairlist`, `indicators_periods_candles`, `include_shifted_candles`). So if you want to plot `%%-rsi` from `feature_engineering_expand_all()`, the final naming scheme for your plotting config would be: `%%-rsi-period_10_ETH/USDT:USDT_1h` for the `rsi` feature with `period=10`, `timeframe=1h`, and `pair=ETH/USDT:USDT` (the `:USDT` is added if you are using futures pairs). It is useful to simply add `print(dataframe.columns)` in your `populate_indicators()` after `self.freqai.start()` to see the full list of available features that are returned to the strategy for plotting purposes.
| `df['%%*']` | Any dataframe column prepended with `%%` in `feature_engineering_*()` is treated as a training feature, just the same as the above `%` prepend. However, in this case, the features are returned back to the strategy for FreqUI/plot-dataframe plotting and monitoring in Dry/Live/Backtesting <br> **Datatype:** Depends on the feature created by the user. Please note that features created in `feature_engineering_expand()` will have automatic FreqAI naming schemas depending on the expansions that you configured (i.e. `include_timeframes`, `include_corr_pairlist`, `indicators_periods_candles`, `include_shifted_candles`). So if you want to plot `%%-rsi` from `feature_engineering_expand_all()`, the final naming scheme for your plotting config would be: `%%-rsi-period_10_ETH/USDT:USDT_1h` for the `rsi` feature with `period=10`, `timeframe=1h`, and `pair=ETH/USDT:USDT` (the `:USDT` is added if you are using futures pairs). It is useful to simply add `print(dataframe.columns)` in your `populate_indicators()` after `self.freqai.start()` to see the full list of available features that are returned to the strategy for plotting purposes.
## Setting the `startup_candle_count`
+1 -1
View File
@@ -2,6 +2,6 @@ markdown==3.10.2
mkdocs==1.6.1
mkdocs-material==9.7.6
mdx_truly_sane_lists==1.3
pymdown-extensions==10.21.3
pymdown-extensions==10.21.2
jinja2==3.1.6
mike==2.2.0
+9 -9
View File
@@ -202,20 +202,20 @@ All endpoints in the below table need to be prefixed with the base URL of the AP
| `/blacklist` | GET | Show the current blacklist.
| `/blacklist` | POST | Adds the specified pair to the blacklist.<br/>*Params:*<br/>- `blacklist` (`str`)
| `/blacklist` | DELETE | Deletes the specified list of pairs from the blacklist.<br/>*Params:*<br/>- `[pair,pair]` (`list[str]`)
| `/pair_candles` | GET | Returns dataframe for a pair / timeframe combination while the bot is running.
| `/pair_candles` | POST | Returns dataframe for a pair / timeframe combination while the bot is running, filtered by a provided list of columns to return.<br/>*Params:*<br/>- `<column_list>` (`list[str]`)
| `/pair_history` | GET | Returns an analyzed dataframe for a given timerange, analyzed by a given strategy.
| `/pair_history` | POST | Returns an analyzed dataframe for a given timerange, analyzed by a given strategy, filtered by a provided list of columns to return.<br/>*Params:*<br/>- `<column_list>` (`list[str]`)
| `/plot_config` | GET | Get plot config from the strategy (or nothing if not configured).
| `/strategies` | GET | List strategies in strategy directory.
| `/strategy/<strategy>` | GET | Get specific Strategy content by strategy class name.<br/>*Params:*<br/>- `<strategy>` (`str`)
| `/available_pairs` | GET | List available backtest data.
| `/pair_candles` | GET | Returns dataframe for a pair / timeframe combination while the bot is running. **Alpha**
| `/pair_candles` | POST | Returns dataframe for a pair / timeframe combination while the bot is running, filtered by a provided list of columns to return. **Alpha**<br/>*Params:*<br/>- `<column_list>` (`list[str]`)
| `/pair_history` | GET | Returns an analyzed dataframe for a given timerange, analyzed by a given strategy. **Alpha**
| `/pair_history` | POST | Returns an analyzed dataframe for a given timerange, analyzed by a given strategy, filtered by a provided list of columns to return. **Alpha**<br/>*Params:*<br/>- `<column_list>` (`list[str]`)
| `/plot_config` | GET | Get plot config from the strategy (or nothing if not configured). **Alpha**
| `/strategies` | GET | List strategies in strategy directory. **Alpha**
| `/strategy/<strategy>` | GET | Get specific Strategy content by strategy class name. **Alpha**<br/>*Params:*<br/>- `<strategy>` (`str`)
| `/available_pairs` | GET | List available backtest data. **Alpha**
| `/version` | GET | Show version.
| `/sysinfo` | GET | Show information about the system load.
| `/health` | GET | Show bot health (last bot loop).
!!! Warning "Alpha status"
Endpoints labeled with *Alpha status* or *Beta status* above may change at any time without notice.
Endpoints labeled with *Alpha status* above may change at any time without notice.
### Message WebSocket
-2
View File
@@ -910,8 +910,6 @@ if self.dp.runmode.value in ('live', 'dry_run'):
### *check_delisting(pair)*
Return Datetime of the pair delisting schedule if any, otherwise return None
```python
def custom_exit(self, pair: str, trade: Trade, current_time: datetime, current_rate: float, current_profit: float, **kwargs):
if self.dp.runmode.value in ('live', 'dry_run'):
+1 -1
View File
@@ -1,6 +1,6 @@
"""Freqtrade bot"""
__version__ = "2026.5-dev"
__version__ = "2026.4"
if "dev" in __version__:
from pathlib import Path
+1 -1
View File
@@ -393,7 +393,7 @@ def start_show_trades(args: dict[str, Any]) -> None:
tfilter = []
if config.get("trade_ids"):
tfilter.append(Trade.id.in_(int(tid) for tid in config["trade_ids"]))
tfilter.append(Trade.id.in_(config["trade_ids"]))
trades = Trade.get_trades(tfilter).all()
logger.info(f"Printing {len(trades)} Trades: ")
-1
View File
@@ -757,7 +757,6 @@ CONF_SCHEMA = {
"description": "Secret key for JWT authentication.",
"type": "string",
"default": "somethingRandomSomethingRandom123",
"minLength": 32,
},
"CORS_origins": {
"description": "List of allowed CORS origins.",
+1 -1
View File
@@ -559,7 +559,7 @@ class Binance(Exchange):
class Binanceusdm(Binance):
"""Binance USDM Exchange
"""Binacne USDM Exchange
Same as Binance - only futures trading is supported (via ccxt).
Not actually necessary, binance should be preferred.
File diff suppressed because it is too large Load Diff
-6
View File
@@ -214,12 +214,6 @@ def dataframe_to_json(dataframe: pd.DataFrame) -> str:
:param dataframe: A pandas DataFrame
:returns: A JSON string of the pandas DataFrame
"""
date_columns = dataframe.select_dtypes(include=["datetime", "datetime64", "datetimetz"])
# Explicit conversion to ms
# This used to be part of to_json, but was deprecated in pandas 3
for date_column in date_columns:
dataframe[date_column] = date_columns[date_column].dt.as_unit("ms").astype("int64")
return dataframe.to_json(orient="split")
+11 -14
View File
@@ -189,8 +189,8 @@ class Order(ModelBase):
def __repr__(self):
return (
f"Order(id={self.id}, trade={self.ft_trade_id}, order_id={self.order_id}, "
f"side={self.side or self.ft_order_side}, filled={self.safe_filled}, "
f"price={self.safe_price}, amount={self.amount}, "
f"side={self.side}, filled={self.safe_filled}, price={self.safe_price}, "
f"amount={self.amount}, "
f"status={self.status}, date={self.order_date_utc:{DATETIME_PRINT_FORMAT}})"
)
@@ -1248,16 +1248,12 @@ class LocalTrade:
close_profit_abs = 0.0
# Reset funding fees
self.funding_fees = 0.0
# Total funding fees - cumulated over all orders
total_funding_fees = 0.0
# current funding fees - resetting on every exit to be aligned with profit calculation,
# as funding fees are part of the profit
current_funding_fee = 0.0
funding_fees = 0.0
ordercount = len(self.orders) - 1
for i, o in enumerate(self.orders):
if o.ft_is_open or not o.filled:
continue
current_funding_fee += o.funding_fee or 0.0
total_funding_fees += o.funding_fee or 0.0
funding_fees += o.funding_fee or 0.0
tmp_amount = FtPrecise(o.safe_amount_after_fee)
tmp_price = FtPrecise(o.safe_price)
@@ -1272,8 +1268,11 @@ class LocalTrade:
avg_price = current_stake / current_amount
if is_exit:
# Intermediate funding fees for profit calculation
self.funding_fees = current_funding_fee
# Process exits
if i == ordercount and is_closing:
# Apply funding fees only to the last closing order
self.funding_fees = funding_fees
exit_rate = o.safe_price
exit_amount = o.safe_amount_after_fee
prof = self.calculate_profit(exit_rate, exit_amount, float(avg_price))
@@ -1282,12 +1281,10 @@ class LocalTrade:
# This needs to be calculated based on the last occurring exit to be aligned
# with realized_profit.
close_profit = (close_profit_abs / total_stake) * self.leverage
current_funding_fee = 0.0
else:
total_stake += self._calc_open_trade_value(tmp_amount, price)
max_stake_amount += tmp_amount * price
# Assign cumulated funding fees after all orders have been processed
self.funding_fees = total_funding_fees
self.funding_fees = funding_fees
self.max_stake_amount = float(max_stake_amount) / (self.leverage or 1.0)
if close_profit:
@@ -38,7 +38,7 @@ class MarketCapPairList(IPairList):
self._max_rank = self._pairlistconfig.get("max_rank", 30)
self._refresh_period = self._pairlistconfig.get("refresh_period", 86400)
self._categories = self._pairlistconfig.get("categories", [])
self._marketcap_cache: FtTTLCache = FtTTLCache(maxsize=2, ttl=self._refresh_period)
self._marketcap_cache: FtTTLCache = FtTTLCache(maxsize=1, ttl=self._refresh_period)
_coingecko_config = self._config.get("coingecko", {})
+1 -1
View File
@@ -51,7 +51,7 @@ class StrategyResolver(IResolver):
strategy: IStrategy = StrategyResolver._load_strategy(
strategy_name, config=config, extra_dir=config.get("strategy_path")
)
strategy.ft_set_special_params_from_file()
strategy.ft_load_params_from_file()
# Set attributes
# Check if we need to override configuration
# (Attribute name, default, subkey)
+1
View File
@@ -15,6 +15,7 @@ from freqtrade.rpc.api_server.deps import get_api_config
logger = logging.getLogger(__name__)
ALGORITHM = "HS256"
__DEFAULT_JWT = "somethingRandomSomethingRandom123"
router_login = APIRouter()
+4 -6
View File
@@ -796,7 +796,7 @@ class RPC:
results = results.rename({"timestamp": "date"}, axis=1)
results.loc[:, "__date_ts"] = results.loc[:, "date"].dt.as_unit("ms").astype("int64")
# Exclude non-bot managed for now
results_filtered = results.loc[results["bot_managed"].astype(bool)]
results_filtered = results.loc[results["bot_managed"]]
results_final = (
results_filtered.groupby(["date", "__date_ts"])
@@ -1548,7 +1548,8 @@ class RPC:
# band-aid until this is fixed:
# https://github.com/pandas-dev/pandas/issues/45836
date_columns = dataframe.select_dtypes(include=["datetime", "datetime64", "datetimetz"])
datetime_types = ["datetime", "datetime64", "datetimetz"]
date_columns = dataframe.select_dtypes(include=datetime_types)
for date_column in date_columns:
# replace NaT with `None`
dataframe[date_column] = dataframe[date_column].astype(object).replace({NaT: None})
@@ -1694,11 +1695,8 @@ class RPC:
else dt_ts(dt_now() - timedelta(days=30)),
is_new_pair=True, # history is never available - so always treat as new pair
candle_type=config.get("candle_type_def", CandleType.SPOT),
until_ms=timerange_parsed.stopts * 1000 if timerange_parsed.stopts else None,
until_ms=timerange_parsed.stopts,
)
if timerange_parsed.stopts and len(data) > 1:
# trim last candle if it is newer than the stop time
data = data.loc[data["date"] <= timerange_parsed.stopdt]
else:
_data = load_data(
datadir=config["datadir"],
+4 -8
View File
@@ -51,10 +51,9 @@ class HyperStrategyMixin:
for par in self._ft_hyper_params[space].values():
yield par.name, par
def ft_set_special_params_from_file(self) -> None:
def ft_load_params_from_file(self) -> None:
"""
Sets special parameters (stoploss, roi, trailing, max_open_trades) from the
previously loaded file.
Load Parameters from parameter file
Should/must run before config values are loaded in strategy_resolver.
"""
if self._ft_params_from_file:
@@ -97,7 +96,7 @@ class HyperStrategyMixin:
params_values = deep_merge_dicts(
self._ft_params_from_file.get(space, {}), getattr(self, f"{space}_params", {})
)
self._ft_set_param(self._ft_hyper_params[space], params_values, space, hyperopt)
self._ft_load_params(self._ft_hyper_params[space], params_values, space, hyperopt)
def load_params_from_file(self) -> dict:
filename_str = getattr(self, "__file__", "")
@@ -119,15 +118,12 @@ class HyperStrategyMixin:
return {}
def _ft_set_param(
def _ft_load_params(
self, params: SpaceParams, param_values: dict, space: str, hyperopt: bool = False
) -> None:
"""
Set optimizable parameter values.
:param params: Dictionary with new parameter values.
:param param_values: Dictionary with values to set.
:param space: The space to which the parameters belong.
:param hyperopt: Flag indicating if we are in hyperopt mode.
"""
if not param_values:
logger.info(f"No params for {space} found, using default values.")
+1 -1
View File
@@ -1,7 +1,7 @@
from freqtrade_client.ft_rest_client import FtRestClient
__version__ = "2026.5-dev"
__version__ = "2026.4"
if "dev" in __version__:
from pathlib import Path
-1
View File
@@ -222,7 +222,6 @@ exclude-newer = "1 week"
[tool.uv.exclude-newer-package]
ccxt = false
pymdown-extensions = "6 days"
[tool.ruff]
line-length = 100
+8 -8
View File
@@ -6,9 +6,9 @@
-r requirements-freqai-rl.txt
-r docs/requirements-docs.txt
ruff==0.15.13
mypy==2.1.0
pre-commit==4.6.0
ruff==0.15.11
mypy==1.20.1
pre-commit==4.5.1
pytest==9.0.3
pytest-asyncio==1.3.0
pytest-cov==7.1.0
@@ -24,13 +24,13 @@ nbconvert==7.17.1
# mypy types
scipy-stubs==1.17.1.4 # keep in sync with `scipy` in `requirements-hyperopt.txt`
types-cachetools==7.0.0.20260503
types-cachetools==6.2.0.20260408
types-filelock==3.2.7
types-requests==2.33.0.20260508
types-tabulate==0.10.0.20260508
types-python-dateutil==2.9.0.20260508
types-requests==2.33.0.20260408
types-tabulate==0.10.0.20260408
types-python-dateutil==2.9.0.20260408
pip-audit==2.10.0
# For build step in CI
build==1.5.0
build==1.4.3
# For pre-commit-update check
pyyaml==6.0.3
+2 -2
View File
@@ -5,7 +5,7 @@
torch==2.11.0; sys_platform != 'darwin' or platform_machine != 'x86_64'
gymnasium==1.2.3
# SB3 >=2.5.0 depends on torch 2.3.0 - which implies it dropped support x86 macos
stable-baselines3==2.8.0; sys_platform != 'darwin' or platform_machine != 'x86_64'
sb3-contrib==2.8.0; sys_platform != 'darwin' or platform_machine != 'x86_64'
stable_baselines3==2.8.0; sys_platform != 'darwin' or platform_machine != 'x86_64'
sb3_contrib==2.8.0; sys_platform != 'darwin' or platform_machine != 'x86_64'
# Progress bar for stable-baselines3 and sb3-contrib
tqdm==4.67.3
+13 -13
View File
@@ -1,4 +1,4 @@
numpy==2.4.5
numpy==2.4.4
pandas==3.0.2
bottleneck==1.6.0
numexpr==2.14.1
@@ -7,39 +7,39 @@ ft-pandas-ta==0.3.16
ta-lib==0.6.8
technical==1.6.0
ccxt==4.5.54
cryptography==48.0.0
ccxt==4.5.50
cryptography==46.0.7
aiohttp==3.13.5
SQLAlchemy==2.0.49
python-telegram-bot==22.7
# can't be hard-pinned due to telegram-bot pinning httpx with ~
httpx>=0.24.1
humanize==4.15.0
cachetools==7.1.2
cachetools==7.0.5
requests==2.33.1
urllib3==2.7.0
certifi==2026.4.22
urllib3==2.6.3
certifi==2026.2.25
jsonschema==4.26.0
tabulate==0.10.0
pycoingecko==3.2.0
jinja2==3.1.6
joblib==1.5.3
rich==15.0.0
pyarrow==24.0.0; platform_machine != 'armv7l'
pyarrow==23.0.1; platform_machine != 'armv7l'
# Load ticker files 30% faster
python-rapidjson==1.23
# Properly format api responses
orjson==3.11.9
orjson==3.11.8
# Notify systemd
sdnotify==0.3.2
# API Server
fastapi==0.136.1
pydantic==2.13.4
uvicorn==0.46.0
fastapi==0.136.0
pydantic==2.13.2
uvicorn==0.44.0
pyjwt==2.12.1
aiofiles==25.1.0
psutil==7.2.2
@@ -49,7 +49,7 @@ questionary==2.1.1
prompt-toolkit==3.0.52
# Extensions to datetime library
python-dateutil==2.9.0.post0
pytz==2026.2
pytz==2026.1.post1
#Futures
schedule==1.2.2
@@ -59,4 +59,4 @@ websockets==16.0
janus==2.0.0
ast-comments==1.3.0
packaging==26.2
packaging==26.1
+1 -1
View File
@@ -190,7 +190,7 @@ class ClientProtocol:
self.logger.info("Empty DataFrame")
async def _handle_default(self, name, msgtype, data):
self.logger.info(f"Unknown message of type {msgtype} received...")
self.logger.info("Unknown message of type {msgtype} received...")
self.logger.info(data)
+2 -4
View File
@@ -163,11 +163,9 @@ class TestCCXTExchange:
pytest.skip(f"No sample Balances available for exchange {exchangename}")
def test_ccxt_fetch_tickers(self, exchange: EXCHANGE_FIXTURE_TYPE):
exch, exchangename, exchange_params = exchange
exch, _, exchange_params = exchange
pair = exchange_params["pair"]
if exchangename == "okx":
# TODO: re-enable test once ccxt has this fixed.
pytest.skip("OKX fetch_tickers is currently broken, skipping test")
tickers = exch.get_tickers()
assert pair in tickers
assert "ask" in tickers[pair]
-38
View File
@@ -1471,44 +1471,6 @@ def test_api_historic_balance(botclient, mocker, ticker, fee, markets, is_short)
assert "total_quote" in resp1["columns"]
def test_api_historic_balance_int_bot_managed(botclient, mocker):
"""
read_sql may return the wallet_history `bot_managed` column as an
integer (e.g. MySQL/MariaDB TINYINT)
"""
_, client = botclient
# Single row: with an int64 `bot_managed`
one_row = pd.DataFrame(
{
"timestamp": pd.to_datetime(["2024-01-01"]),
"total_quote": [100.0],
"bot_managed": [1],
}
).astype({"bot_managed": "int64"})
mocker.patch("freqtrade.rpc.rpc.read_sql", return_value=one_row)
rc = client_get(client, f"{BASE_URI}/historic_balance")
assert_response(rc, 200)
assert rc.json()["length"] == 1
assert rc.json()["data"][0][0] == "2024-01-01T00:00:00"
assert rc.json()["data"][0][2] == 100.0
# Mixed rows: the non-bot-managed row (bot_managed=0) must be excluded.
two_rows = pd.DataFrame(
{
"timestamp": pd.to_datetime(["2024-01-01", "2024-01-02"]),
"total_quote": [100.0, 200.0],
"bot_managed": [0, 1],
}
).astype({"bot_managed": "int64"})
mocker.patch("freqtrade.rpc.rpc.read_sql", return_value=two_rows)
rc = client_get(client, f"{BASE_URI}/historic_balance")
assert_response(rc, 200)
assert rc.json()["length"] == 1
assert rc.json()["data"][0][0] == "2024-01-02T00:00:00"
assert rc.json()["data"][0][2] == 200.0
def test_api_performance(botclient, fee):
ftbot, client = botclient
patch_get_signal(ftbot)
+1 -1
View File
@@ -531,7 +531,7 @@ def test_strategy_interface_versioning(dataframe_1m, default_conf):
assert "exit_long" in exitdf
def test_strategy_ft_set_special_params_from_file(mocker, default_conf):
def test_strategy_ft_load_params_from_file(mocker, default_conf):
default_conf.update({"strategy": "StrategyTestV2"})
del default_conf["max_open_trades"]
mocker.patch(
+2 -2
View File
@@ -230,7 +230,7 @@ def test_deep_merge_dicts():
def test_dataframe_json(ohlcv_history):
from pandas.testing import assert_frame_equal
json = dataframe_to_json(ohlcv_history.copy())
json = dataframe_to_json(ohlcv_history)
dataframe = json_to_dataframe(json)
assert list(ohlcv_history.columns) == list(dataframe.columns)
@@ -238,6 +238,6 @@ def test_dataframe_json(ohlcv_history):
assert_frame_equal(ohlcv_history, dataframe)
ohlcv_history.at[1, "date"] = pd.NaT
json = dataframe_to_json(ohlcv_history.copy())
json = dataframe_to_json(ohlcv_history)
dataframe = json_to_dataframe(json)