Skip to content

Commit bb1d34c

Browse files
authored
Merge pull request freqtrade#12077 from mrpabloyeah/allow-pairs-with-prefix-in-marketcap-pairList
Allow pairs with prefix in MarketCapPairList
2 parents 2824bcf + 8f6d64f commit bb1d34c

File tree

3 files changed

+96
-14
lines changed

3 files changed

+96
-14
lines changed

docs/includes/pairlists.md

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -389,6 +389,8 @@ The `refresh_period` setting defines the interval (in seconds) at which the mark
389389
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.
390390
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.
391391

392+
Coins like 1000PEPE/USDT or KPEPE/USDT:USDT are detected on a best effort basis, with the prefixes `1000` and `K` being used to identify them.
393+
392394
!!! Warning "Many categories"
393395
Each added category corresponds to one API call to CoinGecko. The more categories you add, the longer the pairlist generation will take, potentially causing rate limit issues.
394396

freqtrade/plugins/pairlist/MarketCapPairList.py

Lines changed: 47 additions & 12 deletions
Original file line numberDiff line numberDiff line change
@@ -117,6 +117,16 @@ def available_parameters() -> dict[str, PairlistParameter]:
117117
},
118118
}
119119

120+
def get_markets_exchange(self):
121+
markets = [
122+
k
123+
for k in self._exchange.get_markets(
124+
quote_currencies=[self._stake_currency], tradable_only=True, active_only=True
125+
).keys()
126+
]
127+
128+
return markets
129+
120130
def gen_pairlist(self, tickers: Tickers) -> list[str]:
121131
"""
122132
Generate the pairlist
@@ -132,12 +142,8 @@ def gen_pairlist(self, tickers: Tickers) -> list[str]:
132142
else:
133143
# Use fresh pairlist
134144
# Check if pair quote currency equals to the stake currency.
135-
_pairlist = [
136-
k
137-
for k in self._exchange.get_markets(
138-
quote_currencies=[self._stake_currency], tradable_only=True, active_only=True
139-
).keys()
140-
]
145+
_pairlist = self.get_markets_exchange()
146+
141147
# No point in testing for blacklisted pairs...
142148
_pairlist = self.verify_blacklist(_pairlist, logger.info)
143149

@@ -146,6 +152,31 @@ def gen_pairlist(self, tickers: Tickers) -> list[str]:
146152

147153
return pairlist
148154

155+
# Prefixes to test to discover coins like 1000PEPE/USDDT:USDT or KPEPE/USDC (hyperliquid)
156+
prefixes = ("1000", "K")
157+
158+
def resolve_marketcap_pair(
159+
self,
160+
pair: str,
161+
pairlist: list[str],
162+
markets: list[str],
163+
filtered_pairlist: list[str],
164+
) -> str | None:
165+
if pair in filtered_pairlist:
166+
return None
167+
168+
if pair in pairlist:
169+
return pair
170+
171+
if pair not in markets:
172+
for prefix in self.prefixes:
173+
test_prefix = f"{prefix}{pair}"
174+
175+
if test_prefix in pairlist:
176+
return test_prefix
177+
178+
return None
179+
149180
def filter_pairlist(self, pairlist: list[str], tickers: dict) -> list[str]:
150181
"""
151182
Filters and sorts pairlist and returns the whitelist again.
@@ -188,21 +219,25 @@ def filter_pairlist(self, pairlist: list[str], tickers: dict) -> list[str]:
188219
self._marketcap_cache["marketcap"] = marketcap_list
189220

190221
if marketcap_list:
191-
filtered_pairlist = []
222+
filtered_pairlist: list[str] = []
192223

193224
market = self._exchange._config["trading_mode"]
194225
pair_format = f"{self._stake_currency.upper()}"
195226
if market == "futures":
196227
pair_format += f":{self._stake_currency.upper()}"
197228

198229
top_marketcap = marketcap_list[: self._max_rank :]
230+
markets = self.get_markets_exchange()
199231

200232
for mc_pair in top_marketcap:
201-
test_pair = f"{mc_pair.upper()}/{pair_format}"
202-
if test_pair in pairlist and test_pair not in filtered_pairlist:
203-
filtered_pairlist.append(test_pair)
204-
if len(filtered_pairlist) == self._number_assets:
205-
break
233+
pair = f"{mc_pair.upper()}/{pair_format}"
234+
resolved = self.resolve_marketcap_pair(pair, pairlist, markets, filtered_pairlist)
235+
236+
if resolved:
237+
filtered_pairlist.append(resolved)
238+
239+
if len(filtered_pairlist) == self._number_assets:
240+
break
206241

207242
if len(filtered_pairlist) > 0:
208243
return filtered_pairlist

tests/plugins/test_pairlist.py

Lines changed: 47 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -2409,7 +2409,7 @@ def test_MarketCapPairList_timing(mocker, default_conf_usdt, markets, time_machi
24092409
pm = PairListManager(exchange, default_conf_usdt)
24102410
markets_mock.reset_mock()
24112411
pm.refresh_pairlist()
2412-
assert markets_mock.call_count == 3
2412+
assert markets_mock.call_count == 4
24132413
markets_mock.reset_mock()
24142414

24152415
time_machine.move_to(start_dt + timedelta(hours=20))
@@ -2421,7 +2421,52 @@ def test_MarketCapPairList_timing(mocker, default_conf_usdt, markets, time_machi
24212421
time_machine.move_to(start_dt + timedelta(days=2))
24222422
pm.refresh_pairlist()
24232423
# No longer cached pairlist ...
2424-
assert markets_mock.call_count == 3
2424+
assert markets_mock.call_count == 4
2425+
2426+
2427+
def test_MarketCapPairList_1000_K_fillup(mocker, default_conf_usdt, markets, time_machine):
2428+
test_value = [
2429+
{"symbol": "btc"},
2430+
{"symbol": "eth"},
2431+
{"symbol": "usdt"},
2432+
{"symbol": "bnb"},
2433+
{"symbol": "sol"},
2434+
{"symbol": "xrp"},
2435+
{"symbol": "usdc"},
2436+
{"symbol": "steth"},
2437+
{"symbol": "ada"},
2438+
{"symbol": "avax"},
2439+
]
2440+
2441+
default_conf_usdt["trading_mode"] = "spot"
2442+
default_conf_usdt["exchange"]["pair_whitelist"] = []
2443+
default_conf_usdt["pairlists"] = [{"method": "MarketCapPairList", "number_assets": 3}]
2444+
markets["1000ETH/USDT"] = markets["ETH/USDT"]
2445+
markets["KXRP/USDT"] = markets["XRP/USDT"]
2446+
del markets["ETH/USDT"]
2447+
del markets["XRP/USDT"]
2448+
2449+
markets_mock = MagicMock(return_value=markets)
2450+
mocker.patch.multiple(
2451+
EXMS,
2452+
get_markets=markets_mock,
2453+
exchange_has=MagicMock(return_value=True),
2454+
)
2455+
2456+
mocker.patch(
2457+
"freqtrade.plugins.pairlist.MarketCapPairList.FtCoinGeckoApi.get_coins_markets",
2458+
return_value=test_value,
2459+
)
2460+
2461+
start_dt = dt_now()
2462+
2463+
exchange = get_patched_exchange(mocker, default_conf_usdt)
2464+
time_machine.move_to(start_dt)
2465+
2466+
pm = PairListManager(exchange, default_conf_usdt)
2467+
markets_mock.reset_mock()
2468+
pm.refresh_pairlist()
2469+
assert pm.whitelist == ["BTC/USDT", "1000ETH/USDT", "KXRP/USDT"]
24252470

24262471

24272472
def test_MarketCapPairList_filter_special_no_pair_from_coingecko(

0 commit comments

Comments
 (0)