stable/freqtrade/plugins/pairlist/rangestabilityfilter.py

126 lines
5.1 KiB
Python
Raw Normal View History

2020-11-21 14:29:11 +00:00
"""
2020-11-22 18:59:18 +00:00
Rate of change pairlist filter
2020-11-21 14:29:11 +00:00
"""
import logging
2020-12-15 19:59:58 +00:00
from copy import deepcopy
from typing import Any, Dict, List, Optional
2020-11-21 14:29:11 +00:00
import arrow
from cachetools.ttl import TTLCache
2020-12-15 19:59:58 +00:00
from pandas import DataFrame
2020-11-21 14:29:11 +00:00
from freqtrade.exceptions import OperationalException
from freqtrade.misc import plural
from freqtrade.plugins.pairlist.IPairList import IPairList
2020-11-21 14:29:11 +00:00
logger = logging.getLogger(__name__)
class RangeStabilityFilter(IPairList):
2020-11-21 14:29:11 +00:00
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)
self._days = pairlistconfig.get('lookback_days', 10)
self._min_rate_of_change = pairlistconfig.get('min_rate_of_change', 0.01)
2021-08-02 02:49:49 +00:00
self._max_rate_of_change = pairlistconfig.get('max_rate_of_change', None)
2020-11-21 14:29:11 +00:00
self._refresh_period = pairlistconfig.get('refresh_period', 1440)
self._pair_cache: TTLCache = TTLCache(maxsize=1000, ttl=self._refresh_period)
2020-11-21 14:29:11 +00:00
if self._days < 1:
raise OperationalException("RangeStabilityFilter requires lookback_days to be >= 1")
if self._days > exchange.ohlcv_candle_limit('1d'):
raise OperationalException("RangeStabilityFilter requires lookback_days to not "
2020-11-21 15:01:52 +00:00
"exceed exchange max request size "
f"({exchange.ohlcv_candle_limit('1d')})")
2020-11-21 14:29:11 +00:00
@property
def needstickers(self) -> bool:
"""
Boolean property defining if tickers are necessary.
If no Pairlist requires tickers, an empty List is passed
as tickers argument to filter_pairlist
"""
return False
2020-11-21 14:29:11 +00:00
def short_desc(self) -> str:
"""
Short whitelist method description - used for startup-messages
"""
max_rate_desc = ""
if self._max_rate_of_change:
max_rate_desc = (f" and above {self._max_rate_of_change}")
return (f"{self.name} - Filtering pairs with rate of change below "
f"{self._min_rate_of_change}{max_rate_desc} over the "
f"last {plural(self._days, 'day')}.")
2020-11-21 14:29:11 +00:00
def filter_pairlist(self, pairlist: List[str], tickers: Dict) -> List[str]:
"""
Validate trading range
:param pairlist: pairlist to filter or sort
:param tickers: Tickers (from exchange.get_tickers()). May be cached.
:return: new allowlist
"""
needed_pairs = [(p, '1d') for p in pairlist if p not in self._pair_cache]
2021-07-05 08:39:22 +00:00
since_ms = (arrow.utcnow()
.floor('day')
.shift(days=-self._days - 1)
.int_timestamp) * 1000
# Get all candles
candles = {}
if needed_pairs:
candles = self._exchange.refresh_latest_ohlcv(needed_pairs, since_ms=since_ms,
cache=False)
if self._enabled:
for p in deepcopy(pairlist):
daily_candles = candles[(p, '1d')] if (p, '1d') in candles else None
if not self._validate_pair_loc(p, daily_candles):
pairlist.remove(p)
return pairlist
2020-12-15 19:59:58 +00:00
def _validate_pair_loc(self, pair: str, daily_candles: Optional[DataFrame]) -> bool:
2020-11-21 14:29:11 +00:00
"""
Validate trading range
:param pair: Pair that's currently validated
2021-05-16 18:34:02 +00:00
:param ticker: ticker dict as returned from ccxt.fetch_tickers()
:return: True if the pair can stay, false if it should be removed
2020-11-21 14:29:11 +00:00
"""
# Check symbol in cache
cached_res = self._pair_cache.get(pair, None)
if cached_res is not None:
return cached_res
2020-11-21 14:29:11 +00:00
result = False
if daily_candles is not None and not daily_candles.empty:
2020-11-21 14:29:11 +00:00
highest_high = daily_candles['high'].max()
lowest_low = daily_candles['low'].min()
2020-11-22 14:50:44 +00:00
pct_change = ((highest_high - lowest_low) / lowest_low) if lowest_low > 0 else 0
if pct_change >= self._min_rate_of_change:
2020-11-21 14:29:11 +00:00
result = True
else:
2020-11-22 10:49:41 +00:00
self.log_once(f"Removed {pair} from whitelist, because rate of change "
f"over {self._days} {plural(self._days, 'day')} is {pct_change:.3f}, "
2020-11-22 10:49:41 +00:00
f"which is below the threshold of {self._min_rate_of_change}.",
logger.info)
2020-11-21 14:29:11 +00:00
result = False
2021-08-02 02:49:49 +00:00
if self._max_rate_of_change:
if pct_change <= self._max_rate_of_change:
result = True
else:
self.log_once(
f"Removed {pair} from whitelist, because rate of change "
f"over {self._days} {plural(self._days, 'day')} is {pct_change:.3f}, "
f"which is above the threshold of {self._max_rate_of_change}.",
logger.info)
result = False
2020-11-21 14:29:11 +00:00
self._pair_cache[pair] = result
2021-08-15 17:28:36 +00:00
else:
self.log_once(f"Removed {pair} from whitelist, no candles found.", logger.info)
2020-11-21 14:29:11 +00:00
return result