remove second filter, add max option

This commit is contained in:
sauces1313 2021-07-25 07:24:55 +00:00
parent 8b0a02db8e
commit 34c8a5afaf
5 changed files with 26 additions and 139 deletions

View File

@ -155,10 +155,10 @@ If `DOGE/BTC` maximum bid is 0.00000026 and minimum ask is 0.00000027, the ratio
#### RangeStabilityFilter #### RangeStabilityFilter
Removes pairs where the difference between lowest low and highest high over `lookback_days` days is below `min_rate_of_change`. Since this is a filter that requires additional data, the results are cached for `refresh_period`. Removes pairs where the difference between lowest low and highest high over `lookback_days` days is below `min_rate_of_change` or above `max_rate_of_change`. Since this is a filter that requires additional data, the results are cached for `refresh_period`.
In the below example: In the below example:
If the trading range over the last 10 days is <1%, remove the pair from the whitelist. If the trading range over the last 10 days is <1% or >99%, remove the pair from the whitelist.
```json ```json
"pairlists": [ "pairlists": [
@ -166,6 +166,7 @@ If the trading range over the last 10 days is <1%, remove the pair from the whit
"method": "RangeStabilityFilter", "method": "RangeStabilityFilter",
"lookback_days": 10, "lookback_days": 10,
"min_rate_of_change": 0.01, "min_rate_of_change": 0.01,
"max_rate_of_change": 0.99,
"refresh_period": 1440 "refresh_period": 1440
} }
] ]
@ -173,24 +174,7 @@ If the trading range over the last 10 days is <1%, remove the pair from the whit
!!! Tip !!! Tip
This Filter can be used to automatically remove stable coin pairs, which have a very low trading range, and are therefore extremely difficult to trade with profit. This Filter can be used to automatically remove stable coin pairs, which have a very low trading range, and are therefore extremely difficult to trade with profit.
Additionally, it can also be used to automatically remove pairs with extreme high/low variance over a given amount of time.
#### RangeStabilityFilterMax
Same function as `RangeStabilityFilter` but instead of a minimum value, it uses a maximum value for rate of change, i.e. `max_rate_of_change` as seen in the example below.
```json
"pairlists": [
{
"method": "RangeStabilityFilterMax",
"lookback_days": 10,
"max_rate_of_change": 1.01,
"refresh_period": 1440
}
]
```
!!! Tip
This Filter can be used to automatically remove pairs with extreme high/low variance over a given amount of time (`lookback_days`).
#### VolatilityFilter #### VolatilityFilter

View File

@ -28,7 +28,7 @@ HYPEROPT_LOSS_BUILTIN = ['ShortTradeDurHyperOptLoss', 'OnlyProfitHyperOptLoss',
AVAILABLE_PAIRLISTS = ['StaticPairList', 'VolumePairList', AVAILABLE_PAIRLISTS = ['StaticPairList', 'VolumePairList',
'AgeFilter', 'PerformanceFilter', 'PrecisionFilter', 'AgeFilter', 'PerformanceFilter', 'PrecisionFilter',
'PriceFilter', 'RangeStabilityFilter', 'ShuffleFilter', 'PriceFilter', 'RangeStabilityFilter', 'ShuffleFilter',
'SpreadFilter', 'VolatilityFilter', 'RangeStabilityFilterMax'] 'SpreadFilter', 'VolatilityFilter']
AVAILABLE_PROTECTIONS = ['CooldownPeriod', 'LowProfitPairs', 'MaxDrawdown', 'StoplossGuard'] AVAILABLE_PROTECTIONS = ['CooldownPeriod', 'LowProfitPairs', 'MaxDrawdown', 'StoplossGuard']
AVAILABLE_DATAHANDLERS = ['json', 'jsongz', 'hdf5'] AVAILABLE_DATAHANDLERS = ['json', 'jsongz', 'hdf5']
DRY_RUN_WALLET = 1000 DRY_RUN_WALLET = 1000

View File

@ -26,6 +26,7 @@ class RangeStabilityFilter(IPairList):
self._days = pairlistconfig.get('lookback_days', 10) self._days = pairlistconfig.get('lookback_days', 10)
self._min_rate_of_change = pairlistconfig.get('min_rate_of_change', 0.01) self._min_rate_of_change = pairlistconfig.get('min_rate_of_change', 0.01)
self._max_rate_of_change = pairlistconfig.get('max_rate_of_change', 0.99)
self._refresh_period = pairlistconfig.get('refresh_period', 1440) self._refresh_period = pairlistconfig.get('refresh_period', 1440)
self._pair_cache: TTLCache = TTLCache(maxsize=1000, ttl=self._refresh_period) self._pair_cache: TTLCache = TTLCache(maxsize=1000, ttl=self._refresh_period)
@ -51,7 +52,8 @@ class RangeStabilityFilter(IPairList):
Short whitelist method description - used for startup-messages Short whitelist method description - used for startup-messages
""" """
return (f"{self.name} - Filtering pairs with rate of change below " return (f"{self.name} - Filtering pairs with rate of change below "
f"{self._min_rate_of_change} over the last {plural(self._days, 'day')}.") f"{self._min_rate_of_change} and above "
f"{self._max_rate_of_change} over the last {plural(self._days, 'day')}.")
def filter_pairlist(self, pairlist: List[str], tickers: Dict) -> List[str]: def filter_pairlist(self, pairlist: List[str], tickers: Dict) -> List[str]:
""" """
@ -104,6 +106,14 @@ class RangeStabilityFilter(IPairList):
f"which is below the threshold of {self._min_rate_of_change}.", f"which is below the threshold of {self._min_rate_of_change}.",
logger.info) logger.info)
result = False result = False
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
self._pair_cache[pair] = result self._pair_cache[pair] = result
return result return result

View File

@ -1,109 +0,0 @@
"""
Rate of change pairlist filter
"""
import logging
from copy import deepcopy
from typing import Any, Dict, List, Optional
import arrow
from cachetools.ttl import TTLCache
from pandas import DataFrame
from freqtrade.exceptions import OperationalException
from freqtrade.misc import plural
from freqtrade.plugins.pairlist.IPairList import IPairList
logger = logging.getLogger(__name__)
class RangeStabilityFilterMax(IPairList):
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._max_rate_of_change = pairlistconfig.get('max_rate_of_change', 0.02)
self._refresh_period = pairlistconfig.get('refresh_period', 1440)
self._pair_cache: TTLCache = TTLCache(maxsize=1000, ttl=self._refresh_period)
if self._days < 1:
raise OperationalException("RangeStabilityFilterMax requires lookback_days to be >= 1")
if self._days > exchange.ohlcv_candle_limit('1d'):
raise OperationalException("RangeStabilityFilterMax requires lookback_days to not "
"exceed exchange max request size "
f"({exchange.ohlcv_candle_limit('1d')})")
@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
def short_desc(self) -> str:
"""
Short whitelist method description - used for startup-messages
"""
return (f"{self.name} - Filtering pairs with rate of change below "
f"{self._max_rate_of_change} over the last {plural(self._days, 'day')}.")
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]
since_ms = int(arrow.utcnow()
.floor('day')
.shift(days=-self._days - 1)
.float_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
def _validate_pair_loc(self, pair: str, daily_candles: Optional[DataFrame]) -> bool:
"""
Validate trading range
:param pair: Pair that's currently validated
:param ticker: ticker dict as returned from ccxt.load_markets()
:return: True if the pair can stay, false if it should be removed
"""
# Check symbol in cache
cached_res = self._pair_cache.get(pair, None)
if cached_res is not None:
return cached_res
result = False
if daily_candles is not None and not daily_candles.empty:
highest_high = daily_candles['high'].max()
lowest_low = daily_candles['low'].min()
pct_change = ((highest_high - lowest_low) / lowest_low) if lowest_low > 0 else 0
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
self._pair_cache[pair] = result
return result

View File

@ -412,7 +412,7 @@ def test_VolumePairList_refresh_empty(mocker, markets_empty, whitelist_conf):
"USDT", ['NANO/USDT']), "USDT", ['NANO/USDT']),
([{"method": "StaticPairList"}, ([{"method": "StaticPairList"},
{"method": "RangeStabilityFilter", "lookback_days": 10, {"method": "RangeStabilityFilter", "lookback_days": 10,
"min_rate_of_change": 0.01, "refresh_period": 1440}], "min_rate_of_change": 0.01, "max_rate_of_change": 0.99, "refresh_period": 1440}],
"BTC", ['ETH/BTC', 'TKN/BTC', 'HOT/BTC']), "BTC", ['ETH/BTC', 'TKN/BTC', 'HOT/BTC']),
([{"method": "StaticPairList"}, ([{"method": "StaticPairList"},
{"method": "VolatilityFilter", "lookback_days": 3, {"method": "VolatilityFilter", "lookback_days": 3,
@ -718,15 +718,16 @@ def test_rangestabilityfilter_checks(mocker, default_conf, markets, tickers):
get_patched_freqtradebot(mocker, default_conf) get_patched_freqtradebot(mocker, default_conf)
@pytest.mark.parametrize('min_rate_of_change,expected_length', [ @pytest.mark.parametrize('min_rate_of_change,max_rate_of_change,expected_length', [
(0.01, 5), (0.01, 0.99, 5),
(0.05, 0), # Setting rate_of_change to 5% removes all pairs from the whitelist. (0.05, 0.0, 0), # Setting min rate_of_change to 5% removes all pairs from the whitelist.
]) ])
def test_rangestabilityfilter_caching(mocker, markets, default_conf, tickers, ohlcv_history, def test_rangestabilityfilter_caching(mocker, markets, default_conf, tickers, ohlcv_history,
min_rate_of_change, expected_length): min_rate_of_change, max_rate_of_change, expected_length):
default_conf['pairlists'] = [{'method': 'VolumePairList', 'number_assets': 10}, default_conf['pairlists'] = [{'method': 'VolumePairList', 'number_assets': 10},
{'method': 'RangeStabilityFilter', 'lookback_days': 2, {'method': 'RangeStabilityFilter', 'lookback_days': 2,
'min_rate_of_change': min_rate_of_change}] 'min_rate_of_change': min_rate_of_change,
"max_rate_of_change": max_rate_of_change}]
mocker.patch.multiple('freqtrade.exchange.Exchange', mocker.patch.multiple('freqtrade.exchange.Exchange',
markets=PropertyMock(return_value=markets), markets=PropertyMock(return_value=markets),
@ -828,9 +829,10 @@ def test_spreadfilter_invalid_data(mocker, default_conf, markets, tickers, caplo
None, None,
"PriceFilter requires max_value to be >= 0" "PriceFilter requires max_value to be >= 0"
), # OperationalException expected ), # OperationalException expected
({"method": "RangeStabilityFilter", "lookback_days": 10, "min_rate_of_change": 0.01}, ({"method": "RangeStabilityFilter", "lookback_days": 10,
"min_rate_of_change": 0.01, "max_rate_of_change": 0.99},
"[{'RangeStabilityFilter': 'RangeStabilityFilter - Filtering pairs with rate of change below " "[{'RangeStabilityFilter': 'RangeStabilityFilter - Filtering pairs with rate of change below "
"0.01 over the last days.'}]", "0.01 and above 0.99 over the last days.'}]",
None None
), ),
]) ])