freqtrade_origin/freqtrade/plugins/pairlistmanager.py

209 lines
8.4 KiB
Python
Raw Normal View History

2019-11-09 05:55:16 +00:00
"""
2020-05-17 11:10:11 +00:00
PairList manager class
"""
2024-05-12 14:37:11 +00:00
2019-11-09 05:55:16 +00:00
import logging
from functools import partial
2022-09-18 12:59:16 +00:00
from typing import Dict, List, Optional
2019-11-09 05:55:16 +00:00
from cachetools import TTLCache, cached
2022-09-18 11:31:52 +00:00
from freqtrade.constants import Config, ListPairsWithTimeframes
2022-09-18 12:59:16 +00:00
from freqtrade.data.dataprovider import DataProvider
2021-12-03 13:11:24 +00:00
from freqtrade.enums import CandleType
from freqtrade.enums.runmode import RunMode
from freqtrade.exceptions import OperationalException
from freqtrade.exchange.types import Tickers
from freqtrade.mixins import LoggingMixin
from freqtrade.plugins.pairlist.IPairList import IPairList, SupportsBacktesting
from freqtrade.plugins.pairlist.pairlist_helpers import expand_pairlist
2019-11-09 05:55:16 +00:00
from freqtrade.resolvers import PairListResolver
2019-11-09 05:55:16 +00:00
logger = logging.getLogger(__name__)
class PairListManager(LoggingMixin):
2023-01-21 14:01:56 +00:00
def __init__(
2024-05-12 14:37:11 +00:00
self, exchange, config: Config, dataprovider: Optional[DataProvider] = None
) -> None:
2019-11-09 05:55:16 +00:00
self._exchange = exchange
self._config = config
2024-05-12 14:37:11 +00:00
self._whitelist = self._config["exchange"].get("pair_whitelist")
self._blacklist = self._config["exchange"].get("pair_blacklist", [])
2020-05-19 00:35:01 +00:00
self._pairlist_handlers: List[IPairList] = []
self._tickers_needed = False
2022-09-18 12:59:16 +00:00
self._dataprovider: Optional[DataProvider] = dataprovider
2024-05-12 14:37:11 +00:00
for pairlist_handler_config in self._config.get("pairlists", []):
2020-05-19 00:35:01 +00:00
pairlist_handler = PairListResolver.load_pairlist(
2024-05-12 14:37:11 +00:00
pairlist_handler_config["method"],
2021-08-06 22:19:36 +00:00
exchange=exchange,
pairlistmanager=self,
config=config,
pairlistconfig=pairlist_handler_config,
2024-05-12 14:37:11 +00:00
pairlist_pos=len(self._pairlist_handlers),
2021-08-06 22:19:36 +00:00
)
2020-05-19 00:35:01 +00:00
self._tickers_needed |= pairlist_handler.needstickers
self._pairlist_handlers.append(pairlist_handler)
if not self._pairlist_handlers:
raise OperationalException("No Pairlist Handlers defined")
2019-11-09 05:55:16 +00:00
2024-05-12 14:37:11 +00:00
if self._tickers_needed and not self._exchange.exchange_has("fetchTickers"):
invalid = ". ".join([p.name for p in self._pairlist_handlers if p.needstickers])
raise OperationalException(
"Exchange does not support fetchTickers, therefore the following pairlists "
"cannot be used. Please edit your config and restart the bot.\n"
f"{invalid}."
)
self._check_backtest()
2024-05-12 14:37:11 +00:00
refresh_period = config.get("pairlist_refresh_period", 3600)
LoggingMixin.__init__(self, logger, refresh_period)
def _check_backtest(self) -> None:
if self._config["runmode"] not in (RunMode.BACKTEST, RunMode.EDGE, RunMode.HYPEROPT):
return
pairlist_errors: List[str] = []
noaction_pairlists: List[str] = []
biased_pairlists: List[str] = []
for pairlist_handler in self._pairlist_handlers:
if pairlist_handler.supports_backtesting == SupportsBacktesting.NO:
pairlist_errors.append(pairlist_handler.name)
if pairlist_handler.supports_backtesting == SupportsBacktesting.NO_ACTION:
noaction_pairlists.append(pairlist_handler.name)
if pairlist_handler.supports_backtesting == SupportsBacktesting.TODAYS_DATA:
biased_pairlists.append(pairlist_handler.name)
if noaction_pairlists:
logger.warning(
f"Pairlist Handlers {', '.join(noaction_pairlists)} do not generate "
"any changes during backtesting. While it's safe to leave them enabled, they will "
"not behave like in dry/live modes. "
)
if biased_pairlists:
logger.warning(
f"Pairlist Handlers {', '.join(biased_pairlists)} will introduce a lookahead bias "
"to your backtest results, as they use today's data - which inheritly suffers from "
"'winner bias'."
)
if pairlist_errors:
raise OperationalException(
f"Pairlist Handlers {', '.join(pairlist_errors)} do not support backtesting."
)
2019-11-09 05:55:16 +00:00
@property
def whitelist(self) -> List[str]:
2020-12-31 08:43:24 +00:00
"""The current whitelist"""
2019-11-09 05:55:16 +00:00
return self._whitelist
@property
def blacklist(self) -> List[str]:
"""
2020-12-31 08:43:24 +00:00
The current blacklist
2019-11-09 05:55:16 +00:00
-> no need to overwrite in subclasses
"""
return self._blacklist
2020-12-30 08:55:44 +00:00
@property
def expanded_blacklist(self) -> List[str]:
2020-12-31 08:43:24 +00:00
"""The expanded blacklist (including wildcard expansion)"""
2020-12-30 08:55:44 +00:00
return expand_pairlist(self._blacklist, self._exchange.get_markets().keys())
2019-11-09 08:07:46 +00:00
@property
2019-11-09 13:00:32 +00:00
def name_list(self) -> List[str]:
2020-12-31 08:43:24 +00:00
"""Get list of loaded Pairlist Handler names"""
2020-05-19 00:35:01 +00:00
return [p.name for p in self._pairlist_handlers]
2019-11-09 08:07:46 +00:00
def short_desc(self) -> List[Dict]:
2020-12-31 08:43:24 +00:00
"""List of short_desc for each Pairlist Handler"""
2020-05-19 00:35:01 +00:00
return [{p.name: p.short_desc()} for p in self._pairlist_handlers]
2019-11-09 08:07:46 +00:00
2019-11-09 18:45:09 +00:00
@cached(TTLCache(maxsize=1, ttl=1800))
def _get_cached_tickers(self) -> Tickers:
2019-11-09 18:45:09 +00:00
return self._exchange.get_tickers()
2019-11-09 05:55:16 +00:00
def refresh_pairlist(self) -> None:
2020-12-31 08:43:24 +00:00
"""Run pairlist through all configured Pairlist Handlers."""
2020-05-17 11:10:11 +00:00
# Tickers should be cached to avoid calling the exchange on each call.
2019-11-09 12:40:36 +00:00
tickers: Dict = {}
if self._tickers_needed:
2019-11-09 18:45:09 +00:00
tickers = self._get_cached_tickers()
# Generate the pairlist with first Pairlist Handler in the chain
pairlist = self._pairlist_handlers[0].gen_pairlist(tickers)
2020-05-19 00:35:01 +00:00
# Process all Pairlist Handlers in the chain
# except for the first one, which is the generator.
for pairlist_handler in self._pairlist_handlers[1:]:
2020-05-19 00:35:01 +00:00
pairlist = pairlist_handler.filter_pairlist(pairlist, tickers)
2019-11-09 05:55:16 +00:00
2020-05-19 00:35:01 +00:00
# Validation against blacklist happens after the chain of Pairlist Handlers
# to ensure blacklist is respected.
pairlist = self.verify_blacklist(pairlist, logger.warning)
2019-11-09 06:23:34 +00:00
2022-09-25 07:43:39 +00:00
self.log_once(f"Whitelist with {len(pairlist)} pairs: {pairlist}", logger.info)
2019-11-09 05:55:16 +00:00
self._whitelist = pairlist
def verify_blacklist(self, pairlist: List[str], logmethod) -> List[str]:
2020-05-19 20:13:51 +00:00
"""
Verify and remove items from pairlist - returning a filtered pairlist.
Logs a warning or info depending on `aswarning`.
Pairlist Handlers explicitly using this method shall use
`logmethod=logger.info` to avoid spamming with warning messages
2020-05-19 20:13:51 +00:00
:param pairlist: Pairlist to validate
:param logmethod: Function that'll be called, `logger.info` or `logger.warning`.
2020-05-19 20:13:51 +00:00
:return: pairlist - blacklisted pairs
"""
try:
blacklist = self.expanded_blacklist
except ValueError as err:
logger.error(f"Pair blacklist contains an invalid Wildcard: {err}")
return []
log_once = partial(self.log_once, logmethod=logmethod)
for pair in pairlist.copy():
if pair in blacklist:
log_once(f"Pair {pair} in your blacklist. Removing it from whitelist...")
2020-05-19 20:13:51 +00:00
pairlist.remove(pair)
return pairlist
2024-05-12 14:37:11 +00:00
def verify_whitelist(
self, pairlist: List[str], logmethod, keep_invalid: bool = False
) -> List[str]:
2021-01-12 00:13:58 +00:00
"""
Verify and remove items from pairlist - returning a filtered pairlist.
Logs a warning or info depending on `aswarning`.
Pairlist Handlers explicitly using this method shall use
`logmethod=logger.info` to avoid spamming with warning messages
:param pairlist: Pairlist to validate
:param logmethod: Function that'll be called, `logger.info` or `logger.warning`
:param keep_invalid: If sets to True, drops invalid pairs silently while expanding regexes.
:return: pairlist - whitelisted pairs
2021-01-12 00:13:58 +00:00
"""
try:
whitelist = expand_pairlist(pairlist, self._exchange.get_markets().keys(), keep_invalid)
except ValueError as err:
logger.error(f"Pair whitelist contains an invalid Wildcard: {err}")
return []
2021-01-12 00:13:58 +00:00
return whitelist
2023-01-21 14:01:56 +00:00
def create_pair_list(
2024-05-12 14:37:11 +00:00
self, pairs: List[str], timeframe: Optional[str] = None
) -> ListPairsWithTimeframes:
"""
Create list of pair tuples with (pair, timeframe)
"""
return [
(
pair,
2024-05-12 14:37:11 +00:00
timeframe or self._config["timeframe"],
self._config.get("candle_type_def", CandleType.SPOT),
)
for pair in pairs
]