diff --git a/docs/includes/pairlists.md b/docs/includes/pairlists.md index 2a5540b00..604851aa5 100644 --- a/docs/includes/pairlists.md +++ b/docs/includes/pairlists.md @@ -2,7 +2,7 @@ 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), [`CrossMarketPairlist`](#crossmarketpairlist), [`MarketCapPairlist`](#marketcappairlist) 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), [`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. @@ -26,7 +26,7 @@ You may also use something like `.*DOWN/BTC` or `.*UP/BTC` to exclude leveraged * [`ProducerPairList`](#producerpairlist) * [`RemotePairList`](#remotepairlist) * [`MarketCapPairList`](#marketcappairlist) -* [`CrossMarketPairlist`](#crossmarketpairlist) +* [`CrossMarketPairList`](#CrossMarketPairList) * [`AgeFilter`](#agefilter) * [`DelistFilter`](#delistfilter) * [`FullTradesFilter`](#fulltradesfilter) @@ -403,7 +403,7 @@ 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 +#### 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. diff --git a/freqtrade/constants.py b/freqtrade/constants.py index 659b90f72..3182a9d76 100644 --- a/freqtrade/constants.py +++ b/freqtrade/constants.py @@ -61,7 +61,7 @@ AVAILABLE_PAIRLISTS = [ "ProducerPairList", "RemotePairList", "MarketCapPairList", - "CrossMarketPairlist", + "CrossMarketPairList", "AgeFilter", "DelistFilter", "FullTradesFilter", diff --git a/freqtrade/plugins/pairlist/CrossMarketPairlist.py b/freqtrade/plugins/pairlist/CrossMarketPairList.py similarity index 85% rename from freqtrade/plugins/pairlist/CrossMarketPairlist.py rename to freqtrade/plugins/pairlist/CrossMarketPairList.py index 2a3cd6615..340ce4757 100644 --- a/freqtrade/plugins/pairlist/CrossMarketPairlist.py +++ b/freqtrade/plugins/pairlist/CrossMarketPairList.py @@ -4,6 +4,7 @@ Price pair list filter import logging +from freqtrade.constants import PairPrefixes from freqtrade.exchange.exchange_types import Tickers from freqtrade.plugins.pairlist.IPairList import IPairList, PairlistParameter, SupportsBacktesting from freqtrade.util import FtTTLCache @@ -12,7 +13,7 @@ from freqtrade.util import FtTTLCache logger = logging.getLogger(__name__) -class CrossMarketPairlist(IPairList): +class CrossMarketPairList(IPairList): supports_backtesting = SupportsBacktesting.BIASED def __init__(self, *args, **kwargs) -> None: @@ -76,8 +77,6 @@ class CrossMarketPairlist(IPairList): ] return bases - prefixes = ("1000", "1000000", "1M", "K", "M") - def gen_pairlist(self, tickers: Tickers) -> list[str]: """ Generate the pairlist @@ -115,21 +114,26 @@ class CrossMarketPairlist(IPairList): for pair in pairlist: base = self._exchange.get_pair_base_currency(pair) + if not base: + filtered_pairlist.remove(pair) + continue 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 + for prefix in PairPrefixes: + # Check in case of PEPE needs to be changed into 1000PEPE for example test_prefix = f"{prefix}{base}" 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 + # Avoid false positive since there are KAVA and AVA pairs, which aren't related + if prefix != "K": + # Check in case of 1000PEPE needs to be changed into 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) diff --git a/tests/plugins/test_pairlist.py b/tests/plugins/test_pairlist.py index e3b63bdb3..60598ee24 100644 --- a/tests/plugins/test_pairlist.py +++ b/tests/plugins/test_pairlist.py @@ -2584,6 +2584,107 @@ def test_MarketCapPairList_exceptions(mocker, default_conf_usdt, caplog): PairListManager(exchange, default_conf_usdt) +@pytest.mark.parametrize( + "pairlists,trade_mode,result", + [ + ( + [ + # Whitelist mode on spot + {"method": "StaticPairList", "allow_inactive": True}, + {"method": "CrossMarketPairList", "mode": "whitelist"}, + ], + "spot", + ["ETH/USDT"], + ), + ( + [ + # Blacklist mode on spot + {"method": "StaticPairList", "allow_inactive": True}, + {"method": "CrossMarketPairList", "mode": "blacklist"}, + ], + "spot", + ["LTC/USDT", "XRP/USDT", "NEO/USDT", "TKN/USDT", "BTC/USDT"], + ), + ( + [ + # Whitelist mode on futures + {"method": "StaticPairList", "allow_inactive": True}, + {"method": "CrossMarketPairList", "mode": "whitelist"}, + ], + "futures", + ["ETH/USDT:USDT"], + ), + ( + [ + # Blacklist mode on futures + {"method": "StaticPairList", "allow_inactive": True}, + {"method": "CrossMarketPairList", "mode": "blacklist"}, + ], + "futures", + ["ADA/USDT:USDT"], + ), + ( + [ + # CrossMarketPairList as generator, whitelist mode, spot market + {"method": "CrossMarketPairList", "mode": "whitelist"}, + ], + "spot", + ["ETH/USDT"], + ), + ( + [ + # CrossMarketPairList as generator, blacklist mode, spot market + {"method": "CrossMarketPairList", "mode": "blacklist"}, + ], + "spot", + ["BTC/USDT", "XRP/USDT", "NEO/USDT", "TKN/USDT"], + ), + ( + [ + # CrossMarketPairList as generator, whitelist mode, futures market + {"method": "CrossMarketPairList", "mode": "whitelist"}, + ], + "futures", + ["ETH/USDT:USDT"], + ), + ( + [ + # CrossMarketPairList as generator, blacklist mode, futures market + {"method": "CrossMarketPairList", "mode": "blacklist"}, + ], + "futures", + ["ADA/USDT:USDT"], + ), + ], +) +def test_CrossMarketPairlist_filter( + mocker, default_conf_usdt, trade_mode, markets, pairlists, result +): + default_conf_usdt["trading_mode"] = trade_mode + if trade_mode == "spot": + default_conf_usdt["exchange"]["pair_whitelist"].extend(["BTC/USDT", "ETC/USDT", "ADA/USDT"]) + else: + default_conf_usdt["exchange"]["pair_whitelist"] = [ + "BTC/USDT:USDT", + "ETH/USDT:USDT", + "ETC/USDT:USDT", + "ADA/USDT:USDT", + ] + default_conf_usdt["pairlists"] = pairlists + mocker.patch.multiple( + EXMS, + markets=PropertyMock(return_value=markets), + exchange_has=MagicMock(return_value=True), + ) + + exchange = get_patched_exchange(mocker, default_conf_usdt) + + pm = PairListManager(exchange, default_conf_usdt) + pm.refresh_pairlist() + + assert pm.whitelist == result + + @pytest.mark.parametrize( "pairlists,expected_error,expected_warning", [