Merge pull request #3346 from hroff-1902/refactor_pairlists2

Refactor filter_pairlists()
This commit is contained in:
Matthias 2020-05-21 07:25:43 +02:00 committed by GitHub
commit d31a091d12
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
6 changed files with 50 additions and 65 deletions

View File

@ -3,6 +3,7 @@ PairList Handler base class
""" """
import logging import logging
from abc import ABC, abstractmethod, abstractproperty from abc import ABC, abstractmethod, abstractproperty
from copy import deepcopy
from typing import Any, Dict, List from typing import Any, Dict, List
from cachetools import TTLCache, cached from cachetools import TTLCache, cached
@ -25,6 +26,8 @@ class IPairList(ABC):
:param pairlistconfig: Configuration for this Pairlist Handler - can be empty. :param pairlistconfig: Configuration for this Pairlist Handler - can be empty.
:param pairlist_pos: Position of the Pairlist Handler in the chain :param pairlist_pos: Position of the Pairlist Handler in the chain
""" """
self._enabled = True
self._exchange = exchange self._exchange = exchange
self._pairlistmanager = pairlistmanager self._pairlistmanager = pairlistmanager
self._config = config self._config = config
@ -75,16 +78,41 @@ class IPairList(ABC):
-> Please overwrite in subclasses -> Please overwrite in subclasses
""" """
@abstractmethod def _validate_pair(self, ticker) -> bool:
"""
Check one pair against Pairlist Handler's specific conditions.
Either implement it in the Pairlist Handler or override the generic
filter_pairlist() method.
:param ticker: ticker dict as returned from ccxt.load_markets()
:return: True if the pair can stay, false if it should be removed
"""
raise NotImplementedError()
def filter_pairlist(self, pairlist: List[str], tickers: Dict) -> List[str]: def filter_pairlist(self, pairlist: List[str], tickers: Dict) -> List[str]:
""" """
Filters and sorts pairlist and returns the whitelist again. Filters and sorts pairlist and returns the whitelist again.
Called on each bot iteration - please use internal caching if necessary Called on each bot iteration - please use internal caching if necessary
-> Please overwrite in subclasses This generic implementation calls self._validate_pair() for each pair
in the pairlist.
Some Pairlist Handlers override this generic implementation and employ
own filtration.
:param pairlist: pairlist to filter or sort :param pairlist: pairlist to filter or sort
:param tickers: Tickers (from exchange.get_tickers()). May be cached. :param tickers: Tickers (from exchange.get_tickers()). May be cached.
:return: new whitelist :return: new whitelist
""" """
if self._enabled:
# Copy list since we're modifying this list
for p in deepcopy(pairlist):
# Filter out assets
if not self._validate_pair(tickers[p]):
pairlist.remove(p)
return pairlist
def verify_blacklist(self, pairlist: List[str], logmethod) -> List[str]: def verify_blacklist(self, pairlist: List[str], logmethod) -> List[str]:
""" """

View File

@ -2,8 +2,7 @@
Precision pair list filter Precision pair list filter
""" """
import logging import logging
from copy import deepcopy from typing import Any, Dict
from typing import Any, Dict, List
from freqtrade.pairlist.IPairList import IPairList from freqtrade.pairlist.IPairList import IPairList
@ -18,8 +17,11 @@ class PrecisionFilter(IPairList):
pairlist_pos: int) -> None: pairlist_pos: int) -> None:
super().__init__(exchange, pairlistmanager, config, pairlistconfig, pairlist_pos) super().__init__(exchange, pairlistmanager, config, pairlistconfig, pairlist_pos)
self._stoploss = self._config['stoploss']
self._enabled = self._stoploss != 0
# Precalculate sanitized stoploss value to avoid recalculation for every pair # Precalculate sanitized stoploss value to avoid recalculation for every pair
self._stoploss = 1 - abs(self._config['stoploss']) self._stoploss = 1 - abs(self._stoploss)
@property @property
def needstickers(self) -> bool: def needstickers(self) -> bool:
@ -36,16 +38,14 @@ class PrecisionFilter(IPairList):
""" """
return f"{self.name} - Filtering untradable pairs." return f"{self.name} - Filtering untradable pairs."
def _validate_precision_filter(self, ticker: dict, stoploss: float) -> bool: def _validate_pair(self, ticker: dict) -> bool:
""" """
Check if pair has enough room to add a stoploss to avoid "unsellable" buys of very Check if pair has enough room to add a stoploss to avoid "unsellable" buys of very
low value pairs. low value pairs.
:param ticker: ticker dict as returned from ccxt.load_markets() :param ticker: ticker dict as returned from ccxt.load_markets()
:param stoploss: stoploss value as set in the configuration
(already cleaned to be 1 - stoploss)
:return: True if the pair can stay, False if it should be removed :return: True if the pair can stay, False if it should be removed
""" """
stop_price = ticker['ask'] * stoploss stop_price = ticker['ask'] * self._stoploss
# Adjust stop-prices to precision # Adjust stop-prices to precision
sp = self._exchange.price_to_precision(ticker["symbol"], stop_price) sp = self._exchange.price_to_precision(ticker["symbol"], stop_price)
@ -60,15 +60,3 @@ class PrecisionFilter(IPairList):
return False return False
return True return True
def filter_pairlist(self, pairlist: List[str], tickers: Dict) -> List[str]:
"""
Filters and sorts pairlists and assigns and returns them again.
"""
# Copy list since we're modifying this list
for p in deepcopy(pairlist):
# Filter out assets which would not allow setting a stoploss
if not self._validate_precision_filter(tickers[p], self._stoploss):
pairlist.remove(p)
return pairlist

View File

@ -2,8 +2,7 @@
Price pair list filter Price pair list filter
""" """
import logging import logging
from copy import deepcopy from typing import Any, Dict
from typing import Any, Dict, List
from freqtrade.pairlist.IPairList import IPairList from freqtrade.pairlist.IPairList import IPairList
@ -19,6 +18,7 @@ class PriceFilter(IPairList):
super().__init__(exchange, pairlistmanager, config, pairlistconfig, pairlist_pos) super().__init__(exchange, pairlistmanager, config, pairlistconfig, pairlist_pos)
self._low_price_ratio = pairlistconfig.get('low_price_ratio', 0) self._low_price_ratio = pairlistconfig.get('low_price_ratio', 0)
self._enabled = self._low_price_ratio != 0
@property @property
def needstickers(self) -> bool: def needstickers(self) -> bool:
@ -35,7 +35,7 @@ class PriceFilter(IPairList):
""" """
return f"{self.name} - Filtering pairs priced below {self._low_price_ratio * 100}%." return f"{self.name} - Filtering pairs priced below {self._low_price_ratio * 100}%."
def _validate_ticker_lowprice(self, ticker) -> bool: def _validate_pair(self, ticker) -> bool:
""" """
Check if if one price-step (pip) is > than a certain barrier. Check if if one price-step (pip) is > than a certain barrier.
:param ticker: ticker dict as returned from ccxt.load_markets() :param ticker: ticker dict as returned from ccxt.load_markets()
@ -53,20 +53,3 @@ class PriceFilter(IPairList):
f"because 1 unit is {changeperc * 100:.3f}%") f"because 1 unit is {changeperc * 100:.3f}%")
return False return False
return True return True
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
"""
if self._low_price_ratio:
# Copy list since we're modifying this list
for p in deepcopy(pairlist):
# Filter out assets which would not allow setting a stoploss
if not self._validate_ticker_lowprice(tickers[p]):
pairlist.remove(p)
return pairlist

View File

@ -3,7 +3,7 @@ Shuffle pair list filter
""" """
import logging import logging
import random import random
from typing import Dict, List from typing import Any, Dict, List
from freqtrade.pairlist.IPairList import IPairList from freqtrade.pairlist.IPairList import IPairList
@ -13,7 +13,8 @@ logger = logging.getLogger(__name__)
class ShuffleFilter(IPairList): class ShuffleFilter(IPairList):
def __init__(self, exchange, pairlistmanager, config, pairlistconfig: dict, def __init__(self, exchange, pairlistmanager,
config: Dict[str, Any], pairlistconfig: Dict[str, Any],
pairlist_pos: int) -> None: pairlist_pos: int) -> None:
super().__init__(exchange, pairlistmanager, config, pairlistconfig, pairlist_pos) super().__init__(exchange, pairlistmanager, config, pairlistconfig, pairlist_pos)

View File

@ -2,8 +2,7 @@
Spread pair list filter Spread pair list filter
""" """
import logging import logging
from copy import deepcopy from typing import Any, Dict
from typing import Dict, List
from freqtrade.pairlist.IPairList import IPairList from freqtrade.pairlist.IPairList import IPairList
@ -13,11 +12,13 @@ logger = logging.getLogger(__name__)
class SpreadFilter(IPairList): class SpreadFilter(IPairList):
def __init__(self, exchange, pairlistmanager, config, pairlistconfig: dict, def __init__(self, exchange, pairlistmanager,
config: Dict[str, Any], pairlistconfig: Dict[str, Any],
pairlist_pos: int) -> None: pairlist_pos: int) -> None:
super().__init__(exchange, pairlistmanager, config, pairlistconfig, pairlist_pos) super().__init__(exchange, pairlistmanager, config, pairlistconfig, pairlist_pos)
self._max_spread_ratio = pairlistconfig.get('max_spread_ratio', 0.005) self._max_spread_ratio = pairlistconfig.get('max_spread_ratio', 0.005)
self._enabled = self._max_spread_ratio != 0
@property @property
def needstickers(self) -> bool: def needstickers(self) -> bool:
@ -35,7 +36,7 @@ class SpreadFilter(IPairList):
return (f"{self.name} - Filtering pairs with ask/bid diff above " return (f"{self.name} - Filtering pairs with ask/bid diff above "
f"{self._max_spread_ratio * 100}%.") f"{self._max_spread_ratio * 100}%.")
def _validate_spread(self, ticker: dict) -> bool: def _validate_pair(self, ticker: dict) -> bool:
""" """
Validate spread for the ticker Validate spread for the ticker
:param ticker: ticker dict as returned from ccxt.load_markets() :param ticker: ticker dict as returned from ccxt.load_markets()
@ -51,20 +52,3 @@ class SpreadFilter(IPairList):
else: else:
return True return True
return False return False
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
"""
# Copy list since we're modifying this list
for p in deepcopy(pairlist):
ticker = tickers[p]
# Filter out assets
if not self._validate_spread(ticker):
pairlist.remove(p)
return pairlist

View File

@ -19,7 +19,8 @@ SORT_VALUES = ['askVolume', 'bidVolume', 'quoteVolume']
class VolumePairList(IPairList): class VolumePairList(IPairList):
def __init__(self, exchange, pairlistmanager, config: Dict[str, Any], pairlistconfig: dict, def __init__(self, exchange, pairlistmanager,
config: Dict[str, Any], pairlistconfig: Dict[str, Any],
pairlist_pos: int) -> None: pairlist_pos: int) -> None:
super().__init__(exchange, pairlistmanager, config, pairlistconfig, pairlist_pos) super().__init__(exchange, pairlistmanager, config, pairlistconfig, pairlist_pos)