From 7e43574382c0205d24a308e073b67c148e064d91 Mon Sep 17 00:00:00 2001 From: hroff-1902 Date: Wed, 20 May 2020 13:27:07 +0300 Subject: [PATCH 1/2] Refactor filter_pairlist() --- freqtrade/pairlist/IPairList.py | 29 +++++++++++++++++++++++++-- freqtrade/pairlist/PrecisionFilter.py | 21 +++---------------- freqtrade/pairlist/PriceFilter.py | 25 +++++------------------ freqtrade/pairlist/ShuffleFilter.py | 5 +++-- freqtrade/pairlist/SpreadFilter.py | 25 ++++------------------- freqtrade/pairlist/VolumePairList.py | 3 ++- 6 files changed, 44 insertions(+), 64 deletions(-) diff --git a/freqtrade/pairlist/IPairList.py b/freqtrade/pairlist/IPairList.py index bd8e843e6..b93621f2e 100644 --- a/freqtrade/pairlist/IPairList.py +++ b/freqtrade/pairlist/IPairList.py @@ -3,6 +3,7 @@ PairList Handler base class """ import logging from abc import ABC, abstractmethod, abstractproperty +from copy import deepcopy from typing import Any, Dict, List from cachetools import TTLCache, cached @@ -75,16 +76,40 @@ class IPairList(ABC): -> 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]: """ Filters and sorts pairlist and returns the whitelist again. + 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 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): + # 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]: """ diff --git a/freqtrade/pairlist/PrecisionFilter.py b/freqtrade/pairlist/PrecisionFilter.py index 6bd9c594e..4abc9b637 100644 --- a/freqtrade/pairlist/PrecisionFilter.py +++ b/freqtrade/pairlist/PrecisionFilter.py @@ -2,8 +2,7 @@ Precision pair list filter """ import logging -from copy import deepcopy -from typing import Any, Dict, List +from typing import Any, Dict from freqtrade.pairlist.IPairList import IPairList @@ -36,16 +35,14 @@ class PrecisionFilter(IPairList): """ 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 low value pairs. :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 """ - stop_price = ticker['ask'] * stoploss + stop_price = ticker['ask'] * self._stoploss # Adjust stop-prices to precision sp = self._exchange.price_to_precision(ticker["symbol"], stop_price) @@ -60,15 +57,3 @@ class PrecisionFilter(IPairList): return False 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 diff --git a/freqtrade/pairlist/PriceFilter.py b/freqtrade/pairlist/PriceFilter.py index 167717656..ade524fe7 100644 --- a/freqtrade/pairlist/PriceFilter.py +++ b/freqtrade/pairlist/PriceFilter.py @@ -2,8 +2,7 @@ Price pair list filter """ import logging -from copy import deepcopy -from typing import Any, Dict, List +from typing import Any, Dict from freqtrade.pairlist.IPairList import IPairList @@ -35,12 +34,15 @@ class PriceFilter(IPairList): """ 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. :param ticker: ticker dict as returned from ccxt.load_markets() :return: True if the pair can stay, false if it should be removed """ + if not self._low_price_ratio: + return True + if ticker['last'] is None: self.log_on_refresh(logger.info, f"Removed {ticker['symbol']} from whitelist, because " @@ -53,20 +55,3 @@ class PriceFilter(IPairList): f"because 1 unit is {changeperc * 100:.3f}%") return False 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 diff --git a/freqtrade/pairlist/ShuffleFilter.py b/freqtrade/pairlist/ShuffleFilter.py index a10a1af8b..ba3792213 100644 --- a/freqtrade/pairlist/ShuffleFilter.py +++ b/freqtrade/pairlist/ShuffleFilter.py @@ -3,7 +3,7 @@ Shuffle pair list filter """ import logging import random -from typing import Dict, List +from typing import Any, Dict, List from freqtrade.pairlist.IPairList import IPairList @@ -13,7 +13,8 @@ logger = logging.getLogger(__name__) 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: super().__init__(exchange, pairlistmanager, config, pairlistconfig, pairlist_pos) diff --git a/freqtrade/pairlist/SpreadFilter.py b/freqtrade/pairlist/SpreadFilter.py index 88e143a50..4d6a2bad9 100644 --- a/freqtrade/pairlist/SpreadFilter.py +++ b/freqtrade/pairlist/SpreadFilter.py @@ -2,8 +2,7 @@ Spread pair list filter """ import logging -from copy import deepcopy -from typing import Dict, List +from typing import Any, Dict from freqtrade.pairlist.IPairList import IPairList @@ -13,7 +12,8 @@ logger = logging.getLogger(__name__) 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: super().__init__(exchange, pairlistmanager, config, pairlistconfig, pairlist_pos) @@ -35,7 +35,7 @@ class SpreadFilter(IPairList): return (f"{self.name} - Filtering pairs with ask/bid diff above " 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 :param ticker: ticker dict as returned from ccxt.load_markets() @@ -51,20 +51,3 @@ class SpreadFilter(IPairList): else: return True 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 diff --git a/freqtrade/pairlist/VolumePairList.py b/freqtrade/pairlist/VolumePairList.py index b792ab037..f7bf2299f 100644 --- a/freqtrade/pairlist/VolumePairList.py +++ b/freqtrade/pairlist/VolumePairList.py @@ -19,7 +19,8 @@ SORT_VALUES = ['askVolume', 'bidVolume', 'quoteVolume'] 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: super().__init__(exchange, pairlistmanager, config, pairlistconfig, pairlist_pos) From 4f0d928145faff6ac36e0f9541d2777012348459 Mon Sep 17 00:00:00 2001 From: hroff-1902 Date: Wed, 20 May 2020 13:41:00 +0300 Subject: [PATCH 2/2] Introduce self._enabled in pairlist handlers --- freqtrade/pairlist/IPairList.py | 13 ++++++++----- freqtrade/pairlist/PrecisionFilter.py | 5 ++++- freqtrade/pairlist/PriceFilter.py | 4 +--- freqtrade/pairlist/SpreadFilter.py | 1 + 4 files changed, 14 insertions(+), 9 deletions(-) diff --git a/freqtrade/pairlist/IPairList.py b/freqtrade/pairlist/IPairList.py index b93621f2e..e49ad1561 100644 --- a/freqtrade/pairlist/IPairList.py +++ b/freqtrade/pairlist/IPairList.py @@ -26,6 +26,8 @@ class IPairList(ABC): :param pairlistconfig: Configuration for this Pairlist Handler - can be empty. :param pairlist_pos: Position of the Pairlist Handler in the chain """ + self._enabled = True + self._exchange = exchange self._pairlistmanager = pairlistmanager self._config = config @@ -103,11 +105,12 @@ class IPairList(ABC): :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): - # Filter out assets - if not self._validate_pair(tickers[p]): - pairlist.remove(p) + 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 diff --git a/freqtrade/pairlist/PrecisionFilter.py b/freqtrade/pairlist/PrecisionFilter.py index 4abc9b637..0331347be 100644 --- a/freqtrade/pairlist/PrecisionFilter.py +++ b/freqtrade/pairlist/PrecisionFilter.py @@ -17,8 +17,11 @@ class PrecisionFilter(IPairList): pairlist_pos: int) -> None: 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 - self._stoploss = 1 - abs(self._config['stoploss']) + self._stoploss = 1 - abs(self._stoploss) @property def needstickers(self) -> bool: diff --git a/freqtrade/pairlist/PriceFilter.py b/freqtrade/pairlist/PriceFilter.py index ade524fe7..b85d68269 100644 --- a/freqtrade/pairlist/PriceFilter.py +++ b/freqtrade/pairlist/PriceFilter.py @@ -18,6 +18,7 @@ class PriceFilter(IPairList): super().__init__(exchange, pairlistmanager, config, pairlistconfig, pairlist_pos) self._low_price_ratio = pairlistconfig.get('low_price_ratio', 0) + self._enabled = self._low_price_ratio != 0 @property def needstickers(self) -> bool: @@ -40,9 +41,6 @@ class PriceFilter(IPairList): :param ticker: ticker dict as returned from ccxt.load_markets() :return: True if the pair can stay, false if it should be removed """ - if not self._low_price_ratio: - return True - if ticker['last'] is None: self.log_on_refresh(logger.info, f"Removed {ticker['symbol']} from whitelist, because " diff --git a/freqtrade/pairlist/SpreadFilter.py b/freqtrade/pairlist/SpreadFilter.py index 4d6a2bad9..0147c0068 100644 --- a/freqtrade/pairlist/SpreadFilter.py +++ b/freqtrade/pairlist/SpreadFilter.py @@ -18,6 +18,7 @@ class SpreadFilter(IPairList): super().__init__(exchange, pairlistmanager, config, pairlistconfig, pairlist_pos) self._max_spread_ratio = pairlistconfig.get('max_spread_ratio', 0.005) + self._enabled = self._max_spread_ratio != 0 @property def needstickers(self) -> bool: