From 9719f287957675238856670de6053678eccd2cfd Mon Sep 17 00:00:00 2001 From: Stefano Date: Wed, 25 Feb 2026 15:51:56 +0900 Subject: [PATCH] add crossmarketfilter --- docs/includes/pairlists.md | 9 +- freqtrade/constants.py | 1 + .../plugins/pairlist/CrossMarketFilter.py | 99 +++++++++++++++++++ 3 files changed, 108 insertions(+), 1 deletion(-) create mode 100644 freqtrade/plugins/pairlist/CrossMarketFilter.py diff --git a/docs/includes/pairlists.md b/docs/includes/pairlists.md index 2ee7533f2..dc73223d5 100644 --- a/docs/includes/pairlists.md +++ b/docs/includes/pairlists.md @@ -4,7 +4,7 @@ Pairlist Handlers define the list of pairs (pairlist) that the bot should trade. 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). -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. +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. 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. @@ -27,6 +27,7 @@ You may also use something like `.*DOWN/BTC` or `.*UP/BTC` to exclude leveraged * [`RemotePairList`](#remotepairlist) * [`MarketCapPairList`](#marketcappairlist) * [`AgeFilter`](#agefilter) +* [`CrossMarketFilter`](#crossmarketfilter) * [`DelistFilter`](#delistfilter) * [`FullTradesFilter`](#fulltradesfilter) * [`OffsetFilter`](#offsetfilter) @@ -412,6 +413,12 @@ 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 5a57f773f..38d4f6017 100644 --- a/freqtrade/constants.py +++ b/freqtrade/constants.py @@ -62,6 +62,7 @@ AVAILABLE_PAIRLISTS = [ "RemotePairList", "MarketCapPairList", "AgeFilter", + "CrossMarketFilter", "DelistFilter", "FullTradesFilter", "OffsetFilter", diff --git a/freqtrade/plugins/pairlist/CrossMarketFilter.py b/freqtrade/plugins/pairlist/CrossMarketFilter.py new file mode 100644 index 000000000..67701eae2 --- /dev/null +++ b/freqtrade/plugins/pairlist/CrossMarketFilter.py @@ -0,0 +1,99 @@ +""" +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 + + +logger = logging.getLogger(__name__) + + +class CrossMarketFilter(IPairList): + supports_backtesting = SupportsBacktesting.BIASED + + def __init__(self, *args, **kwargs) -> None: + super().__init__(*args, **kwargs) + + self._mode: str = self._pairlistconfig.get("mode", "whitelist") + 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" + + @property + def needstickers(self) -> bool: + """ + Boolean property defining if tickers are necessary. + If no Pairlist requires tickers, an empty Dict is passed + as tickers argument to filter_pairlist + """ + return False + + def short_desc(self) -> str: + """ + Short whitelist method description - used for startup-messages + """ + mode = self._mode + target_mode = self._target_mode + msg = f"{self.name} - {mode.capitalize()} pairs that exists on {target_mode} market." + return msg + + @staticmethod + def description() -> str: + return "Filter pairs if they exist on another market." + + @staticmethod + def available_parameters() -> dict[str, PairlistParameter]: + return { + "mode": { + "type": "option", + "default": "whitelist", + "options": ["whitelist", "blacklist"], + "description": "Mode of operation", + "help": "Mode of operation (whitelist/blacklist)", + }, + } + + def get_base_list(self): + target_mode = self._target_mode + spot_only = True if target_mode == "spot" else False + futures_only = True if target_mode == "futures" else False + bases = [ + v.get("base", "") + for k, v in self._exchange.get_markets( + quote_currencies=[self._stake_currency], + tradable_only=False, + active_only=True, + spot_only=spot_only, + futures_only=futures_only, + ).items() + ] + return bases + + prefixes = ("1000", "1000000", "1M", "K", "M") + + def filter_pairlist(self, pairlist: list[str], tickers: Tickers) -> list[str]: + bases = self.get_base_list() + is_whitelist_mode = self._mode == "whitelist" + whitelisted_pairlist: list[str] = [] + filtered_pairlist = pairlist.copy() + + for pair in pairlist: + base = self._exchange.get_pair_base_currency(pair) + found_in_bases = base in bases + if not found_in_bases: + for prefix in self.prefixes: + test_prefix = f"{prefix}{base}" + if test_prefix in bases: + found_in_bases = True + break + if found_in_bases: + whitelisted_pairlist.append(pair) + filtered_pairlist.remove(pair) + + return whitelisted_pairlist if is_whitelist_mode else filtered_pairlist