From 1e4920059650cba27d91999dfc311de0a21af07b Mon Sep 17 00:00:00 2001 From: Stefano Ariestasia Date: Sun, 7 Jan 2024 20:16:44 +0900 Subject: [PATCH] early stage of marketcapfilter --- freqtrade/constants.py | 7 +- freqtrade/plugins/pairlist/MarketCapFilter.py | 119 ++++++++++++++++++ 2 files changed, 123 insertions(+), 3 deletions(-) create mode 100644 freqtrade/plugins/pairlist/MarketCapFilter.py diff --git a/freqtrade/constants.py b/freqtrade/constants.py index 71081433e..d7767dc2e 100644 --- a/freqtrade/constants.py +++ b/freqtrade/constants.py @@ -33,9 +33,10 @@ HYPEROPT_LOSS_BUILTIN = ['ShortTradeDurHyperOptLoss', 'OnlyProfitHyperOptLoss', 'MaxDrawDownHyperOptLoss', 'MaxDrawDownRelativeHyperOptLoss', 'ProfitDrawDownHyperOptLoss'] AVAILABLE_PAIRLISTS = ['StaticPairList', 'VolumePairList', 'ProducerPairList', 'RemotePairList', - 'AgeFilter', "FullTradesFilter", 'OffsetFilter', 'PerformanceFilter', - 'PrecisionFilter', 'PriceFilter', 'RangeStabilityFilter', - 'ShuffleFilter', 'SpreadFilter', 'VolatilityFilter'] + 'AgeFilter', "FullTradesFilter", 'MarketCapFilter', 'OffsetFilter', + 'PerformanceFilter', 'PrecisionFilter', 'PriceFilter', + 'RangeStabilityFilter', 'ShuffleFilter', 'SpreadFilter', + 'VolatilityFilter'] AVAILABLE_PROTECTIONS = ['CooldownPeriod', 'LowProfitPairs', 'MaxDrawdown', 'StoplossGuard'] AVAILABLE_DATAHANDLERS = ['json', 'jsongz', 'hdf5', 'feather', 'parquet'] diff --git a/freqtrade/plugins/pairlist/MarketCapFilter.py b/freqtrade/plugins/pairlist/MarketCapFilter.py new file mode 100644 index 000000000..6238b6211 --- /dev/null +++ b/freqtrade/plugins/pairlist/MarketCapFilter.py @@ -0,0 +1,119 @@ +""" +Market Cap PairList provider + +Provides dynamic pair list based on Market Cap +""" +import logging +from datetime import timedelta +from typing import Any, Dict, List, Literal + +from cachetools import TTLCache + +from freqtrade.constants import Config, ListPairsWithTimeframes +from freqtrade.exceptions import OperationalException +from freqtrade.exchange import timeframe_to_minutes, timeframe_to_prev_date +from freqtrade.exchange.types import Tickers +from freqtrade.plugins.pairlist.IPairList import IPairList, PairlistParameter +from freqtrade.util import dt_now, format_ms_time + +from pycoingecko import CoinGeckoAPI + +logger = logging.getLogger(__name__) + + +SORT_VALUES = ['quoteVolume'] + + +class MarketCapFilter(IPairList): + + is_pairlist_generator = True + + def __init__(self, exchange, pairlistmanager, + config: Config, pairlistconfig: Dict[str, Any], + pairlist_pos: int) -> None: + super().__init__(exchange, pairlistmanager, config, pairlistconfig, pairlist_pos) + + if 'max_rank' not in self._pairlistconfig: + raise OperationalException( + '`max_rank` not specified. Please check your configuration ' + 'for "pairlist.config.max_rank"') + + self._stake_currency = config['stake_currency'] + self._max_rank = self._pairlistconfig['max_rank'] + self._refresh_period = self._pairlistconfig.get('refresh_period', 86400) + self._marketcap_cache: TTLCache = TTLCache(maxsize=1, ttl=self._refresh_period) + self._def_candletype = self._config['candle_type_def'] + self._coingekko: CoinGeckoAPI = CoinGeckoAPI() + + if self._max_rank > 250: + raise OperationalException( + "This filter only support up to rank 250." + ) + + + @property + def needstickers(self) -> bool: + """ + Boolean property defining if tickers are necessary. + If no Pairlist requires tickers, an empty Dict is passed + as tickers argument to filter_pairlist + """ + return False + + def _validate_keys(self, key): + return key in SORT_VALUES + + def short_desc(self) -> str: + """ + Short whitelist method description - used for startup-messages + """ + return f"{self.name} - Only use top {self._pairlistconfig['max_rank']} market cap pairs." + + @staticmethod + def description() -> str: + return "Filter pair list based on market cap." + + @staticmethod + def available_parameters() -> Dict[str, PairlistParameter]: + return { + "max_rank": { + "type": "number", + "default": 30, + "description": "Max market cap rank", + "help": "Only use assets that ranked within top max_rank market cap", + }, + "refresh_period": { + "type": "number", + "default": 86400, + "description": "Refresh period", + "help": "Refresh period in seconds", + } + } + + def filter_pairlist(self, pairlist: List[str], tickers: Dict) -> List[str]: + """ + Filters and sorts pairlist and returns the whitelist again. + Called on each bot iteration - please use internal caching if necessary + :param pairlist: pairlist to filter or sort + :param tickers: Tickers (from exchange.get_tickers). May be cached. + :return: new whitelist + """ + marketcap_list = self._marketcap_cache.get('marketcap') + can_filter = False + + if marketcap_list: + can_filter = True + else: + data = self._coingekko.get_coins_markets(vs_currencies='usd', order='market_cap_desc', + per_page='250', page='1', sparkline='false', + locale='en') + if data: + pairs_data = [] + for row in data: + pairs_data.append(row['symbol']) + + if len(pairs_data) > 0: + logger.info(pairs_data) + self._marketcap_cache['marketcap'] = pairs_data + + return pairlist