Compare commits

...

40 Commits

Author SHA1 Message Date
Matthias
bfe9aac16b fix(hyperliquid): exclude HIP3 pairs for now
part of #12558
2025-11-28 18:16:26 +01:00
Matthias
1770a68457 chore: fix wrongly worded exchange_response endpoint naming 2025-11-28 13:37:31 +01:00
Matthias
57fd455adf fix: Proper fix for plotscript
this time also saving the file ...

closes #12557
2025-11-28 13:29:50 +01:00
Matthias
61ecaa4c41 fix: ensure automatic exportfilename detection works
closes #12557
2025-11-28 13:13:57 +01:00
Matthias
682c4137b4 docs: improved wording 2025-11-27 20:16:38 +01:00
Matthias
759c18df3d docs: improve pre-commit docs wording 2025-11-27 07:04:00 +01:00
Matthias
04fda255de Merge pull request #12529 from mrpabloyeah/fix-backtesting-exception-when-no-data-is-available-for-a-pair
Fix backtesting exception when no data is available for a pair
2025-11-27 06:29:24 +01:00
Matthias
26f23c10b5 Merge pull request #12555 from freqtrade/update/binance-leverage-tiers
Update Binance Leverage Tiers
2025-11-27 06:13:17 +01:00
Freqtrade Bot
001baa07b1 chore: update pre-commit hooks 2025-11-27 03:21:53 +00:00
mrpabloyeah
72724037af Replaced unsafe loop with a list comprehension & added docstring in refresh_pairlist() 2025-11-26 11:03:18 +01:00
Matthias
1bd60c2afb Merge pull request #12554 from freqtrade/restructure_ci
restructure CI to reliably fail the build step if other ch…
2025-11-25 20:11:49 +01:00
Matthias
c0a1911f22 chore(ci): improve zizmor action formatting 2025-11-25 19:47:51 +01:00
Matthias
f4920f199c chore(ci): only notify after build. 2025-11-25 19:47:15 +01:00
Matthias
6b8968ed0f chore(ci): restructure CI to reliably fail the build step if other checks failed 2025-11-25 19:47:15 +01:00
Matthias
85a88d3594 Merge pull request #12552 from freqtrade/codecov
Switch from Coveralls to Codecov
2025-11-25 19:18:53 +01:00
Matthias
b5e17735a6 chore: use codecov with token 2025-11-25 19:03:48 +01:00
Matthias
67beeb6f26 Merge pull request #12553 from freqtrade/update/pre-commit-hooks
Update pre-commit hooks
2025-11-25 06:34:42 +01:00
Freqtrade Bot
f94acc9173 chore: update pre-commit hooks 2025-11-25 03:23:56 +00:00
Matthias
a7ac957399 chore: implement cleanup for codecov action bug 2025-11-24 20:19:20 +01:00
Matthias
b337b075ab chore: limit to appropriate runner 2025-11-24 19:53:55 +01:00
Matthias
3152e97b2f chore: switch from coveralls to codecov 2025-11-24 19:49:44 +01:00
mrpabloyeah
77e8a53572 Fix backtesting exception when no data is available for a pair (new approach) 2025-11-24 10:43:00 +01:00
Matthias
4eea0cca08 Merge pull request #12546 from freqtrade/dependabot/pip/develop/time-machine-3.0.0
chore(deps-dev): bump time-machine from 2.19.0 to 3.0.0
2025-11-24 08:11:34 +01:00
Matthias
8274a6c3a9 chore: update usage of TTLCache to FtTTLCache 2025-11-24 07:07:13 +01:00
Matthias
f0a5b95ec0 feat: add FtTTLCache to avoid mocking issues
overrides timer in a central location.
2025-11-24 07:05:49 +01:00
Matthias
e4dd3b2821 Merge pull request #12544 from freqtrade/dependabot/pip/develop/types-2e6adcc4ce
chore(deps-dev): bump types-python-dateutil from 2.9.0.20251108 to 2.9.0.20251115 in the types group
2025-11-24 07:02:11 +01:00
Matthias
7698ee9f3d Merge pull request #12549 from freqtrade/dependabot/pip/develop/numpy-2.3.5
chore(deps): bump numpy from 2.3.4 to 2.3.5
2025-11-24 06:50:52 +01:00
Matthias
c5127ba522 Merge pull request #12548 from freqtrade/dependabot/pip/develop/ruff-0.14.5
chore(deps-dev): bump ruff from 0.14.4 to 0.14.5
2025-11-24 06:50:13 +01:00
Matthias
024d2db2e2 chore: bump pre-commit config dateutil types 2025-11-24 06:37:39 +01:00
Matthias
f2498df99b Merge pull request #12550 from freqtrade/dependabot/pip/develop/plotly-6.5.0
chore(deps): bump plotly from 6.4.0 to 6.5.0
2025-11-24 06:35:18 +01:00
Matthias
37ce645bad Merge pull request #12545 from freqtrade/dependabot/pip/develop/cachetools-6.2.2
chore(deps): bump cachetools from 6.2.1 to 6.2.2
2025-11-24 06:32:43 +01:00
Matthias
d4188a093b Merge pull request #12547 from freqtrade/dependabot/pip/develop/fastapi-0.121.3
chore(deps): bump fastapi from 0.121.1 to 0.121.3
2025-11-24 06:32:14 +01:00
dependabot[bot]
bc3342b4d2 chore(deps): bump plotly from 6.4.0 to 6.5.0
Bumps [plotly](https://github.com/plotly/plotly.py) from 6.4.0 to 6.5.0.
- [Release notes](https://github.com/plotly/plotly.py/releases)
- [Changelog](https://github.com/plotly/plotly.py/blob/main/CHANGELOG.md)
- [Commits](https://github.com/plotly/plotly.py/compare/v6.4.0...v6.5.0)

---
updated-dependencies:
- dependency-name: plotly
  dependency-version: 6.5.0
  dependency-type: direct:production
  update-type: version-update:semver-minor
...

Signed-off-by: dependabot[bot] <support@github.com>
2025-11-24 03:02:27 +00:00
dependabot[bot]
64cf284c07 chore(deps): bump numpy from 2.3.4 to 2.3.5
Bumps [numpy](https://github.com/numpy/numpy) from 2.3.4 to 2.3.5.
- [Release notes](https://github.com/numpy/numpy/releases)
- [Changelog](https://github.com/numpy/numpy/blob/main/doc/RELEASE_WALKTHROUGH.rst)
- [Commits](https://github.com/numpy/numpy/compare/v2.3.4...v2.3.5)

---
updated-dependencies:
- dependency-name: numpy
  dependency-version: 2.3.5
  dependency-type: direct:production
  update-type: version-update:semver-patch
...

Signed-off-by: dependabot[bot] <support@github.com>
2025-11-24 03:02:23 +00:00
dependabot[bot]
15b0cc28f8 chore(deps-dev): bump ruff from 0.14.4 to 0.14.5
Bumps [ruff](https://github.com/astral-sh/ruff) from 0.14.4 to 0.14.5.
- [Release notes](https://github.com/astral-sh/ruff/releases)
- [Changelog](https://github.com/astral-sh/ruff/blob/main/CHANGELOG.md)
- [Commits](https://github.com/astral-sh/ruff/compare/0.14.4...0.14.5)

---
updated-dependencies:
- dependency-name: ruff
  dependency-version: 0.14.5
  dependency-type: direct:development
  update-type: version-update:semver-patch
...

Signed-off-by: dependabot[bot] <support@github.com>
2025-11-24 03:02:14 +00:00
dependabot[bot]
a647ceb753 chore(deps): bump fastapi from 0.121.1 to 0.121.3
Bumps [fastapi](https://github.com/fastapi/fastapi) from 0.121.1 to 0.121.3.
- [Release notes](https://github.com/fastapi/fastapi/releases)
- [Commits](https://github.com/fastapi/fastapi/compare/0.121.1...0.121.3)

---
updated-dependencies:
- dependency-name: fastapi
  dependency-version: 0.121.3
  dependency-type: direct:production
  update-type: version-update:semver-patch
...

Signed-off-by: dependabot[bot] <support@github.com>
2025-11-24 03:01:54 +00:00
dependabot[bot]
54840a3f7e chore(deps-dev): bump time-machine from 2.19.0 to 3.0.0
Bumps [time-machine](https://github.com/adamchainz/time-machine) from 2.19.0 to 3.0.0.
- [Changelog](https://github.com/adamchainz/time-machine/blob/main/docs/changelog.rst)
- [Commits](https://github.com/adamchainz/time-machine/compare/2.19.0...3.0.0)

---
updated-dependencies:
- dependency-name: time-machine
  dependency-version: 3.0.0
  dependency-type: direct:development
  update-type: version-update:semver-major
...

Signed-off-by: dependabot[bot] <support@github.com>
2025-11-24 03:01:50 +00:00
dependabot[bot]
2b1ef1bf59 chore(deps): bump cachetools from 6.2.1 to 6.2.2
Bumps [cachetools](https://github.com/tkem/cachetools) from 6.2.1 to 6.2.2.
- [Changelog](https://github.com/tkem/cachetools/blob/master/CHANGELOG.rst)
- [Commits](https://github.com/tkem/cachetools/compare/v6.2.1...v6.2.2)

---
updated-dependencies:
- dependency-name: cachetools
  dependency-version: 6.2.2
  dependency-type: direct:production
  update-type: version-update:semver-patch
...

Signed-off-by: dependabot[bot] <support@github.com>
2025-11-24 03:01:45 +00:00
dependabot[bot]
757439a594 chore(deps-dev): bump types-python-dateutil in the types group
Bumps the types group with 1 update: [types-python-dateutil](https://github.com/typeshed-internal/stub_uploader).


Updates `types-python-dateutil` from 2.9.0.20251108 to 2.9.0.20251115
- [Commits](https://github.com/typeshed-internal/stub_uploader/commits)

---
updated-dependencies:
- dependency-name: types-python-dateutil
  dependency-version: 2.9.0.20251115
  dependency-type: direct:development
  update-type: version-update:semver-patch
  dependency-group: types
...

Signed-off-by: dependabot[bot] <support@github.com>
2025-11-24 03:01:12 +00:00
mrpabloyeah
f98efdbe07 Fix backtesting exception when no data is available for a pair 2025-11-17 13:09:48 +01:00
29 changed files with 911 additions and 525 deletions

View File

@@ -74,15 +74,17 @@ jobs:
run: |
pytest --random-order --cov=freqtrade --cov=freqtrade_client --cov-config=.coveragerc
- name: Coveralls
- uses: codecov/codecov-action@5a1091511ad55cbe89839c7260b706298ca349f7 # v5.5.1
if: (runner.os == 'Linux' && matrix.python-version == '3.12' && matrix.os == 'ubuntu-24.04')
with:
fail_ci_if_error: true
token: ${{ secrets.CODECOV_TOKEN }}
- name: Cleanup codecov dirty state files
if: (runner.os == 'Linux' && matrix.python-version == '3.12' && matrix.os == 'ubuntu-24.04')
env:
# Coveralls token. Not used as secret due to github not providing secrets to forked repositories
COVERALLS_REPO_TOKEN: 6D1m0xupS3FgutfuGao8keFf9Hc0FpIXu
run: |
# Allow failure for coveralls
uv pip install coveralls
coveralls || true
# See https://github.com/codecov/codecov-action/issues/1851
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
@@ -273,10 +275,7 @@ jobs:
# Notify only once - when CI completes (and after deploy) in case it's successful
notify-complete:
needs: [
tests,
docs-check,
mypy-version-check,
pre-commit,
build,
build-linux-online
]
runs-on: ubuntu-22.04
@@ -304,11 +303,23 @@ jobs:
webhookUrl: ${{ secrets.DISCORD_WEBHOOK }}
build:
if: always()
name: "Build"
needs: [ tests, docs-check, mypy-version-check, pre-commit ]
needs: [
tests,
docs-check,
mypy-version-check,
pre-commit,
]
runs-on: ubuntu-22.04
steps:
- name: Decide whether the needed jobs succeeded or failed
uses: re-actors/alls-green@05ac9388f0aebcb5727afa17fcccfecd6f8ec5fe # v1.2.2
with:
jobs: ${{ toJSON(needs) }}
- uses: actions/checkout@v5
with:
persist-credentials: false
@@ -403,10 +414,7 @@ jobs:
docker-build:
name: "Docker Build and Deploy"
needs: [
tests,
docs-check,
mypy-version-check,
pre-commit
build,
]
if: (github.event_name == 'push' || github.event_name == 'schedule' || github.event_name == 'release') && github.repository == 'freqtrade/freqtrade'
uses: ./.github/workflows/docker-build.yml

View File

@@ -14,6 +14,7 @@ permissions: {}
jobs:
zizmor:
name: Run zizmor 🌈
runs-on: ubuntu-latest
permissions:
security-events: write

View File

@@ -30,7 +30,7 @@ repos:
- types-filelock==3.2.7
- types-requests==2.32.4.20250913
- types-tabulate==0.9.0.20241207
- types-python-dateutil==2.9.0.20251108
- types-python-dateutil==2.9.0.20251115
- scipy-stubs==1.16.3.0
- SQLAlchemy==2.0.44
# stages: [push]
@@ -44,7 +44,7 @@ repos:
- repo: https://github.com/charliermarsh/ruff-pre-commit
# Ruff version.
rev: 'v0.14.5'
rev: 'v0.14.6'
hooks:
- id: ruff
- id: ruff-format

View File

@@ -675,7 +675,7 @@ Should you experience problems you suspect are caused by websockets, you can dis
Should you be required to use a proxy, please refer to the [proxy section](#using-a-proxy-with-freqtrade) for more information.
!!! Info "Rollout"
We're implementing this out slowly, ensuring stability of your bots.
We're rolling this out slowly, ensuring stability of your bots.
Currently, usage is limited to ohlcv data streams.
It's also limited to a few exchanges, with new exchanges being added on an ongoing basis.

View File

@@ -26,10 +26,19 @@ Alternatively (e.g. if your system is not supported by the setup.sh script), fol
This will install all required tools for development, including `pytest`, `ruff`, `mypy`, and `coveralls`.
Then install the git hook scripts by running `pre-commit install`, so your changes will be verified locally before committing.
This avoids a lot of waiting for CI already, as some basic formatting checks are done locally on your machine.
Run the following command to install the git hook scripts:
Before opening a pull request, please familiarize yourself with our [Contributing Guidelines](https://github.com/freqtrade/freqtrade/blob/develop/CONTRIBUTING.md).
``` bash
pre-commit install
```
These pre-commit scripts check your changes automatically before each commit.
If any formatting issues are found, the commit will fail and will prompt for fixes.
This reduces unnecessary CI failures, reduces maintenance burden, and improves code quality.
You can run the checks manually when necessary with `pre-commit run -a`.
Before opening a pull request, please also familiarize yourself with our [Contributing Guidelines](https://github.com/freqtrade/freqtrade/blob/develop/CONTRIBUTING.md).
### Devcontainer setup

View File

@@ -5,7 +5,6 @@ from datetime import UTC, datetime
from pathlib import Path
import ccxt
from cachetools import TTLCache
from pandas import DataFrame
from freqtrade.constants import DEFAULT_DATAFRAME_COLUMNS
@@ -21,6 +20,7 @@ from freqtrade.exchange.common import retrier
from freqtrade.exchange.exchange_types import FtHas, Tickers
from freqtrade.exchange.exchange_utils_timeframe import timeframe_to_msecs
from freqtrade.misc import deep_merge_dicts, json_load
from freqtrade.util import FtTTLCache
from freqtrade.util.datetime_helpers import dt_from_ts, dt_ts
@@ -76,7 +76,7 @@ class Binance(Exchange):
def __init__(self, *args, **kwargs) -> None:
super().__init__(*args, **kwargs)
self._spot_delist_schedule_cache: TTLCache = TTLCache(maxsize=100, ttl=300)
self._spot_delist_schedule_cache: FtTTLCache = FtTTLCache(maxsize=100, ttl=300)
def get_proxy_coin(self) -> str:
"""

File diff suppressed because it is too large Load Diff

View File

@@ -16,7 +16,6 @@ from typing import Any, Literal, TypeGuard, TypeVar
import ccxt
import ccxt.pro as ccxt_pro
from cachetools import TTLCache
from ccxt import TICK_SIZE
from dateutil import parser
from pandas import DataFrame, concat
@@ -107,9 +106,8 @@ from freqtrade.misc import (
file_load_json,
safe_value_fallback2,
)
from freqtrade.util import dt_from_ts, dt_now
from freqtrade.util import FtTTLCache, PeriodicCache, dt_from_ts, dt_now
from freqtrade.util.datetime_helpers import dt_humanize_delta, dt_ts, format_ms_time
from freqtrade.util.periodic_cache import PeriodicCache
logger = logging.getLogger(__name__)
@@ -230,13 +228,13 @@ class Exchange:
self._cache_lock = Lock()
# Cache for 10 minutes ...
self._fetch_tickers_cache: TTLCache = TTLCache(maxsize=4, ttl=60 * 10)
self._fetch_tickers_cache: FtTTLCache = FtTTLCache(maxsize=4, ttl=60 * 10)
# Cache values for 300 to avoid frequent polling of the exchange for prices
# Caching only applies to RPC methods, so prices for open trades are still
# refreshed once every iteration.
# Shouldn't be too high either, as it'll freeze UI updates in case of open orders.
self._exit_rate_cache: TTLCache = TTLCache(maxsize=100, ttl=300)
self._entry_rate_cache: TTLCache = TTLCache(maxsize=100, ttl=300)
self._exit_rate_cache: FtTTLCache = FtTTLCache(maxsize=100, ttl=300)
self._entry_rate_cache: FtTTLCache = FtTTLCache(maxsize=100, ttl=300)
# Holds candles
self._klines: dict[PairWithTimeframe, DataFrame] = {}
@@ -1771,7 +1769,7 @@ class Exchange:
balances.pop("total", None)
balances.pop("used", None)
self._log_exchange_response("fetch_balances", balances)
self._log_exchange_response("fetch_balance", balances)
return balances
except ccxt.DDoSProtection as e:
raise DDosProtection(e) from e
@@ -2164,7 +2162,9 @@ class Exchange:
name = side.capitalize()
strat_name = "entry_pricing" if side == "entry" else "exit_pricing"
cache_rate: TTLCache = self._entry_rate_cache if side == "entry" else self._exit_rate_cache
cache_rate: FtTTLCache = (
self._entry_rate_cache if side == "entry" else self._exit_rate_cache
)
if not refresh:
with self._cache_lock:
rate = cache_rate.get(pair)

View File

@@ -3,6 +3,7 @@
import logging
from copy import deepcopy
from datetime import datetime
from typing import Any
from freqtrade.constants import BuySell
from freqtrade.enums import MarginMode, TradingMode
@@ -56,6 +57,13 @@ class Hyperliquid(Exchange):
config.update(super()._ccxt_config)
return config
def market_is_tradable(self, market: dict[str, Any]) -> bool:
parent_check = super().market_is_tradable(market)
# Exclude hip3 markets for now - which have the format XYZ:GOOGL/USDT:USDT -
# and XYZ:GOOGL as base
return parent_check and ":" not in market["base"]
def get_max_leverage(self, pair: str, stake_amount: float | None) -> float:
# There are no leverage tiers
if self.trading_mode == TradingMode.FUTURES:

View File

@@ -82,7 +82,7 @@ class Kraken(Exchange):
balances.pop("free", None)
balances.pop("total", None)
balances.pop("used", None)
self._log_exchange_response("fetch_balances", balances)
self._log_exchange_response("fetch_balance", balances)
# Consolidate balances
balances = self.consolidate_balances(balances)
@@ -104,7 +104,7 @@ class Kraken(Exchange):
balances[bal]["used"] = sum(order[1] for order in order_list if order[0] == bal)
balances[bal]["free"] = balances[bal]["total"] - balances[bal]["used"]
self._log_exchange_response("fetch_balances2", balances)
self._log_exchange_response("fetch_balance2", balances)
return balances
except ccxt.DDoSProtection as e:
raise DDosProtection(e) from e

View File

@@ -1,6 +1,8 @@
from collections.abc import Callable
from cachetools import TTLCache, cached
from cachetools import cached
from freqtrade.util import FtTTLCache
class LoggingMixin:
@@ -18,7 +20,7 @@ class LoggingMixin:
"""
self.logger = logger
self.refresh_period = refresh_period
self._log_cache: TTLCache = TTLCache(maxsize=1024, ttl=self.refresh_period)
self._log_cache: FtTTLCache = FtTTLCache(maxsize=1024, ttl=self.refresh_period)
def log_once(self, message: str, logmethod: Callable, force_show: bool = False) -> None:
"""

View File

@@ -126,6 +126,7 @@ class Backtesting:
self.config["dry_run"] = True
self.price_pair_prec: dict[str, Series] = {}
self.available_pairs: list[str] = []
self.run_ids: dict[str, str] = {}
self.strategylist: list[IStrategy] = []
self.all_bt_content: dict[str, BacktestContentType] = {}
@@ -176,7 +177,8 @@ class Backtesting:
self._validate_pairlists_for_backtesting()
self.dataprovider.add_pairlisthandler(self.pairlists)
self.pairlists.refresh_pairlist()
self.dynamic_pairlist: bool = self.config.get("enable_dynamic_pairlist", False)
self.pairlists.refresh_pairlist(only_first=self.dynamic_pairlist)
if len(self.pairlists.whitelist) == 0:
raise OperationalException("No pair in whitelist.")
@@ -211,7 +213,6 @@ class Backtesting:
self._can_short = self.trading_mode != TradingMode.SPOT
self._position_stacking: bool = self.config.get("position_stacking", False)
self.enable_protections: bool = self.config.get("enable_protections", False)
self.dynamic_pairlist: bool = self.config.get("enable_dynamic_pairlist", False)
migrate_data(config, self.exchange)
self.init_backtest()
@@ -335,10 +336,12 @@ class Backtesting:
self.progress.set_new_value(1)
self._load_bt_data_detail()
self.price_pair_prec = {}
for pair in self.pairlists.whitelist:
if pair in data:
# Load price precision logic
self.price_pair_prec[pair] = get_tick_size_over_time(data[pair])
self.available_pairs.append(pair)
return data, self.timerange
def _load_bt_data_detail(self) -> None:
@@ -1587,7 +1590,7 @@ class Backtesting:
self.check_abort()
if self.dynamic_pairlist and self.pairlists:
self.pairlists.refresh_pairlist()
self.pairlists.refresh_pairlist(pairs=self.available_pairs)
pairs = self.pairlists.whitelist
# Reset open trade count for this candle

View File

@@ -75,11 +75,11 @@ def init_plotscript(config, markets: list, startup_candles: int = 0):
)
no_trades = False
filename = config.get("exportfilename")
filename = config.get("exportfilename") or config.get("exportdirectory")
if config.get("no_trades", False):
no_trades = True
elif config["trade_source"] == "file":
if not filename.is_dir() and not filename.is_file():
if not filename or (not filename.is_dir() and not filename.is_file()):
logger.warning("Backtest file is missing skipping trades.")
no_trades = True
try:

View File

@@ -7,11 +7,10 @@ Provides dynamic pair list based on Market Cap
import logging
import math
from cachetools import TTLCache
from freqtrade.exceptions import OperationalException
from freqtrade.exchange.exchange_types import Tickers
from freqtrade.plugins.pairlist.IPairList import IPairList, PairlistParameter, SupportsBacktesting
from freqtrade.util import FtTTLCache
from freqtrade.util.coin_gecko import FtCoinGeckoApi
@@ -38,7 +37,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: TTLCache = TTLCache(maxsize=1, ttl=self._refresh_period)
self._marketcap_cache: FtTTLCache = FtTTLCache(maxsize=1, ttl=self._refresh_period)
_coingecko_config = self._config.get("coingecko", {})

View File

@@ -10,7 +10,6 @@ import logging
from datetime import timedelta
from typing import TypedDict
from cachetools import TTLCache
from pandas import DataFrame
from freqtrade.constants import ListPairsWithTimeframes, PairWithTimeframe
@@ -18,7 +17,7 @@ from freqtrade.exceptions import OperationalException
from freqtrade.exchange import timeframe_to_minutes, timeframe_to_prev_date
from freqtrade.exchange.exchange_types import Ticker, Tickers
from freqtrade.plugins.pairlist.IPairList import IPairList, PairlistParameter, SupportsBacktesting
from freqtrade.util import dt_now, format_ms_time
from freqtrade.util import FtTTLCache, dt_now, format_ms_time
logger = logging.getLogger(__name__)
@@ -47,7 +46,7 @@ class PercentChangePairList(IPairList):
self._min_value = self._pairlistconfig.get("min_value", None)
self._max_value = self._pairlistconfig.get("max_value", None)
self._refresh_period = self._pairlistconfig.get("refresh_period", 1800)
self._pair_cache: TTLCache = TTLCache(maxsize=1, ttl=self._refresh_period)
self._pair_cache: FtTTLCache = FtTTLCache(maxsize=1, ttl=self._refresh_period)
self._lookback_days = self._pairlistconfig.get("lookback_days", 0)
self._lookback_timeframe = self._pairlistconfig.get("lookback_timeframe", "1d")
self._lookback_period = self._pairlistconfig.get("lookback_period", 0)

View File

@@ -10,7 +10,6 @@ from typing import Any
import rapidjson
import requests
from cachetools import TTLCache
from freqtrade import __version__
from freqtrade.configuration.load_config import CONFIG_PARSE_MODE
@@ -18,6 +17,7 @@ from freqtrade.exceptions import OperationalException
from freqtrade.exchange.exchange_types import Tickers
from freqtrade.plugins.pairlist.IPairList import IPairList, PairlistParameter, SupportsBacktesting
from freqtrade.plugins.pairlist.pairlist_helpers import expand_pairlist
from freqtrade.util import FtTTLCache
logger = logging.getLogger(__name__)
@@ -48,7 +48,7 @@ class RemotePairList(IPairList):
self._number_pairs = self._pairlistconfig["number_assets"]
self._refresh_period: int = self._pairlistconfig.get("refresh_period", 1800)
self._keep_pairlist_on_failure = self._pairlistconfig.get("keep_pairlist_on_failure", True)
self._pair_cache: TTLCache = TTLCache(maxsize=1, ttl=self._refresh_period)
self._pair_cache: FtTTLCache = FtTTLCache(maxsize=1, ttl=self._refresh_period)
self._pairlist_url = self._pairlistconfig.get("pairlist_url", "")
self._read_timeout = self._pairlistconfig.get("read_timeout", 60)
self._bearer_token = self._pairlistconfig.get("bearer_token", "")
@@ -159,7 +159,7 @@ class RemotePairList(IPairList):
)
self._refresh_period = remote_refresh_period
self._pair_cache = TTLCache(maxsize=1, ttl=remote_refresh_period)
self._pair_cache = FtTTLCache(maxsize=1, ttl=remote_refresh_period)
self._init_done = True

View File

@@ -7,7 +7,6 @@ import sys
from datetime import timedelta
import numpy as np
from cachetools import TTLCache
from pandas import DataFrame
from freqtrade.constants import ListPairsWithTimeframes
@@ -15,7 +14,7 @@ from freqtrade.exceptions import OperationalException
from freqtrade.exchange.exchange_types import Tickers
from freqtrade.misc import plural
from freqtrade.plugins.pairlist.IPairList import IPairList, PairlistParameter, SupportsBacktesting
from freqtrade.util import dt_floor_day, dt_now, dt_ts
from freqtrade.util import FtTTLCache, dt_floor_day, dt_now, dt_ts
logger = logging.getLogger(__name__)
@@ -38,7 +37,7 @@ class VolatilityFilter(IPairList):
self._def_candletype = self._config["candle_type_def"]
self._sort_direction: str | None = self._pairlistconfig.get("sort_direction", None)
self._pair_cache: TTLCache = TTLCache(maxsize=1000, ttl=self._refresh_period)
self._pair_cache: FtTTLCache = FtTTLCache(maxsize=1000, ttl=self._refresh_period)
candle_limit = self._exchange.ohlcv_candle_limit("1d", self._def_candletype)
if self._days < 1:

View File

@@ -8,14 +8,12 @@ import logging
from datetime import timedelta
from typing import Any, Literal
from cachetools import TTLCache
from freqtrade.constants import ListPairsWithTimeframes
from freqtrade.exceptions import OperationalException
from freqtrade.exchange import timeframe_to_minutes, timeframe_to_prev_date
from freqtrade.exchange.exchange_types import Tickers
from freqtrade.plugins.pairlist.IPairList import IPairList, PairlistParameter, SupportsBacktesting
from freqtrade.util import dt_now, format_ms_time
from freqtrade.util import FtTTLCache, dt_now, format_ms_time
logger = logging.getLogger(__name__)
@@ -43,7 +41,7 @@ class VolumePairList(IPairList):
self._min_value = self._pairlistconfig.get("min_value", 0)
self._max_value = self._pairlistconfig.get("max_value", None)
self._refresh_period = self._pairlistconfig.get("refresh_period", 1800)
self._pair_cache: TTLCache = TTLCache(maxsize=1, ttl=self._refresh_period)
self._pair_cache: FtTTLCache = FtTTLCache(maxsize=1, ttl=self._refresh_period)
self._lookback_days = self._pairlistconfig.get("lookback_days", 0)
self._lookback_timeframe = self._pairlistconfig.get("lookback_timeframe", "1d")
self._lookback_period = self._pairlistconfig.get("lookback_period", 0)

View File

@@ -5,7 +5,6 @@ Rate of change pairlist filter
import logging
from datetime import timedelta
from cachetools import TTLCache
from pandas import DataFrame
from freqtrade.constants import ListPairsWithTimeframes
@@ -13,7 +12,7 @@ from freqtrade.exceptions import OperationalException
from freqtrade.exchange.exchange_types import Tickers
from freqtrade.misc import plural
from freqtrade.plugins.pairlist.IPairList import IPairList, PairlistParameter, SupportsBacktesting
from freqtrade.util import dt_floor_day, dt_now, dt_ts
from freqtrade.util import FtTTLCache, dt_floor_day, dt_now, dt_ts
logger = logging.getLogger(__name__)
@@ -32,7 +31,7 @@ class RangeStabilityFilter(IPairList):
self._def_candletype = self._config["candle_type_def"]
self._sort_direction: str | None = self._pairlistconfig.get("sort_direction", None)
self._pair_cache: TTLCache = TTLCache(maxsize=1000, ttl=self._refresh_period)
self._pair_cache: FtTTLCache = FtTTLCache(maxsize=1000, ttl=self._refresh_period)
candle_limit = self._exchange.ohlcv_candle_limit("1d", self._def_candletype)
if self._days < 1:

View File

@@ -5,7 +5,7 @@ PairList manager class
import logging
from functools import partial
from cachetools import LRUCache, TTLCache, cached
from cachetools import LRUCache, cached
from freqtrade.constants import Config, ListPairsWithTimeframes
from freqtrade.data.dataprovider import DataProvider
@@ -17,6 +17,7 @@ from freqtrade.mixins import LoggingMixin
from freqtrade.plugins.pairlist.IPairList import IPairList, SupportsBacktesting
from freqtrade.plugins.pairlist.pairlist_helpers import expand_pairlist
from freqtrade.resolvers import PairListResolver
from freqtrade.util import FtTTLCache
logger = logging.getLogger(__name__)
@@ -129,12 +130,24 @@ class PairListManager(LoggingMixin):
"""List of short_desc for each Pairlist Handler"""
return [{p.name: p.short_desc()} for p in self._pairlist_handlers]
@cached(TTLCache(maxsize=1, ttl=1800))
@cached(FtTTLCache(maxsize=1, ttl=1800))
def _get_cached_tickers(self) -> Tickers:
return self._exchange.get_tickers()
def refresh_pairlist(self) -> None:
"""Run pairlist through all configured Pairlist Handlers."""
def refresh_pairlist(self, only_first: bool = False, pairs: list[str] | None = None) -> None:
"""
Run pairlist through all configured Pairlist Handlers.
:param only_first: If True, only run the first PairList handler (the generator)
and skip all subsequent filters. Used during backtesting startup to ensure
historic data is loaded for the complete universe of pairs that the
generator can produce (even if later filters would reduce the list size).
Prevents missing data when a filter returns a variable number of pairs
across refresh cycles.
:param pairs: Optional list of pairs to intersect with the generated pairlist.
Only pairs present both in the generated list and this parameter are kept.
Used in backtesting to filter out pairs with no available data.
"""
# Tickers should be cached to avoid calling the exchange on each call.
tickers: dict = {}
if self._tickers_needed:
@@ -143,10 +156,15 @@ class PairListManager(LoggingMixin):
# Generate the pairlist with first Pairlist Handler in the chain
pairlist = self._pairlist_handlers[0].gen_pairlist(tickers)
# Process all Pairlist Handlers in the chain
# except for the first one, which is the generator.
for pairlist_handler in self._pairlist_handlers[1:]:
pairlist = pairlist_handler.filter_pairlist(pairlist, tickers)
# Optional intersection with an explicit list of pairs (used in backtesting)
if pairs is not None:
pairlist = [p for p in pairlist if p in pairs]
if not only_first:
# Process all Pairlist Handlers in the chain
# except for the first one, which is the generator.
for pairlist_handler in self._pairlist_handlers[1:]:
pairlist = pairlist_handler.filter_pairlist(pairlist, tickers)
# Validation against blacklist happens after the chain of Pairlist Handlers
# to ensure blacklist is respected.

View File

@@ -37,7 +37,7 @@ class ApiBG:
# Generic background jobs
# TODO: Change this to TTLCache
# TODO: Change this to FtTTLCache
jobs: dict[str, JobsContainer] = {}
# Pairlist evaluate things
pairlist_running: bool = False

View File

@@ -7,11 +7,11 @@ import logging
from datetime import datetime
from typing import Any
from cachetools import TTLCache
from requests.exceptions import RequestException
from freqtrade.constants import SUPPORTED_FIAT, Config
from freqtrade.mixins.logging_mixin import LoggingMixin
from freqtrade.util import FtTTLCache
from freqtrade.util.coin_gecko import FtCoinGeckoApi
@@ -54,7 +54,7 @@ class CryptoToFiatConverter(LoggingMixin):
def __init__(self, config: Config) -> None:
# Timeout: 6h
self._pair_price: TTLCache = TTLCache(maxsize=500, ttl=6 * 60 * 60)
self._pair_price: FtTTLCache = FtTTLCache(maxsize=500, ttl=6 * 60 * 60)
_coingecko_config = config.get("coingecko", {})
self._coingecko = FtCoinGeckoApi(

View File

@@ -22,6 +22,7 @@ from freqtrade.util.formatters import (
round_value,
)
from freqtrade.util.ft_precise import FtPrecise
from freqtrade.util.ft_ttlcache import FtTTLCache
from freqtrade.util.measure_time import MeasureTime
from freqtrade.util.periodic_cache import PeriodicCache
from freqtrade.util.progress_tracker import ( # noqa F401
@@ -59,4 +60,5 @@ __all__ = [
"print_rich_table",
"print_df_rich_table",
"CustomProgress",
"FtTTLCache",
]

View File

@@ -0,0 +1,12 @@
import time
from cachetools import TTLCache
class FtTTLCache(TTLCache):
"""
A TTLCache with a different default timer to allow for easier mocking in tests.
"""
def __init__(self, maxsize, ttl, timer=time.time, getsizeof=None):
super().__init__(maxsize=maxsize, ttl=ttl, timer=timer, getsizeof=getsizeof)

View File

@@ -2,7 +2,7 @@ import logging
import time
from collections.abc import Callable
from cachetools import TTLCache
from freqtrade.util import FtTTLCache
logger = logging.getLogger(__name__)
@@ -27,7 +27,7 @@ class MeasureTime:
"""
self._callback = callback
self._time_limit = time_limit
self.__cache: TTLCache = TTLCache(maxsize=1, ttl=ttl)
self.__cache: FtTTLCache = FtTTLCache(maxsize=1, ttl=ttl)
def __enter__(self):
self._start = time.time()

View File

@@ -6,7 +6,7 @@
-r requirements-freqai-rl.txt
-r docs/requirements-docs.txt
ruff==0.14.4
ruff==0.14.5
mypy==1.18.2
pre-commit==4.4.0
pytest==9.0.1
@@ -18,7 +18,7 @@ pytest-timeout==2.4.0
pytest-xdist==3.8.0
isort==7.0.0
# For datetime mocking
time-machine==2.19.0
time-machine==3.0.0
# Convert jupyter notebooks to markdown documents
nbconvert==7.16.6
@@ -29,4 +29,4 @@ 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.20251108
types-python-dateutil==2.9.0.20251115

View File

@@ -1,4 +1,4 @@
# Include all requirements to run the bot.
-r requirements.txt
plotly==6.4.0
plotly==6.5.0

View File

@@ -1,4 +1,4 @@
numpy==2.3.4
numpy==2.3.5
pandas==2.3.3
bottleneck==1.6.0
numexpr==2.14.1
@@ -15,7 +15,7 @@ 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.1
cachetools==6.2.2
requests==2.32.5
urllib3==2.5.0
certifi==2025.11.12
@@ -37,7 +37,7 @@ orjson==3.11.4
sdnotify==0.3.2
# API Server
fastapi==0.121.1
fastapi==0.121.3
pydantic==2.12.4
uvicorn==0.38.0
pyjwt==2.10.1

View File

@@ -2772,7 +2772,7 @@ def test_time_pair_generator_open_trades_first(mocker, default_conf, dynamic_pai
dummy_row = (end_date, 1.0, 1.1, 0.9, 1.0, 0, 0, 0, 0, None, None)
data = {pair: [dummy_row] for pair in pairs}
def mock_refresh(self):
def mock_refresh(self, **kwargs):
# Simulate shuffle
self._whitelist = pairs[::-1] # ['ETH/BTC', 'NEO/BTC', 'LTC/BTC', 'XRP/BTC']