From dc26d0d7ba421cf95e00968a6cf786d5c8224474 Mon Sep 17 00:00:00 2001 From: "Jakub Werner (jakubikan)" Date: Mon, 16 Sep 2024 22:50:08 +0200 Subject: [PATCH 01/18] adding category for MarketCapPairList.py --- freqtrade/plugins/pairlist/MarketCapPairList.py | 15 +++++++++++++-- 1 file changed, 13 insertions(+), 2 deletions(-) diff --git a/freqtrade/plugins/pairlist/MarketCapPairList.py b/freqtrade/plugins/pairlist/MarketCapPairList.py index 95f0e2805..e865a2ea5 100644 --- a/freqtrade/plugins/pairlist/MarketCapPairList.py +++ b/freqtrade/plugins/pairlist/MarketCapPairList.py @@ -35,6 +35,7 @@ class MarketCapPairList(IPairList): self._number_assets = self._pairlistconfig["number_assets"] self._max_rank = self._pairlistconfig.get("max_rank", 30) self._refresh_period = self._pairlistconfig.get("refresh_period", 86400) + self._category = self._pairlistconfig.get("category", None) self._marketcap_cache: TTLCache = TTLCache(maxsize=1, ttl=self._refresh_period) self._def_candletype = self._config["candle_type_def"] @@ -85,6 +86,12 @@ class MarketCapPairList(IPairList): "description": "Max rank of assets", "help": "Maximum rank of assets to use from the pairlist", }, + "category": { + "type": "string", + "default": None, + "description": "The Category", + "help": "Th Category of the coin e.g layer-1 default None", + }, "refresh_period": { "type": "number", "default": 86400, @@ -133,6 +140,9 @@ class MarketCapPairList(IPairList): marketcap_list = self._marketcap_cache.get("marketcap") if marketcap_list is None: + # categories = self._coingecko.get_coins_categories() + # print([cat['id'] for cat in categories]) + data = self._coingecko.get_coins_markets( vs_currency="usd", order="market_cap_desc", @@ -140,6 +150,7 @@ class MarketCapPairList(IPairList): page="1", sparkline="false", locale="en", + **({"category": self._category} if self._category else {}) ) if data: marketcap_list = [row["symbol"] for row in data] @@ -153,11 +164,11 @@ class MarketCapPairList(IPairList): if market == "futures": pair_format += f":{self._stake_currency.upper()}" - top_marketcap = marketcap_list[: self._max_rank :] + top_marketcap = marketcap_list[: self._max_rank:] for mc_pair in top_marketcap: test_pair = f"{mc_pair.upper()}/{pair_format}" - if test_pair in pairlist: + if test_pair in pairlist and test_pair not in filtered_pairlist: filtered_pairlist.append(test_pair) if len(filtered_pairlist) == self._number_assets: break From 92af01b0cba577a7877d236025786cc039c894b5 Mon Sep 17 00:00:00 2001 From: "Jakub Werner (jakubikan)" Date: Mon, 16 Sep 2024 22:51:42 +0200 Subject: [PATCH 02/18] adding category for MarketCapPairList.py --- freqtrade/plugins/pairlist/MarketCapPairList.py | 9 ++++++--- 1 file changed, 6 insertions(+), 3 deletions(-) diff --git a/freqtrade/plugins/pairlist/MarketCapPairList.py b/freqtrade/plugins/pairlist/MarketCapPairList.py index e865a2ea5..25e0c21b7 100644 --- a/freqtrade/plugins/pairlist/MarketCapPairList.py +++ b/freqtrade/plugins/pairlist/MarketCapPairList.py @@ -34,9 +34,11 @@ class MarketCapPairList(IPairList): self._stake_currency = self._config["stake_currency"] self._number_assets = self._pairlistconfig["number_assets"] self._max_rank = self._pairlistconfig.get("max_rank", 30) - self._refresh_period = self._pairlistconfig.get("refresh_period", 86400) + self._refresh_period = self._pairlistconfig.get( + "refresh_period", 86400) self._category = self._pairlistconfig.get("category", None) - self._marketcap_cache: TTLCache = TTLCache(maxsize=1, ttl=self._refresh_period) + self._marketcap_cache: TTLCache = TTLCache( + maxsize=1, ttl=self._refresh_period) self._def_candletype = self._config["candle_type_def"] _coingecko_config = self._config.get("coingecko", {}) @@ -47,7 +49,8 @@ class MarketCapPairList(IPairList): ) if self._max_rank > 250: - raise OperationalException("This filter only support marketcap rank up to 250.") + raise OperationalException( + "This filter only support marketcap rank up to 250.") @property def needstickers(self) -> bool: From 0b7cb2a1a81c575885e4352b91a40d17d8f1c944 Mon Sep 17 00:00:00 2001 From: "Jakub Werner (jakubikan)" Date: Mon, 16 Sep 2024 22:52:26 +0200 Subject: [PATCH 03/18] cleanup --- freqtrade/plugins/pairlist/MarketCapPairList.py | 2 -- 1 file changed, 2 deletions(-) diff --git a/freqtrade/plugins/pairlist/MarketCapPairList.py b/freqtrade/plugins/pairlist/MarketCapPairList.py index 25e0c21b7..05f2e6a0a 100644 --- a/freqtrade/plugins/pairlist/MarketCapPairList.py +++ b/freqtrade/plugins/pairlist/MarketCapPairList.py @@ -143,8 +143,6 @@ class MarketCapPairList(IPairList): marketcap_list = self._marketcap_cache.get("marketcap") if marketcap_list is None: - # categories = self._coingecko.get_coins_categories() - # print([cat['id'] for cat in categories]) data = self._coingecko.get_coins_markets( vs_currency="usd", From 03ee3aaf40b4bee8e2724574e559bf21a81f4d44 Mon Sep 17 00:00:00 2001 From: "Jakub Werner (jakubikan)" Date: Tue, 17 Sep 2024 22:35:00 +0200 Subject: [PATCH 04/18] adding category list if the category is not from the category --- freqtrade/plugins/pairlist/MarketCapPairList.py | 16 +++++++++------- 1 file changed, 9 insertions(+), 7 deletions(-) diff --git a/freqtrade/plugins/pairlist/MarketCapPairList.py b/freqtrade/plugins/pairlist/MarketCapPairList.py index 05f2e6a0a..738081d46 100644 --- a/freqtrade/plugins/pairlist/MarketCapPairList.py +++ b/freqtrade/plugins/pairlist/MarketCapPairList.py @@ -14,7 +14,6 @@ from freqtrade.exchange.exchange_types import Tickers from freqtrade.plugins.pairlist.IPairList import IPairList, PairlistParameter, SupportsBacktesting from freqtrade.util.coin_gecko import FtCoinGeckoApi - logger = logging.getLogger(__name__) @@ -34,11 +33,9 @@ class MarketCapPairList(IPairList): self._stake_currency = self._config["stake_currency"] self._number_assets = self._pairlistconfig["number_assets"] self._max_rank = self._pairlistconfig.get("max_rank", 30) - self._refresh_period = self._pairlistconfig.get( - "refresh_period", 86400) + self._refresh_period = self._pairlistconfig.get("refresh_period", 86400) self._category = self._pairlistconfig.get("category", None) - self._marketcap_cache: TTLCache = TTLCache( - maxsize=1, ttl=self._refresh_period) + self._marketcap_cache: TTLCache = TTLCache(maxsize=1, ttl=self._refresh_period) self._def_candletype = self._config["candle_type_def"] _coingecko_config = self._config.get("coingecko", {}) @@ -48,9 +45,14 @@ class MarketCapPairList(IPairList): is_demo=_coingecko_config.get("is_demo", True), ) + categories = self._coingecko.get_coins_categories_list() + category_ids = [cat['category_id'] for cat in categories] + + if self._category not in category_ids: + raise OperationalException(f"category not in coingecko category list you can choose from {category_ids}") + if self._max_rank > 250: - raise OperationalException( - "This filter only support marketcap rank up to 250.") + raise OperationalException("This filter only support marketcap rank up to 250.") @property def needstickers(self) -> bool: From 660623181a00d5677a0414ef3360c5fe7dfd510c Mon Sep 17 00:00:00 2001 From: "Jakub Werner (jakubikan)" Date: Tue, 17 Sep 2024 22:36:21 +0200 Subject: [PATCH 05/18] adding category list if the category is not from the category --- freqtrade/plugins/pairlist/MarketCapPairList.py | 12 +++++++----- 1 file changed, 7 insertions(+), 5 deletions(-) diff --git a/freqtrade/plugins/pairlist/MarketCapPairList.py b/freqtrade/plugins/pairlist/MarketCapPairList.py index 738081d46..72ef939d5 100644 --- a/freqtrade/plugins/pairlist/MarketCapPairList.py +++ b/freqtrade/plugins/pairlist/MarketCapPairList.py @@ -14,6 +14,7 @@ from freqtrade.exchange.exchange_types import Tickers from freqtrade.plugins.pairlist.IPairList import IPairList, PairlistParameter, SupportsBacktesting from freqtrade.util.coin_gecko import FtCoinGeckoApi + logger = logging.getLogger(__name__) @@ -46,10 +47,12 @@ class MarketCapPairList(IPairList): ) categories = self._coingecko.get_coins_categories_list() - category_ids = [cat['category_id'] for cat in categories] + category_ids = [cat["category_id"] for cat in categories] if self._category not in category_ids: - raise OperationalException(f"category not in coingecko category list you can choose from {category_ids}") + raise OperationalException( + f"category not in coingecko category list you can choose from {category_ids}" + ) if self._max_rank > 250: raise OperationalException("This filter only support marketcap rank up to 250.") @@ -145,7 +148,6 @@ class MarketCapPairList(IPairList): marketcap_list = self._marketcap_cache.get("marketcap") if marketcap_list is None: - data = self._coingecko.get_coins_markets( vs_currency="usd", order="market_cap_desc", @@ -153,7 +155,7 @@ class MarketCapPairList(IPairList): page="1", sparkline="false", locale="en", - **({"category": self._category} if self._category else {}) + **({"category": self._category} if self._category else {}), ) if data: marketcap_list = [row["symbol"] for row in data] @@ -167,7 +169,7 @@ class MarketCapPairList(IPairList): if market == "futures": pair_format += f":{self._stake_currency.upper()}" - top_marketcap = marketcap_list[: self._max_rank:] + top_marketcap = marketcap_list[: self._max_rank :] for mc_pair in top_marketcap: test_pair = f"{mc_pair.upper()}/{pair_format}" From 50f07e7b1116fa13b9c9983487e2040c695c266e Mon Sep 17 00:00:00 2001 From: "Jakub Werner (jakubikan)" Date: Tue, 17 Sep 2024 23:03:51 +0200 Subject: [PATCH 06/18] only doing this if the category is set --- freqtrade/plugins/pairlist/MarketCapPairList.py | 15 ++++++++------- 1 file changed, 8 insertions(+), 7 deletions(-) diff --git a/freqtrade/plugins/pairlist/MarketCapPairList.py b/freqtrade/plugins/pairlist/MarketCapPairList.py index 72ef939d5..96edf81b5 100644 --- a/freqtrade/plugins/pairlist/MarketCapPairList.py +++ b/freqtrade/plugins/pairlist/MarketCapPairList.py @@ -46,13 +46,14 @@ class MarketCapPairList(IPairList): is_demo=_coingecko_config.get("is_demo", True), ) - categories = self._coingecko.get_coins_categories_list() - category_ids = [cat["category_id"] for cat in categories] + if self._category: + categories = self._coingecko.get_coins_categories_list() + category_ids = [cat["category_id"] for cat in categories] - if self._category not in category_ids: - raise OperationalException( - f"category not in coingecko category list you can choose from {category_ids}" - ) + if self._category not in category_ids: + raise OperationalException( + f"category not in coingecko category list you can choose from {category_ids}" + ) if self._max_rank > 250: raise OperationalException("This filter only support marketcap rank up to 250.") @@ -173,7 +174,7 @@ class MarketCapPairList(IPairList): for mc_pair in top_marketcap: test_pair = f"{mc_pair.upper()}/{pair_format}" - if test_pair in pairlist and test_pair not in filtered_pairlist: + if test_pair in pairlist: filtered_pairlist.append(test_pair) if len(filtered_pairlist) == self._number_assets: break From 0dbe507b26ffc45ec2d4f07ff41720781c9873d9 Mon Sep 17 00:00:00 2001 From: "Jakub Werner (jakubikan)" Date: Wed, 25 Sep 2024 21:11:52 +0200 Subject: [PATCH 07/18] making list of categories available --- freqtrade/plugins/pairlist/IPairList.py | 20 +++--- .../plugins/pairlist/MarketCapPairList.py | 63 ++++++++++++------- 2 files changed, 52 insertions(+), 31 deletions(-) diff --git a/freqtrade/plugins/pairlist/IPairList.py b/freqtrade/plugins/pairlist/IPairList.py index 755f52b06..7be86df7e 100644 --- a/freqtrade/plugins/pairlist/IPairList.py +++ b/freqtrade/plugins/pairlist/IPairList.py @@ -38,6 +38,11 @@ class __OptionPairlistParameter(__PairlistParameterBase): default: Union[str, None] options: List[str] +class __ListPairListParamenter(__PairlistParameterBase): + type: Literal["list"] + default: Union[List[str], None] + options: List[str] + class __BoolPairlistParameter(__PairlistParameterBase): type: Literal["boolean"] @@ -49,6 +54,7 @@ PairlistParameter = Union[ __StringPairlistParameter, __OptionPairlistParameter, __BoolPairlistParameter, + __ListPairListParamenter ] @@ -68,12 +74,12 @@ class IPairList(LoggingMixin, ABC): supports_backtesting: SupportsBacktesting = SupportsBacktesting.NO def __init__( - self, - exchange: Exchange, - pairlistmanager, - config: Config, - pairlistconfig: Dict[str, Any], - pairlist_pos: int, + self, + exchange: Exchange, + pairlistmanager, + config: Config, + pairlistconfig: Dict[str, Any], + pairlist_pos: int, ) -> None: """ :param exchange: Exchange instance @@ -213,7 +219,7 @@ class IPairList(LoggingMixin, ABC): return self._pairlistmanager.verify_blacklist(pairlist, logmethod) def verify_whitelist( - self, pairlist: List[str], logmethod, keep_invalid: bool = False + self, pairlist: List[str], logmethod, keep_invalid: bool = False ) -> List[str]: """ Proxy method to verify_whitelist for easy access for child classes. diff --git a/freqtrade/plugins/pairlist/MarketCapPairList.py b/freqtrade/plugins/pairlist/MarketCapPairList.py index 96edf81b5..b9461589d 100644 --- a/freqtrade/plugins/pairlist/MarketCapPairList.py +++ b/freqtrade/plugins/pairlist/MarketCapPairList.py @@ -14,7 +14,6 @@ from freqtrade.exchange.exchange_types import Tickers from freqtrade.plugins.pairlist.IPairList import IPairList, PairlistParameter, SupportsBacktesting from freqtrade.util.coin_gecko import FtCoinGeckoApi - logger = logging.getLogger(__name__) @@ -35,7 +34,7 @@ class MarketCapPairList(IPairList): self._number_assets = self._pairlistconfig["number_assets"] self._max_rank = self._pairlistconfig.get("max_rank", 30) self._refresh_period = self._pairlistconfig.get("refresh_period", 86400) - self._category = self._pairlistconfig.get("category", None) + self._categories = self._pairlistconfig.get("categories", []) self._marketcap_cache: TTLCache = TTLCache(maxsize=1, ttl=self._refresh_period) self._def_candletype = self._config["candle_type_def"] @@ -46,14 +45,14 @@ class MarketCapPairList(IPairList): is_demo=_coingecko_config.get("is_demo", True), ) - if self._category: + if self._categories: categories = self._coingecko.get_coins_categories_list() - category_ids = [cat["category_id"] for cat in categories] + category_ids = [cat['category_id'] for cat in categories] - if self._category not in category_ids: - raise OperationalException( - f"category not in coingecko category list you can choose from {category_ids}" - ) + for category in self._categories: + if category not in category_ids: + raise OperationalException( + f"category not in coingecko category list you can choose from {category_ids}") if self._max_rank > 250: raise OperationalException("This filter only support marketcap rank up to 250.") @@ -95,11 +94,11 @@ class MarketCapPairList(IPairList): "description": "Max rank of assets", "help": "Maximum rank of assets to use from the pairlist", }, - "category": { - "type": "string", - "default": None, - "description": "The Category", - "help": "Th Category of the coin e.g layer-1 default None", + "categories": { + "type": "list", + "default": [], + "description": "The Categories to be set", + "help": "The Category of the coin e.g layer-1 default [] (https://www.coingecko.com/en/categories)", }, "refresh_period": { "type": "number", @@ -148,16 +147,32 @@ class MarketCapPairList(IPairList): """ marketcap_list = self._marketcap_cache.get("marketcap") + default_kwargs = { + "vs_currency": "usd", + "order": "market_cap_desc", + "per_page": "250", + "page": "1", + "sparkline": "false", + "locale": "en", + } + if marketcap_list is None: - data = self._coingecko.get_coins_markets( - vs_currency="usd", - order="market_cap_desc", - per_page="250", - page="1", - sparkline="false", - locale="en", - **({"category": self._category} if self._category else {}), - ) + data = [] + + if not self._categories: + data = self._coingecko.get_coins_markets( + **default_kwargs + ) + else: + for category in self._categories: + category_data = self._coingecko.get_coins_markets( + **default_kwargs, + **({"category": category} if category else {}) + ) + data += category_data + + data.sort(key=lambda d: float(d['market_cap'] or 0.0), reverse=True) + if data: marketcap_list = [row["symbol"] for row in data] self._marketcap_cache["marketcap"] = marketcap_list @@ -170,11 +185,11 @@ class MarketCapPairList(IPairList): if market == "futures": pair_format += f":{self._stake_currency.upper()}" - top_marketcap = marketcap_list[: self._max_rank :] + top_marketcap = marketcap_list[: self._max_rank:] for mc_pair in top_marketcap: test_pair = f"{mc_pair.upper()}/{pair_format}" - if test_pair in pairlist: + if test_pair in pairlist and test_pair not in filtered_pairlist: filtered_pairlist.append(test_pair) if len(filtered_pairlist) == self._number_assets: break From b00ca5470741166f74a03642af5c720fa8c9617e Mon Sep 17 00:00:00 2001 From: "Jakub Werner (jakubikan)" Date: Wed, 25 Sep 2024 21:20:35 +0200 Subject: [PATCH 08/18] adding docu --- docs/includes/pairlists.md | 5 ++++- 1 file changed, 4 insertions(+), 1 deletion(-) diff --git a/docs/includes/pairlists.md b/docs/includes/pairlists.md index 804190e24..fe6a11bc1 100644 --- a/docs/includes/pairlists.md +++ b/docs/includes/pairlists.md @@ -360,7 +360,8 @@ The optional `bearer_token` will be included in the requests Authorization Heade "method": "MarketCapPairList", "number_assets": 20, "max_rank": 50, - "refresh_period": 86400 + "refresh_period": 86400, + "categories": ['layer-1'] } ] ``` @@ -369,6 +370,8 @@ The optional `bearer_token` will be included in the requests Authorization Heade `refresh_period` setting defines the period (in seconds) at which the marketcap rank data will be refreshed. Defaults to 86,400s (1 day). The pairlist cache (`refresh_period`) is applicable on both generating pairlists (first position in the list) and filtering instances (not the first position in the list). +`categories` settings this defines takes the list of coins from a category on coingecko. (https://www.coingecko.com/en/categories). Defaults to []. If you choose a wrong category string the Plugin will print the categories you that you can choose from on coingecko. Category is the id of the category so e.g. https://www.coingecko.com/en/categories/layer-1 -> `layer-1` would be the category. You can pass in a list `["layer-1", "meme-token"]` is possible if you choose to. + #### AgeFilter Removes pairs that have been listed on the exchange for less than `min_days_listed` days (defaults to `10`) or more than `max_days_listed` days (defaults `None` mean infinity). From 514558796b863543ca0161dc89aa5933208d47a2 Mon Sep 17 00:00:00 2001 From: "Jakub Werner (jakubikan)" Date: Wed, 25 Sep 2024 21:21:56 +0200 Subject: [PATCH 09/18] double quotes --- docs/includes/pairlists.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/docs/includes/pairlists.md b/docs/includes/pairlists.md index fe6a11bc1..4a797930b 100644 --- a/docs/includes/pairlists.md +++ b/docs/includes/pairlists.md @@ -361,7 +361,7 @@ The optional `bearer_token` will be included in the requests Authorization Heade "number_assets": 20, "max_rank": 50, "refresh_period": 86400, - "categories": ['layer-1'] + "categories": ["layer-1"] } ] ``` From 8aefae3aff3faa264e4c07215824773e326d881d Mon Sep 17 00:00:00 2001 From: "Jakub Werner (jakubikan)" Date: Wed, 25 Sep 2024 21:22:40 +0200 Subject: [PATCH 10/18] format --- freqtrade/plugins/pairlist/IPairList.py | 17 +++++++++-------- freqtrade/plugins/pairlist/MarketCapPairList.py | 16 +++++++--------- 2 files changed, 16 insertions(+), 17 deletions(-) diff --git a/freqtrade/plugins/pairlist/IPairList.py b/freqtrade/plugins/pairlist/IPairList.py index 7be86df7e..4e566f899 100644 --- a/freqtrade/plugins/pairlist/IPairList.py +++ b/freqtrade/plugins/pairlist/IPairList.py @@ -38,6 +38,7 @@ class __OptionPairlistParameter(__PairlistParameterBase): default: Union[str, None] options: List[str] + class __ListPairListParamenter(__PairlistParameterBase): type: Literal["list"] default: Union[List[str], None] @@ -54,7 +55,7 @@ PairlistParameter = Union[ __StringPairlistParameter, __OptionPairlistParameter, __BoolPairlistParameter, - __ListPairListParamenter + __ListPairListParamenter, ] @@ -74,12 +75,12 @@ class IPairList(LoggingMixin, ABC): supports_backtesting: SupportsBacktesting = SupportsBacktesting.NO def __init__( - self, - exchange: Exchange, - pairlistmanager, - config: Config, - pairlistconfig: Dict[str, Any], - pairlist_pos: int, + self, + exchange: Exchange, + pairlistmanager, + config: Config, + pairlistconfig: Dict[str, Any], + pairlist_pos: int, ) -> None: """ :param exchange: Exchange instance @@ -219,7 +220,7 @@ class IPairList(LoggingMixin, ABC): return self._pairlistmanager.verify_blacklist(pairlist, logmethod) def verify_whitelist( - self, pairlist: List[str], logmethod, keep_invalid: bool = False + self, pairlist: List[str], logmethod, keep_invalid: bool = False ) -> List[str]: """ Proxy method to verify_whitelist for easy access for child classes. diff --git a/freqtrade/plugins/pairlist/MarketCapPairList.py b/freqtrade/plugins/pairlist/MarketCapPairList.py index b9461589d..922d5235c 100644 --- a/freqtrade/plugins/pairlist/MarketCapPairList.py +++ b/freqtrade/plugins/pairlist/MarketCapPairList.py @@ -47,12 +47,13 @@ class MarketCapPairList(IPairList): if self._categories: categories = self._coingecko.get_coins_categories_list() - category_ids = [cat['category_id'] for cat in categories] + category_ids = [cat["category_id"] for cat in categories] for category in self._categories: if category not in category_ids: raise OperationalException( - f"category not in coingecko category list you can choose from {category_ids}") + f"category not in coingecko category list you can choose from {category_ids}" + ) if self._max_rank > 250: raise OperationalException("This filter only support marketcap rank up to 250.") @@ -160,18 +161,15 @@ class MarketCapPairList(IPairList): data = [] if not self._categories: - data = self._coingecko.get_coins_markets( - **default_kwargs - ) + data = self._coingecko.get_coins_markets(**default_kwargs) else: for category in self._categories: category_data = self._coingecko.get_coins_markets( - **default_kwargs, - **({"category": category} if category else {}) + **default_kwargs, **({"category": category} if category else {}) ) data += category_data - data.sort(key=lambda d: float(d['market_cap'] or 0.0), reverse=True) + data.sort(key=lambda d: float(d["market_cap"] or 0.0), reverse=True) if data: marketcap_list = [row["symbol"] for row in data] @@ -185,7 +183,7 @@ class MarketCapPairList(IPairList): if market == "futures": pair_format += f":{self._stake_currency.upper()}" - top_marketcap = marketcap_list[: self._max_rank:] + top_marketcap = marketcap_list[: self._max_rank :] for mc_pair in top_marketcap: test_pair = f"{mc_pair.upper()}/{pair_format}" From 31680f3b590b550a402db305da5fbbcf88775ce7 Mon Sep 17 00:00:00 2001 From: Matthias Date: Thu, 26 Sep 2024 19:31:43 +0200 Subject: [PATCH 11/18] chore: Improve UI wording --- freqtrade/plugins/pairlist/MarketCapPairList.py | 8 ++++++-- 1 file changed, 6 insertions(+), 2 deletions(-) diff --git a/freqtrade/plugins/pairlist/MarketCapPairList.py b/freqtrade/plugins/pairlist/MarketCapPairList.py index 922d5235c..5542cfaf0 100644 --- a/freqtrade/plugins/pairlist/MarketCapPairList.py +++ b/freqtrade/plugins/pairlist/MarketCapPairList.py @@ -14,6 +14,7 @@ from freqtrade.exchange.exchange_types import Tickers from freqtrade.plugins.pairlist.IPairList import IPairList, PairlistParameter, SupportsBacktesting from freqtrade.util.coin_gecko import FtCoinGeckoApi + logger = logging.getLogger(__name__) @@ -98,8 +99,11 @@ class MarketCapPairList(IPairList): "categories": { "type": "list", "default": [], - "description": "The Categories to be set", - "help": "The Category of the coin e.g layer-1 default [] (https://www.coingecko.com/en/categories)", + "description": "Coin Categories", + "help": ( + "The Category of the coin e.g layer-1 default [] " + "(https://www.coingecko.com/en/categories)" + ), }, "refresh_period": { "type": "number", From 6837196e4451e2409ececfdf5300ed7449b0101f Mon Sep 17 00:00:00 2001 From: Matthias Date: Thu, 26 Sep 2024 19:59:23 +0200 Subject: [PATCH 12/18] fix: treat marketcap as optional parameter --- freqtrade/plugins/pairlist/MarketCapPairList.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/freqtrade/plugins/pairlist/MarketCapPairList.py b/freqtrade/plugins/pairlist/MarketCapPairList.py index 5542cfaf0..35b1cba8f 100644 --- a/freqtrade/plugins/pairlist/MarketCapPairList.py +++ b/freqtrade/plugins/pairlist/MarketCapPairList.py @@ -173,7 +173,7 @@ class MarketCapPairList(IPairList): ) data += category_data - data.sort(key=lambda d: float(d["market_cap"] or 0.0), reverse=True) + data.sort(key=lambda d: float(d.get("market_cap") or 0.0), reverse=True) if data: marketcap_list = [row["symbol"] for row in data] From 7b93b55b784a8b1fef1e10490336f904ad52895f Mon Sep 17 00:00:00 2001 From: Matthias Date: Thu, 26 Sep 2024 20:07:41 +0200 Subject: [PATCH 13/18] docs: rephrase categories docs and add performance warning --- docs/includes/pairlists.md | 8 ++++++-- 1 file changed, 6 insertions(+), 2 deletions(-) diff --git a/docs/includes/pairlists.md b/docs/includes/pairlists.md index 4a797930b..8d79a7bc1 100644 --- a/docs/includes/pairlists.md +++ b/docs/includes/pairlists.md @@ -368,9 +368,13 @@ The optional `bearer_token` will be included in the requests Authorization Heade `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. -`refresh_period` setting defines the period (in seconds) at which the marketcap rank data will be refreshed. Defaults to 86,400s (1 day). The pairlist cache (`refresh_period`) is applicable on both generating pairlists (first position in the list) and filtering instances (not the first position in the list). +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). -`categories` settings this defines takes the list of coins from a category on coingecko. (https://www.coingecko.com/en/categories). Defaults to []. If you choose a wrong category string the Plugin will print the categories you that you can choose from on coingecko. Category is the id of the category so e.g. https://www.coingecko.com/en/categories/layer-1 -> `layer-1` would be the category. You can pass in a list `["layer-1", "meme-token"]` is possible if you choose to. +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. + +!!! Warning "Many categories" + 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. #### AgeFilter From cb36f2844e87a7566aad10dac40338f6a507932d Mon Sep 17 00:00:00 2001 From: Matthias Date: Thu, 26 Sep 2024 20:21:27 +0200 Subject: [PATCH 14/18] chore: Improve "wrong category" error. --- freqtrade/plugins/pairlist/MarketCapPairList.py | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/freqtrade/plugins/pairlist/MarketCapPairList.py b/freqtrade/plugins/pairlist/MarketCapPairList.py index 35b1cba8f..37c8c3c8b 100644 --- a/freqtrade/plugins/pairlist/MarketCapPairList.py +++ b/freqtrade/plugins/pairlist/MarketCapPairList.py @@ -53,7 +53,8 @@ class MarketCapPairList(IPairList): for category in self._categories: if category not in category_ids: raise OperationalException( - f"category not in coingecko category list you can choose from {category_ids}" + f"category {category} not in coingecko category list. " + f"You can choose from {category_ids}" ) if self._max_rank > 250: From 1ed5a37280aa6cb35ffffc6c8aae42b6111f1a18 Mon Sep 17 00:00:00 2001 From: "Jakub W." Date: Thu, 26 Sep 2024 23:38:17 +0200 Subject: [PATCH 15/18] Update freqtrade/plugins/pairlist/IPairList.py Co-authored-by: Matthias --- freqtrade/plugins/pairlist/IPairList.py | 1 - 1 file changed, 1 deletion(-) diff --git a/freqtrade/plugins/pairlist/IPairList.py b/freqtrade/plugins/pairlist/IPairList.py index 4e566f899..6a4ad32fb 100644 --- a/freqtrade/plugins/pairlist/IPairList.py +++ b/freqtrade/plugins/pairlist/IPairList.py @@ -42,7 +42,6 @@ class __OptionPairlistParameter(__PairlistParameterBase): class __ListPairListParamenter(__PairlistParameterBase): type: Literal["list"] default: Union[List[str], None] - options: List[str] class __BoolPairlistParameter(__PairlistParameterBase): From 8c097a81ea55855ad88d7afdc2499c0044b934e7 Mon Sep 17 00:00:00 2001 From: Matthias Date: Sat, 28 Sep 2024 10:10:07 +0200 Subject: [PATCH 16/18] tests: enhance test for marketcappairlist --- tests/plugins/test_pairlist.py | 53 ++++++++++++++++++++++++++++++++-- 1 file changed, 50 insertions(+), 3 deletions(-) diff --git a/tests/plugins/test_pairlist.py b/tests/plugins/test_pairlist.py index 37ebdc58b..79ca8dc0c 100644 --- a/tests/plugins/test_pairlist.py +++ b/tests/plugins/test_pairlist.py @@ -2212,7 +2212,7 @@ def test_FullTradesFilter(mocker, default_conf_usdt, fee, caplog) -> None: @pytest.mark.parametrize( - "pairlists,trade_mode,result", + "pairlists,trade_mode,result,coin_market_calls", [ ( [ @@ -2222,6 +2222,7 @@ def test_FullTradesFilter(mocker, default_conf_usdt, fee, caplog) -> None: ], "spot", ["BTC/USDT", "ETH/USDT"], + 1, ), ( [ @@ -2231,6 +2232,7 @@ def test_FullTradesFilter(mocker, default_conf_usdt, fee, caplog) -> None: ], "spot", ["BTC/USDT", "ETH/USDT", "XRP/USDT", "ADA/USDT"], + 1, ), ( [ @@ -2240,6 +2242,7 @@ def test_FullTradesFilter(mocker, default_conf_usdt, fee, caplog) -> None: ], "spot", ["BTC/USDT", "ETH/USDT", "XRP/USDT"], + 1, ), ( [ @@ -2249,6 +2252,7 @@ def test_FullTradesFilter(mocker, default_conf_usdt, fee, caplog) -> None: ], "spot", ["BTC/USDT", "ETH/USDT", "XRP/USDT"], + 1, ), ( [ @@ -2257,6 +2261,7 @@ def test_FullTradesFilter(mocker, default_conf_usdt, fee, caplog) -> None: ], "spot", ["BTC/USDT", "ETH/USDT", "XRP/USDT"], + 1, ), ( [ @@ -2265,6 +2270,7 @@ def test_FullTradesFilter(mocker, default_conf_usdt, fee, caplog) -> None: ], "spot", ["BTC/USDT", "ETH/USDT"], + 1, ), ( [ @@ -2273,6 +2279,7 @@ def test_FullTradesFilter(mocker, default_conf_usdt, fee, caplog) -> None: ], "futures", ["ETH/USDT:USDT"], + 1, ), ( [ @@ -2281,11 +2288,34 @@ def test_FullTradesFilter(mocker, default_conf_usdt, fee, caplog) -> None: ], "futures", ["ETH/USDT:USDT", "ADA/USDT:USDT"], + 1, + ), + ( + [ + # MarketCapPairList as generator - futures, 1 category + {"method": "MarketCapPairList", "number_assets": 2, "categories": ["layer-1"]} + ], + "futures", + ["ETH/USDT:USDT", "ADA/USDT:USDT"], + ["layer-1"], + ), + ( + [ + # MarketCapPairList as generator - futures, 1 category + { + "method": "MarketCapPairList", + "number_assets": 2, + "categories": ["layer-1", "protocol"], + } + ], + "futures", + ["ETH/USDT:USDT", "ADA/USDT:USDT"], + ["layer-1", "protocol"], ), ], ) def test_MarketCapPairList_filter( - mocker, default_conf_usdt, trade_mode, markets, pairlists, result + mocker, default_conf_usdt, trade_mode, markets, pairlists, result, coin_market_calls ): test_value = [ {"symbol": "btc"}, @@ -2309,8 +2339,16 @@ def test_MarketCapPairList_filter( markets=PropertyMock(return_value=markets), exchange_has=MagicMock(return_value=True), ) - mocker.patch( + "freqtrade.plugins.pairlist.MarketCapPairList.FtCoinGeckoApi.get_coins_categories_list", + return_value=[ + {"category_id": "layer-1"}, + {"category_id": "protocol"}, + {"category_id": "defi"}, + ], + ) + + gcm_mock = mocker.patch( "freqtrade.plugins.pairlist.MarketCapPairList.FtCoinGeckoApi.get_coins_markets", return_value=test_value, ) @@ -2319,6 +2357,15 @@ def test_MarketCapPairList_filter( pm = PairListManager(exchange, default_conf_usdt) pm.refresh_pairlist() + if isinstance(coin_market_calls, int): + assert gcm_mock.call_count == coin_market_calls + else: + assert gcm_mock.call_count == len(coin_market_calls) + for call in coin_market_calls: + assert any( + "category" in c.kwargs and c.kwargs["category"] == call + for c in gcm_mock.call_args_list + ) assert pm.whitelist == result From 255ad7cac559490c1ec1835a12be7d5928ab6f92 Mon Sep 17 00:00:00 2001 From: Matthias Date: Sat, 28 Sep 2024 10:14:31 +0200 Subject: [PATCH 17/18] tests: test invalid category in list --- tests/plugins/test_pairlist.py | 21 +++++++++++++++++++++ 1 file changed, 21 insertions(+) diff --git a/tests/plugins/test_pairlist.py b/tests/plugins/test_pairlist.py index 79ca8dc0c..aa1872cd1 100644 --- a/tests/plugins/test_pairlist.py +++ b/tests/plugins/test_pairlist.py @@ -2438,6 +2438,27 @@ def test_MarketCapPairList_exceptions(mocker, default_conf_usdt): ): PairListManager(exchange, default_conf_usdt) + # Test invalid coinmarkets list + mocker.patch( + "freqtrade.plugins.pairlist.MarketCapPairList.FtCoinGeckoApi.get_coins_categories_list", + return_value=[ + {"category_id": "layer-1"}, + {"category_id": "protocol"}, + {"category_id": "defi"}, + ], + ) + default_conf_usdt["pairlists"] = [ + { + "method": "MarketCapPairList", + "number_assets": 20, + "categories": ["layer-1", "defi", "layer250"], + } + ] + with pytest.raises( + OperationalException, match="category layer250 not in coingecko category list." + ): + PairListManager(exchange, default_conf_usdt) + @pytest.mark.parametrize( "pairlists,expected_error,expected_warning", From f4d76aa36090e4ade7613a7fec908ddc08bacaf3 Mon Sep 17 00:00:00 2001 From: Matthias Date: Sat, 28 Sep 2024 10:18:59 +0200 Subject: [PATCH 18/18] chore: improved wording --- freqtrade/plugins/pairlist/MarketCapPairList.py | 2 +- tests/plugins/test_pairlist.py | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/freqtrade/plugins/pairlist/MarketCapPairList.py b/freqtrade/plugins/pairlist/MarketCapPairList.py index 37c8c3c8b..8bd425c32 100644 --- a/freqtrade/plugins/pairlist/MarketCapPairList.py +++ b/freqtrade/plugins/pairlist/MarketCapPairList.py @@ -53,7 +53,7 @@ class MarketCapPairList(IPairList): for category in self._categories: if category not in category_ids: raise OperationalException( - f"category {category} not in coingecko category list. " + f"Category {category} not in coingecko category list. " f"You can choose from {category_ids}" ) diff --git a/tests/plugins/test_pairlist.py b/tests/plugins/test_pairlist.py index aa1872cd1..6c58acd68 100644 --- a/tests/plugins/test_pairlist.py +++ b/tests/plugins/test_pairlist.py @@ -2455,7 +2455,7 @@ def test_MarketCapPairList_exceptions(mocker, default_conf_usdt): } ] with pytest.raises( - OperationalException, match="category layer250 not in coingecko category list." + OperationalException, match="Category layer250 not in coingecko category list." ): PairListManager(exchange, default_conf_usdt)