From 8fb21e8f50a9015af021805e8514f9c9716077df Mon Sep 17 00:00:00 2001 From: Stefano Date: Wed, 25 Feb 2026 17:17:26 +0900 Subject: [PATCH] change into pairlist plugin --- docs/includes/pairlists.md | 20 ++++---- freqtrade/constants.py | 2 +- ...MarketFilter.py => CrossMarketPairlist.py} | 50 ++++++++++++++++--- 3 files changed, 55 insertions(+), 17 deletions(-) rename freqtrade/plugins/pairlist/{CrossMarketFilter.py => CrossMarketPairlist.py} (62%) diff --git a/docs/includes/pairlists.md b/docs/includes/pairlists.md index dc73223d5..2a5540b00 100644 --- a/docs/includes/pairlists.md +++ b/docs/includes/pairlists.md @@ -2,11 +2,11 @@ Pairlist Handlers define the list of pairs (pairlist) that the bot should trade. They are configured in the `pairlists` section of the configuration settings. -In your configuration, you can use Static Pairlist (defined by the [`StaticPairList`](#static-pair-list) Pairlist Handler) and Dynamic Pairlist (defined by the [`VolumePairList`](#volume-pair-list) and [`PercentChangePairList`](#percent-change-pair-list) Pairlist Handlers). +In your configuration, you can use Static Pairlist (defined by the [`StaticPairList`](#static-pair-list) Pairlist Handler) and Dynamic Pairlist (defined by the [`VolumePairList`](#volume-pair-list), [`CrossMarketPairlist`](#crossmarketpairlist), [`MarketCapPairlist`](#marketcappairlist) and [`PercentChangePairList`](#percent-change-pair-list) Pairlist Handlers). -Additionally, [`AgeFilter`](#agefilter), [`CrossMarketFilter`](#crossmarketfilter), [`DelistFilter`](#delistfilter), [`PrecisionFilter`](#precisionfilter), [`PriceFilter`](#pricefilter), [`ShuffleFilter`](#shufflefilter), [`SpreadFilter`](#spreadfilter) and [`VolatilityFilter`](#volatilityfilter) act as Pairlist Filters, removing certain pairs and/or moving their positions in the pairlist. +Additionally, [`AgeFilter`](#agefilter), [`DelistFilter`](#delistfilter), [`PrecisionFilter`](#precisionfilter), [`PriceFilter`](#pricefilter), [`ShuffleFilter`](#shufflefilter), [`SpreadFilter`](#spreadfilter) and [`VolatilityFilter`](#volatilityfilter) act as Pairlist Filters, removing certain pairs and/or moving their positions in the pairlist. -If multiple Pairlist Handlers are used, they are chained and a combination of all Pairlist Handlers forms the resulting pairlist the bot uses for trading and backtesting. Pairlist Handlers are executed in the sequence they are configured. You can define either `StaticPairList`, `VolumePairList`, `ProducerPairList`, `RemotePairList`, `MarketCapPairList` or `PercentChangePairList` as the starting Pairlist Handler. +If multiple Pairlist Handlers are used, they are chained and a combination of all Pairlist Handlers forms the resulting pairlist the bot uses for trading and backtesting. Pairlist Handlers are executed in the sequence they are configured. You can define either `StaticPairList`, `VolumePairList`, `ProducerPairList`, `RemotePairList`, `MarketCapPairList`, `PercentChangePairList` or `CrossMarketPairList` as the starting Pairlist Handler. Inactive markets are always removed from the resulting pairlist. Explicitly blacklisted pairs (those in the `pair_blacklist` configuration setting) are also always removed from the resulting pairlist. @@ -26,8 +26,8 @@ You may also use something like `.*DOWN/BTC` or `.*UP/BTC` to exclude leveraged * [`ProducerPairList`](#producerpairlist) * [`RemotePairList`](#remotepairlist) * [`MarketCapPairList`](#marketcappairlist) +* [`CrossMarketPairlist`](#crossmarketpairlist) * [`AgeFilter`](#agefilter) -* [`CrossMarketFilter`](#crossmarketfilter) * [`DelistFilter`](#delistfilter) * [`FullTradesFilter`](#fulltradesfilter) * [`OffsetFilter`](#offsetfilter) @@ -403,6 +403,12 @@ Coins like 1000PEPE/USDT or KPEPE/USDT:USDT are detected on a best effort basis, !!! Danger "Duplicate symbols in coingecko" Coingecko often has duplicate symbols, where the same symbol is used for different coins. Freqtrade will use the symbol as is and try to search for it on the exchange. If the symbol exists - it will be used. Freqtrade will however not check if the _intended_ symbol is the one coingecko meant. This can sometimes lead to unexpected results, especially on low volume coins or with meme coin categories. +#### CrossMarketPairlist + +Generate or filter pairs based of their availability on the opposite market. So for spot pairs, it will be checked against futures market, and vice versa. + +The `mode` setting defines whether the plugin will filters in (whitelist `mode`) or filters out (blacklist `mode`) pairs if they are active on opposite market. By default, the plugin will be in whitelist mode. + #### AgeFilter Removes pairs that have been listed on the exchange for less than `min_days_listed` days (defaults to `10`) or more than `max_days_listed` days (defaults `None` mean infinity). @@ -413,12 +419,6 @@ be caught out buying before the pair has finished dropping in price. This filter allows freqtrade to ignore pairs until they have been listed for at least `min_days_listed` days and listed before `max_days_listed`. -#### CrossMarketFilter - -Filter pairs based of their availability on the opposite market. So for spot pairs, it will be checked against futures market, and vice versa. - -The `mode` setting defines whether the plugin will filters in (whitelist `mode`) or filters out (blacklist `mode`) based of the availability on the opposite market. By default, the plugin will be in whitelist mode. - #### DelistFilter Removes pairs that will be delisted on the exchange maximum `max_days_from_now` days from now (defaults to `0` which remove all future delisted pairs no matter how far from now). Currently this filter only supports following exchanges: diff --git a/freqtrade/constants.py b/freqtrade/constants.py index 38d4f6017..68fada77e 100644 --- a/freqtrade/constants.py +++ b/freqtrade/constants.py @@ -61,8 +61,8 @@ AVAILABLE_PAIRLISTS = [ "ProducerPairList", "RemotePairList", "MarketCapPairList", + "CrossMarketPairlist", "AgeFilter", - "CrossMarketFilter", "DelistFilter", "FullTradesFilter", "OffsetFilter", diff --git a/freqtrade/plugins/pairlist/CrossMarketFilter.py b/freqtrade/plugins/pairlist/CrossMarketPairlist.py similarity index 62% rename from freqtrade/plugins/pairlist/CrossMarketFilter.py rename to freqtrade/plugins/pairlist/CrossMarketPairlist.py index 67701eae2..2a3cd6615 100644 --- a/freqtrade/plugins/pairlist/CrossMarketFilter.py +++ b/freqtrade/plugins/pairlist/CrossMarketPairlist.py @@ -4,17 +4,15 @@ Price pair list filter import logging -import ccxt.pro as ccxt_pro - -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 logger = logging.getLogger(__name__) -class CrossMarketFilter(IPairList): +class CrossMarketPairlist(IPairList): supports_backtesting = SupportsBacktesting.BIASED def __init__(self, *args, **kwargs) -> None: @@ -24,6 +22,8 @@ class CrossMarketFilter(IPairList): self._trading_mode: str = self._config["trading_mode"] self._stake_currency: str = self._config["stake_currency"] self._target_mode = "futures" if self._trading_mode == "spot" else "spot" + self._refresh_period = self._pairlistconfig.get("refresh_period", 1800) + self._pair_cache: FtTTLCache = FtTTLCache(maxsize=1, ttl=self._refresh_period) @property def needstickers(self) -> bool: @@ -57,6 +57,7 @@ class CrossMarketFilter(IPairList): "description": "Mode of operation", "help": "Mode of operation (whitelist/blacklist)", }, + **IPairList.refresh_period_parameter(), } def get_base_list(self): @@ -77,6 +78,35 @@ class CrossMarketFilter(IPairList): prefixes = ("1000", "1000000", "1M", "K", "M") + def gen_pairlist(self, tickers: Tickers) -> list[str]: + """ + Generate the pairlist + :param tickers: Tickers (from exchange.get_tickers). May be cached. + :return: List of pairs + """ + # Generate dynamic whitelist + # Must always run if this pairlist is the first in the list. + pairlist = self._pair_cache.get("pairlist") + if pairlist: + # Item found - no refresh necessary + return pairlist.copy() + else: + # Use fresh pairlist + # Check if pair quote currency equals to the stake currency. + _pairlist = [ + k + for k in self._exchange.get_markets( + quote_currencies=[self._stake_currency], tradable_only=True, active_only=True + ).keys() + ] + + _pairlist = self.verify_blacklist(_pairlist, logger.info) + + pairlist = self.filter_pairlist(_pairlist, tickers) + self._pair_cache["pairlist"] = pairlist.copy() + + return pairlist + def filter_pairlist(self, pairlist: list[str], tickers: Tickers) -> list[str]: bases = self.get_base_list() is_whitelist_mode = self._mode == "whitelist" @@ -88,10 +118,18 @@ class CrossMarketFilter(IPairList): found_in_bases = base in bases if not found_in_bases: for prefix in self.prefixes: + # Check in case of PEPE needs to be changed to 1000PEPE for example test_prefix = f"{prefix}{base}" - if test_prefix in bases: - found_in_bases = True + found_in_bases = test_prefix in bases + if found_in_bases: break + + # Check in case of 1000PEPE needs to be changed to PEPE for example + if base.startswith(prefix): + temp_base = base.removeprefix(prefix) + found_in_bases = temp_base in bases + if found_in_bases: + break if found_in_bases: whitelisted_pairlist.append(pair) filtered_pairlist.remove(pair)