From 638bed3dac45206eb001e0512aeb938ae063c20d Mon Sep 17 00:00:00 2001 From: user Date: Wed, 7 Jul 2021 06:46:51 +0000 Subject: [PATCH 1/6] Add RangeStabilityFilterMax pairlist filter --- freqtrade/constants.py | 2 +- .../pairlist/rangestabilityfiltermax.py | 109 ++++++++++++++++++ 2 files changed, 110 insertions(+), 1 deletion(-) create mode 100644 freqtrade/plugins/pairlist/rangestabilityfiltermax.py diff --git a/freqtrade/constants.py b/freqtrade/constants.py index f4c32387b..089569842 100644 --- a/freqtrade/constants.py +++ b/freqtrade/constants.py @@ -28,7 +28,7 @@ HYPEROPT_LOSS_BUILTIN = ['ShortTradeDurHyperOptLoss', 'OnlyProfitHyperOptLoss', AVAILABLE_PAIRLISTS = ['StaticPairList', 'VolumePairList', 'AgeFilter', 'PerformanceFilter', 'PrecisionFilter', 'PriceFilter', 'RangeStabilityFilter', 'ShuffleFilter', - 'SpreadFilter', 'VolatilityFilter'] + 'SpreadFilter', 'VolatilityFilter', 'RangeStabilityFilterMax'] AVAILABLE_PROTECTIONS = ['CooldownPeriod', 'LowProfitPairs', 'MaxDrawdown', 'StoplossGuard'] AVAILABLE_DATAHANDLERS = ['json', 'jsongz', 'hdf5'] DRY_RUN_WALLET = 1000 diff --git a/freqtrade/plugins/pairlist/rangestabilityfiltermax.py b/freqtrade/plugins/pairlist/rangestabilityfiltermax.py new file mode 100644 index 000000000..98f65904b --- /dev/null +++ b/freqtrade/plugins/pairlist/rangestabilityfiltermax.py @@ -0,0 +1,109 @@ +""" +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("RangeStabilityFilter requires lookback_days to be >= 1") + if self._days > exchange.ohlcv_candle_limit('1d'): + raise OperationalException("RangeStabilityFilter 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 From 8b485d197a6778499f362275ce0c521bca2778a3 Mon Sep 17 00:00:00 2001 From: sauces1313 Date: Wed, 7 Jul 2021 07:54:17 +0000 Subject: [PATCH 2/6] Update Plugins doc --- docs/includes/pairlists.md | 18 ++++++++++++++++++ 1 file changed, 18 insertions(+) diff --git a/docs/includes/pairlists.md b/docs/includes/pairlists.md index f19c5a181..0368e8a6f 100644 --- a/docs/includes/pairlists.md +++ b/docs/includes/pairlists.md @@ -174,6 +174,24 @@ If the trading range over the last 10 days is <1%, remove the pair from the whit !!! 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. +#### 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 Volatility is the degree of historical variation of a pairs over time, is is measured by the standard deviation of logarithmic daily returns. Returns are assumed to be normally distributed, although actual distribution might be different. In a normal distribution, 68% of observations fall within one standard deviation and 95% of observations fall within two standard deviations. Assuming a volatility of 0.05 means that the expected returns for 20 out of 30 days is expected to be less than 5% (one standard deviation). Volatility is a positive ratio of the expected deviation of return and can be greater than 1.00. Please refer to the wikipedia definition of [`volatility`](https://en.wikipedia.org/wiki/Volatility_(finance)). From 8b0a02db8eef64840ac57ba6a5313047f69f6bdb Mon Sep 17 00:00:00 2001 From: sauces1313 Date: Wed, 7 Jul 2021 08:11:13 +0000 Subject: [PATCH 3/6] Correct exception messages --- freqtrade/plugins/pairlist/rangestabilityfiltermax.py | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/freqtrade/plugins/pairlist/rangestabilityfiltermax.py b/freqtrade/plugins/pairlist/rangestabilityfiltermax.py index 98f65904b..e0cf5b9b4 100644 --- a/freqtrade/plugins/pairlist/rangestabilityfiltermax.py +++ b/freqtrade/plugins/pairlist/rangestabilityfiltermax.py @@ -31,9 +31,9 @@ class RangeStabilityFilterMax(IPairList): self._pair_cache: TTLCache = TTLCache(maxsize=1000, ttl=self._refresh_period) if self._days < 1: - raise OperationalException("RangeStabilityFilter requires lookback_days to be >= 1") + raise OperationalException("RangeStabilityFilterMax requires lookback_days to be >= 1") if self._days > exchange.ohlcv_candle_limit('1d'): - raise OperationalException("RangeStabilityFilter requires lookback_days to not " + raise OperationalException("RangeStabilityFilterMax requires lookback_days to not " "exceed exchange max request size " f"({exchange.ohlcv_candle_limit('1d')})") From 34c8a5afaff2c06fc0a09df0d4eba6ad2464bcc6 Mon Sep 17 00:00:00 2001 From: sauces1313 Date: Sun, 25 Jul 2021 07:24:55 +0000 Subject: [PATCH 4/6] remove second filter, add max option --- docs/includes/pairlists.md | 24 +--- freqtrade/constants.py | 2 +- .../plugins/pairlist/rangestabilityfilter.py | 12 +- .../pairlist/rangestabilityfiltermax.py | 109 ------------------ tests/plugins/test_pairlist.py | 18 +-- 5 files changed, 26 insertions(+), 139 deletions(-) delete mode 100644 freqtrade/plugins/pairlist/rangestabilityfiltermax.py diff --git a/docs/includes/pairlists.md b/docs/includes/pairlists.md index 0368e8a6f..e9d2f45e7 100644 --- a/docs/includes/pairlists.md +++ b/docs/includes/pairlists.md @@ -155,10 +155,10 @@ If `DOGE/BTC` maximum bid is 0.00000026 and minimum ask is 0.00000027, the ratio #### 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: -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 "pairlists": [ @@ -166,6 +166,7 @@ If the trading range over the last 10 days is <1%, remove the pair from the whit "method": "RangeStabilityFilter", "lookback_days": 10, "min_rate_of_change": 0.01, + "max_rate_of_change": 0.99, "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 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. - -#### 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`). + Additionally, it can also be used to automatically remove pairs with extreme high/low variance over a given amount of time. #### VolatilityFilter diff --git a/freqtrade/constants.py b/freqtrade/constants.py index 089569842..f4c32387b 100644 --- a/freqtrade/constants.py +++ b/freqtrade/constants.py @@ -28,7 +28,7 @@ HYPEROPT_LOSS_BUILTIN = ['ShortTradeDurHyperOptLoss', 'OnlyProfitHyperOptLoss', AVAILABLE_PAIRLISTS = ['StaticPairList', 'VolumePairList', 'AgeFilter', 'PerformanceFilter', 'PrecisionFilter', 'PriceFilter', 'RangeStabilityFilter', 'ShuffleFilter', - 'SpreadFilter', 'VolatilityFilter', 'RangeStabilityFilterMax'] + 'SpreadFilter', 'VolatilityFilter'] AVAILABLE_PROTECTIONS = ['CooldownPeriod', 'LowProfitPairs', 'MaxDrawdown', 'StoplossGuard'] AVAILABLE_DATAHANDLERS = ['json', 'jsongz', 'hdf5'] DRY_RUN_WALLET = 1000 diff --git a/freqtrade/plugins/pairlist/rangestabilityfilter.py b/freqtrade/plugins/pairlist/rangestabilityfilter.py index a6d1820de..105568f17 100644 --- a/freqtrade/plugins/pairlist/rangestabilityfilter.py +++ b/freqtrade/plugins/pairlist/rangestabilityfilter.py @@ -26,6 +26,7 @@ class RangeStabilityFilter(IPairList): self._days = pairlistconfig.get('lookback_days', 10) 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._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 """ 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]: """ @@ -104,6 +106,14 @@ class RangeStabilityFilter(IPairList): f"which is below the threshold of {self._min_rate_of_change}.", logger.info) 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 return result diff --git a/freqtrade/plugins/pairlist/rangestabilityfiltermax.py b/freqtrade/plugins/pairlist/rangestabilityfiltermax.py deleted file mode 100644 index e0cf5b9b4..000000000 --- a/freqtrade/plugins/pairlist/rangestabilityfiltermax.py +++ /dev/null @@ -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 diff --git a/tests/plugins/test_pairlist.py b/tests/plugins/test_pairlist.py index ae8f6e958..550587da9 100644 --- a/tests/plugins/test_pairlist.py +++ b/tests/plugins/test_pairlist.py @@ -412,7 +412,7 @@ def test_VolumePairList_refresh_empty(mocker, markets_empty, whitelist_conf): "USDT", ['NANO/USDT']), ([{"method": "StaticPairList"}, {"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']), ([{"method": "StaticPairList"}, {"method": "VolatilityFilter", "lookback_days": 3, @@ -718,15 +718,16 @@ def test_rangestabilityfilter_checks(mocker, default_conf, markets, tickers): get_patched_freqtradebot(mocker, default_conf) -@pytest.mark.parametrize('min_rate_of_change,expected_length', [ - (0.01, 5), - (0.05, 0), # Setting rate_of_change to 5% removes all pairs from the whitelist. +@pytest.mark.parametrize('min_rate_of_change,max_rate_of_change,expected_length', [ + (0.01, 0.99, 5), + (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, - min_rate_of_change, expected_length): + min_rate_of_change, max_rate_of_change, expected_length): default_conf['pairlists'] = [{'method': 'VolumePairList', 'number_assets': 10}, {'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', markets=PropertyMock(return_value=markets), @@ -828,9 +829,10 @@ def test_spreadfilter_invalid_data(mocker, default_conf, markets, tickers, caplo None, "PriceFilter requires max_value to be >= 0" ), # 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 " - "0.01 over the last days.'}]", + "0.01 and above 0.99 over the last days.'}]", None ), ]) From 059c32b0675607986af0dc2c00738d3b4106bcce Mon Sep 17 00:00:00 2001 From: sauces1313 Date: Mon, 2 Aug 2021 02:49:49 +0000 Subject: [PATCH 5/6] Check for and default to 'None' --- .../plugins/pairlist/rangestabilityfilter.py | 20 ++++++++++--------- 1 file changed, 11 insertions(+), 9 deletions(-) diff --git a/freqtrade/plugins/pairlist/rangestabilityfilter.py b/freqtrade/plugins/pairlist/rangestabilityfilter.py index 105568f17..63184c726 100644 --- a/freqtrade/plugins/pairlist/rangestabilityfilter.py +++ b/freqtrade/plugins/pairlist/rangestabilityfilter.py @@ -26,7 +26,7 @@ class RangeStabilityFilter(IPairList): self._days = pairlistconfig.get('lookback_days', 10) 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._max_rate_of_change = pairlistconfig.get('max_rate_of_change', None) self._refresh_period = pairlistconfig.get('refresh_period', 1440) self._pair_cache: TTLCache = TTLCache(maxsize=1000, ttl=self._refresh_period) @@ -106,14 +106,16 @@ class RangeStabilityFilter(IPairList): f"which is below the threshold of {self._min_rate_of_change}.", logger.info) 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 + 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 self._pair_cache[pair] = result return result From b63eda3a2b7e694a32d524c0413a30f737ef9d8d Mon Sep 17 00:00:00 2001 From: Matthias Date: Mon, 2 Aug 2021 07:15:09 +0200 Subject: [PATCH 6/6] Some minor cleanup and improved test coverage --- freqtrade/plugins/pairlist/rangestabilityfilter.py | 7 +++++-- tests/plugins/test_pairlist.py | 12 +++++++++++- 2 files changed, 16 insertions(+), 3 deletions(-) diff --git a/freqtrade/plugins/pairlist/rangestabilityfilter.py b/freqtrade/plugins/pairlist/rangestabilityfilter.py index 63184c726..ef7f2cbcb 100644 --- a/freqtrade/plugins/pairlist/rangestabilityfilter.py +++ b/freqtrade/plugins/pairlist/rangestabilityfilter.py @@ -51,9 +51,12 @@ class RangeStabilityFilter(IPairList): """ 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} and above " - f"{self._max_rate_of_change} over the last {plural(self._days, 'day')}.") + f"{self._min_rate_of_change}{max_rate_desc} over the " + f"last {plural(self._days, 'day')}.") def filter_pairlist(self, pairlist: List[str], tickers: Dict) -> List[str]: """ diff --git a/tests/plugins/test_pairlist.py b/tests/plugins/test_pairlist.py index a3f5b0875..5f0701a22 100644 --- a/tests/plugins/test_pairlist.py +++ b/tests/plugins/test_pairlist.py @@ -425,8 +425,12 @@ def test_VolumePairList_refresh_empty(mocker, markets_empty, whitelist_conf): "USDT", ['NANO/USDT']), ([{"method": "StaticPairList"}, {"method": "RangeStabilityFilter", "lookback_days": 10, - "min_rate_of_change": 0.01, "max_rate_of_change": 0.99, "refresh_period": 1440}], + "min_rate_of_change": 0.01, "refresh_period": 1440}], "BTC", ['ETH/BTC', 'TKN/BTC', 'HOT/BTC']), + ([{"method": "StaticPairList"}, + {"method": "RangeStabilityFilter", "lookback_days": 10, + "max_rate_of_change": 0.01, "refresh_period": 1440}], + "BTC", []), # All removed because of max_rate_of_change being 0.017 ([{"method": "StaticPairList"}, {"method": "VolatilityFilter", "lookback_days": 3, "min_volatility": 0.002, "max_volatility": 0.004, "refresh_period": 1440}], @@ -985,6 +989,12 @@ def test_spreadfilter_invalid_data(mocker, default_conf, markets, tickers, caplo None, "PriceFilter requires max_value to be >= 0" ), # OperationalException expected + ({"method": "RangeStabilityFilter", "lookback_days": 10, + "min_rate_of_change": 0.01}, + "[{'RangeStabilityFilter': 'RangeStabilityFilter - Filtering pairs with rate of change below " + "0.01 over the last days.'}]", + None + ), ({"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 "