mirror of
https://github.com/freqtrade/freqtrade.git
synced 2025-11-29 00:23:07 +00:00
Compare commits
27 Commits
e4dd3b2821
...
develop
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
bfe9aac16b | ||
|
|
1770a68457 | ||
|
|
57fd455adf | ||
|
|
61ecaa4c41 | ||
|
|
682c4137b4 | ||
|
|
759c18df3d | ||
|
|
04fda255de | ||
|
|
26f23c10b5 | ||
|
|
001baa07b1 | ||
|
|
72724037af | ||
|
|
1bd60c2afb | ||
|
|
c0a1911f22 | ||
|
|
f4920f199c | ||
|
|
6b8968ed0f | ||
|
|
85a88d3594 | ||
|
|
b5e17735a6 | ||
|
|
67beeb6f26 | ||
|
|
f94acc9173 | ||
|
|
a7ac957399 | ||
|
|
b337b075ab | ||
|
|
3152e97b2f | ||
|
|
77e8a53572 | ||
|
|
4eea0cca08 | ||
|
|
8274a6c3a9 | ||
|
|
f0a5b95ec0 | ||
|
|
54840a3f7e | ||
|
|
f98efdbe07 |
40
.github/workflows/ci.yml
vendored
40
.github/workflows/ci.yml
vendored
@@ -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
|
||||
|
||||
1
.github/workflows/zizmor.yml
vendored
1
.github/workflows/zizmor.yml
vendored
@@ -14,6 +14,7 @@ permissions: {}
|
||||
|
||||
jobs:
|
||||
zizmor:
|
||||
name: Run zizmor 🌈
|
||||
runs-on: ubuntu-latest
|
||||
permissions:
|
||||
security-events: write
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -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.
|
||||
|
||||
|
||||
@@ -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
|
||||
|
||||
|
||||
@@ -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
@@ -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)
|
||||
|
||||
@@ -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:
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -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:
|
||||
"""
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -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:
|
||||
|
||||
@@ -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", {})
|
||||
|
||||
|
||||
@@ -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)
|
||||
|
||||
@@ -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
|
||||
|
||||
|
||||
@@ -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:
|
||||
|
||||
@@ -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)
|
||||
|
||||
@@ -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:
|
||||
|
||||
@@ -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.
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -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(
|
||||
|
||||
@@ -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",
|
||||
]
|
||||
|
||||
12
freqtrade/util/ft_ttlcache.py
Normal file
12
freqtrade/util/ft_ttlcache.py
Normal 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)
|
||||
@@ -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()
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -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']
|
||||
|
||||
|
||||
Reference in New Issue
Block a user