Merge pull request #12448 from stash86/main-stash

Add blacklist mode to MarketcapPairlist
This commit is contained in:
Matthias
2025-11-06 07:30:37 +01:00
committed by GitHub
3 changed files with 66 additions and 16 deletions

View File

@@ -367,7 +367,7 @@ The optional `bearer_token` will be included in the requests Authorization Heade
#### MarketCapPairList
`MarketCapPairList` employs sorting/filtering of pairs by their marketcap rank based of CoinGecko. The returned pairlist will be sorted based of their marketcap ranks.
`MarketCapPairList` employs sorting/filtering of pairs by their marketcap rank based of CoinGecko. The returned pairlist will be sorted based of their marketcap ranks if used in whitelist `mode`.
```json
"pairlists": [
@@ -376,16 +376,21 @@ The optional `bearer_token` will be included in the requests Authorization Heade
"number_assets": 20,
"max_rank": 50,
"refresh_period": 86400,
"mode": "whitelist",
"categories": ["layer-1"]
}
]
```
`number_assets` defines the maximum number of pairs returned by the pairlist. `max_rank` will determine the maximum rank used in creating/filtering the pairlist. It's expected that some coins within the top `max_rank` marketcap will not be included in the resulting pairlist since not all pairs will have active trading pairs in your preferred market/stake/exchange combination.
`number_assets` defines the maximum number of pairs returned by the pairlist if used in whitelist `mode`. In blacklist `mode`, this setting will be ignored.
`max_rank` will determine the maximum rank used in creating/filtering the pairlist. It's expected that some coins within the top `max_rank` marketcap will not be included in the resulting pairlist since not all pairs will have active trading pairs in your preferred market/stake/exchange combination.
While using a `max_rank` bigger than 250 is supported, it's not recommended, as it'll cause multiple API calls to CoinGecko, which can lead to rate limit issues.
The `refresh_period` setting defines the interval (in seconds) at which the marketcap rank data will be refreshed. The default is 86,400 seconds (1 day). The pairlist cache (`refresh_period`) applies to both generating pairlists (when in the first position in the list) and filtering instances (when not in the first position in the list).
The `mode` setting defines whether the plugin will filters in (whitelist `mode`) or filters out (blacklist `mode`) top marketcap ranked coins. By default, the plugin will be in whitelist mode.
The `categories` setting specifies the [coingecko categories](https://www.coingecko.com/en/categories) from which to select coins from. The default is an empty list `[]`, meaning no category filtering is applied.
If an incorrect category string is chosen, the plugin will print the available categories from CoinGecko and fail. The category should be the ID of the category, for example, for `https://www.coingecko.com/en/categories/layer-1`, the category ID would be `layer-1`. You can pass multiple categories such as `["layer-1", "meme-token"]` to select from several categories.

View File

@@ -25,14 +25,16 @@ class MarketCapPairList(IPairList):
def __init__(self, *args, **kwargs) -> None:
super().__init__(*args, **kwargs)
if "number_assets" not in self._pairlistconfig:
self._mode = self._pairlistconfig.get("mode", "whitelist")
if (self._mode == "whitelist") and ("number_assets" not in self._pairlistconfig):
raise OperationalException(
"`number_assets` not specified. Please check your configuration "
'for "pairlist.config.number_assets"'
)
self._stake_currency = self._config["stake_currency"]
self._number_assets = self._pairlistconfig["number_assets"]
self._number_assets = self._pairlistconfig.get("number_assets", 30)
self._max_rank = self._pairlistconfig.get("max_rank", 30)
self._refresh_period = self._pairlistconfig.get("refresh_period", 86400)
self._categories = self._pairlistconfig.get("categories", [])
@@ -78,7 +80,9 @@ class MarketCapPairList(IPairList):
"""
num = self._number_assets
rank = self._max_rank
msg = f"{self.name} - {num} pairs placed within top {rank} market cap."
mode = self._mode
pair_text = num if (mode == "whitelist") else "blacklisting"
msg = f"{self.name} - {pair_text} pairs placed within top {rank} market cap."
return msg
@staticmethod
@@ -115,6 +119,13 @@ class MarketCapPairList(IPairList):
"description": "Refresh period",
"help": "Refresh period in seconds",
},
"mode": {
"type": "option",
"default": "whitelist",
"options": ["whitelist", "blacklist"],
"description": "Mode of operation",
"help": "Mode of operation (whitelist/blacklist)",
},
}
def get_markets_exchange(self):
@@ -186,6 +197,9 @@ class MarketCapPairList(IPairList):
:return: new whitelist
"""
marketcap_list = self._marketcap_cache.get("marketcap")
mode = self._mode
is_whitelist_mode = mode == "whitelist"
filtered_pairlist: list[str] = []
default_kwargs = {
"vs_currency": "usd",
@@ -219,12 +233,10 @@ class MarketCapPairList(IPairList):
self._marketcap_cache["marketcap"] = marketcap_list
if marketcap_list:
filtered_pairlist: list[str] = []
market = self._exchange._config["trading_mode"]
pair_format = f"{self._stake_currency.upper()}"
if market == "futures":
pair_format += f":{self._stake_currency.upper()}"
pair_format = f"{self._stake_currency.upper()}" + (
f":{self._stake_currency.upper()}" if market == "futures" else ""
)
top_marketcap = marketcap_list[: self._max_rank :]
markets = self.get_markets_exchange()
@@ -234,13 +246,16 @@ class MarketCapPairList(IPairList):
resolved = self.resolve_marketcap_pair(pair, pairlist, markets, filtered_pairlist)
if resolved:
if not is_whitelist_mode:
pairlist.remove(resolved)
continue
filtered_pairlist.append(resolved)
if len(filtered_pairlist) == self._number_assets:
break
if len(filtered_pairlist) == self._number_assets:
break
if len(filtered_pairlist) > 0:
return filtered_pairlist
if not is_whitelist_mode:
return pairlist
# If no pairs are found, return the original pairlist
return []
return filtered_pairlist

View File

@@ -2334,6 +2334,36 @@ def test_FullTradesFilter(mocker, default_conf_usdt, fee, caplog) -> None:
["ETH/USDT:USDT", "ADA/USDT:USDT"],
["layer-1", "protocol"],
),
(
[
# Blacklist high MC pairs
{"method": "StaticPairList", "allow_inactive": True},
{"method": "MarketCapPairList", "mode": "blacklist"},
],
"spot",
["LTC/USDT", "NEO/USDT", "TKN/USDT", "ETC/USDT"],
1,
),
(
[
# Blacklist high MC pairs
{"method": "StaticPairList", "allow_inactive": True},
{"method": "MarketCapPairList", "mode": "blacklist", "max_rank": 2},
],
"spot",
["LTC/USDT", "XRP/USDT", "NEO/USDT", "TKN/USDT", "ETC/USDT", "ADA/USDT"],
1,
),
(
[
# Blacklist top 6 MarketCap pairs - removes XRP which is at spot 6.
{"method": "StaticPairList", "allow_inactive": True},
{"method": "MarketCapPairList", "mode": "blacklist", "max_rank": 6},
],
"spot",
["LTC/USDT", "NEO/USDT", "TKN/USDT", "ETC/USDT", "ADA/USDT"],
1,
),
],
)
def test_MarketCapPairList_filter(