2019-11-09 05:55:16 +00:00
|
|
|
"""
|
2020-05-17 11:10:11 +00:00
|
|
|
PairList manager class
|
|
|
|
"""
|
2019-11-09 05:55:16 +00:00
|
|
|
import logging
|
2020-05-17 08:39:18 +00:00
|
|
|
from copy import deepcopy
|
2020-05-18 18:59:50 +00:00
|
|
|
from typing import Dict, List
|
2019-11-09 05:55:16 +00:00
|
|
|
|
2019-12-30 14:02:17 +00:00
|
|
|
from cachetools import TTLCache, cached
|
|
|
|
|
|
|
|
from freqtrade.exceptions import OperationalException
|
2019-11-09 05:55:16 +00:00
|
|
|
from freqtrade.pairlist.IPairList import IPairList
|
|
|
|
from freqtrade.resolvers import PairListResolver
|
2020-05-18 18:59:50 +00:00
|
|
|
from freqtrade.typing import ListPairsWithTimeframes
|
2019-11-09 05:55:16 +00:00
|
|
|
|
2020-05-18 10:54:21 +00:00
|
|
|
|
2019-11-09 05:55:16 +00:00
|
|
|
logger = logging.getLogger(__name__)
|
|
|
|
|
|
|
|
|
|
|
|
class PairListManager():
|
|
|
|
|
|
|
|
def __init__(self, exchange, config: dict) -> None:
|
|
|
|
self._exchange = exchange
|
|
|
|
self._config = config
|
|
|
|
self._whitelist = self._config['exchange'].get('pair_whitelist')
|
|
|
|
self._blacklist = self._config['exchange'].get('pair_blacklist', [])
|
|
|
|
self._pairlists: List[IPairList] = []
|
2019-11-09 06:05:17 +00:00
|
|
|
self._tickers_needed = False
|
2019-11-09 13:49:25 +00:00
|
|
|
for pl in self._config.get('pairlists', None):
|
|
|
|
if 'method' not in pl:
|
|
|
|
logger.warning(f"No method in {pl}")
|
|
|
|
continue
|
2019-12-23 08:56:12 +00:00
|
|
|
pairl = PairListResolver.load_pairlist(pl.get('method'),
|
|
|
|
exchange=exchange,
|
|
|
|
pairlistmanager=self,
|
|
|
|
config=config,
|
|
|
|
pairlistconfig=pl,
|
|
|
|
pairlist_pos=len(self._pairlists)
|
|
|
|
)
|
2019-11-09 06:05:17 +00:00
|
|
|
self._tickers_needed = pairl.needstickers or self._tickers_needed
|
2019-11-09 05:55:16 +00:00
|
|
|
self._pairlists.append(pairl)
|
2019-11-09 13:49:25 +00:00
|
|
|
|
2019-11-09 13:00:32 +00:00
|
|
|
if not self._pairlists:
|
2019-11-09 13:49:25 +00:00
|
|
|
raise OperationalException("No Pairlist defined!")
|
2019-11-09 05:55:16 +00:00
|
|
|
|
|
|
|
@property
|
|
|
|
def whitelist(self) -> List[str]:
|
|
|
|
"""
|
|
|
|
Has the current whitelist
|
|
|
|
"""
|
|
|
|
return self._whitelist
|
|
|
|
|
|
|
|
@property
|
|
|
|
def blacklist(self) -> List[str]:
|
|
|
|
"""
|
|
|
|
Has the current blacklist
|
|
|
|
-> no need to overwrite in subclasses
|
|
|
|
"""
|
|
|
|
return self._blacklist
|
|
|
|
|
2019-11-09 08:07:46 +00:00
|
|
|
@property
|
2019-11-09 13:00:32 +00:00
|
|
|
def name_list(self) -> List[str]:
|
2019-11-09 08:07:46 +00:00
|
|
|
"""
|
2019-11-09 13:00:32 +00:00
|
|
|
Get list of loaded pairlists names
|
2019-11-09 08:07:46 +00:00
|
|
|
"""
|
2019-11-09 13:00:32 +00:00
|
|
|
return [p.name for p in self._pairlists]
|
2019-11-09 08:07:46 +00:00
|
|
|
|
|
|
|
def short_desc(self) -> List[Dict]:
|
|
|
|
"""
|
|
|
|
List of short_desc for each pairlist
|
|
|
|
"""
|
|
|
|
return [{p.name: p.short_desc()} for p in self._pairlists]
|
|
|
|
|
2019-11-09 18:45:09 +00:00
|
|
|
@cached(TTLCache(maxsize=1, ttl=1800))
|
|
|
|
def _get_cached_tickers(self):
|
|
|
|
return self._exchange.get_tickers()
|
|
|
|
|
2019-11-09 05:55:16 +00:00
|
|
|
def refresh_pairlist(self) -> None:
|
|
|
|
"""
|
2019-11-09 14:04:04 +00:00
|
|
|
Run pairlist through all configured pairlists.
|
2019-11-09 05:55:16 +00:00
|
|
|
"""
|
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 = {}
|
2019-11-09 06:05:17 +00:00
|
|
|
if self._tickers_needed:
|
2019-11-09 18:45:09 +00:00
|
|
|
tickers = self._get_cached_tickers()
|
2019-11-09 06:05:17 +00:00
|
|
|
|
2020-05-17 08:39:18 +00:00
|
|
|
# Adjust whitelist if filters are using tickers
|
|
|
|
pairlist = self._prepare_whitelist(self._whitelist.copy(), tickers)
|
|
|
|
|
2019-11-09 06:19:46 +00:00
|
|
|
# Process all pairlists in chain
|
2019-11-09 05:55:16 +00:00
|
|
|
for pl in self._pairlists:
|
2019-11-09 06:19:46 +00:00
|
|
|
pairlist = pl.filter_pairlist(pairlist, tickers)
|
2019-11-09 05:55:16 +00:00
|
|
|
|
2020-05-17 11:10:11 +00:00
|
|
|
# Validation against blacklist happens after the pairlists to ensure
|
|
|
|
# blacklist is respected.
|
2020-03-09 10:30:13 +00:00
|
|
|
pairlist = IPairList.verify_blacklist(pairlist, self.blacklist, True)
|
2019-11-09 06:23:34 +00:00
|
|
|
|
2019-11-09 05:55:16 +00:00
|
|
|
self._whitelist = pairlist
|
2020-05-18 10:54:21 +00:00
|
|
|
|
2020-05-17 08:39:18 +00:00
|
|
|
def _prepare_whitelist(self, pairlist: List[str], tickers) -> List[str]:
|
|
|
|
"""
|
2020-05-17 11:10:11 +00:00
|
|
|
Prepare sanitized pairlist for Pairlist Filters that use tickers data - remove
|
2020-05-17 08:39:18 +00:00
|
|
|
pairs that do not have ticker available
|
|
|
|
"""
|
|
|
|
if self._tickers_needed:
|
|
|
|
# Copy list since we're modifying this list
|
|
|
|
for p in deepcopy(pairlist):
|
2020-05-17 11:10:11 +00:00
|
|
|
if p not in tickers:
|
2020-05-17 08:39:18 +00:00
|
|
|
pairlist.remove(p)
|
|
|
|
|
|
|
|
return pairlist
|
2020-05-18 11:00:09 +00:00
|
|
|
|
2020-05-18 10:54:21 +00:00
|
|
|
def create_pair_list(self, pairs: List[str], timeframe: str = None) -> ListPairsWithTimeframes:
|
|
|
|
"""
|
|
|
|
Create list of pair tuples with (pair, ticker_interval)
|
|
|
|
"""
|
2020-05-18 12:03:36 +00:00
|
|
|
return [(pair, timeframe or self._config['ticker_interval']) for pair in pairs]
|