add tests

This commit is contained in:
Stefano
2026-02-28 10:58:34 +09:00
parent b94b0926a7
commit 991a1b1ab7
4 changed files with 120 additions and 15 deletions

View File

@@ -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.

View File

@@ -61,7 +61,7 @@ AVAILABLE_PAIRLISTS = [
"ProducerPairList",
"RemotePairList",
"MarketCapPairList",
"CrossMarketPairlist",
"CrossMarketPairList",
"AgeFilter",
"DelistFilter",
"FullTradesFilter",

View File

@@ -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)

View File

@@ -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",
[