Compare commits
15 Commits
| Author | SHA1 | Date | |
|---|---|---|---|
| 281ba605da | |||
| d460eef576 | |||
| 6c2cd96622 | |||
| dc2abe32a3 | |||
| 43bb0f7cb1 | |||
| 9ee87cb6ba | |||
| 0e2313be7b | |||
| f535c4cff4 | |||
| 5f901d837c | |||
| c86484b152 | |||
| a33eb51f36 | |||
| 373cd8141c | |||
| 9f00a1d0d2 | |||
| 9a37d7bfbb | |||
| c9c08906e5 |
@@ -49,11 +49,7 @@ updates:
|
||||
patterns:
|
||||
- "scipy"
|
||||
- "scipy-stubs"
|
||||
gymnasium:
|
||||
patterns:
|
||||
- "gymnasium"
|
||||
- "stable-baselines3"
|
||||
- "sb3-contrib"
|
||||
|
||||
- package-ecosystem: "github-actions"
|
||||
directory: "/"
|
||||
cooldown:
|
||||
|
||||
@@ -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>
|
||||
@@ -2,7 +2,7 @@ name: Pre-commit auto-update
|
||||
|
||||
on:
|
||||
schedule:
|
||||
- cron: "13 1 * * 2"
|
||||
- cron: "0 3 * * 2"
|
||||
# on demand
|
||||
workflow_dispatch:
|
||||
|
||||
|
||||
@@ -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
@@ -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
|
||||
|
||||
@@ -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)
|
||||
|
||||
BIN
Binary file not shown.
@@ -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.",
|
||||
|
||||
@@ -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`
|
||||
|
||||
|
||||
@@ -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
@@ -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
|
||||
|
||||
|
||||
@@ -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,6 +1,6 @@
|
||||
"""Freqtrade bot"""
|
||||
|
||||
__version__ = "2026.5-dev"
|
||||
__version__ = "2026.4"
|
||||
|
||||
if "dev" in __version__:
|
||||
from pathlib import Path
|
||||
|
||||
@@ -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: ")
|
||||
|
||||
@@ -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.",
|
||||
|
||||
@@ -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
@@ -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")
|
||||
|
||||
|
||||
|
||||
@@ -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", {})
|
||||
|
||||
|
||||
@@ -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)
|
||||
|
||||
@@ -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()
|
||||
|
||||
|
||||
@@ -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"],
|
||||
|
||||
@@ -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,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
|
||||
|
||||
@@ -222,7 +222,6 @@ exclude-newer = "1 week"
|
||||
|
||||
[tool.uv.exclude-newer-package]
|
||||
ccxt = false
|
||||
pymdown-extensions = "6 days"
|
||||
|
||||
[tool.ruff]
|
||||
line-length = 100
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -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
@@ -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
|
||||
|
||||
@@ -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)
|
||||
|
||||
|
||||
|
||||
@@ -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]
|
||||
|
||||
@@ -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)
|
||||
|
||||
@@ -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
@@ -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)
|
||||
|
||||
Reference in New Issue
Block a user