diff --git a/config_full.json.example b/config_full.json.example index 0d82b9a2b..365b6180b 100644 --- a/config_full.json.example +++ b/config_full.json.example @@ -69,8 +69,8 @@ {"method": "PriceFilter", "low_price_ratio": 0.01, "min_price": 0.00000010}, {"method": "SpreadFilter", "max_spread_ratio": 0.005}, { - "method": "VolatilityFilter", - "volatility_over_days": 10, + "method": "RangeStabilityFilter", + "lookback_days": 10, "min_volatility": 0.01, "refresh_period": 1440 } diff --git a/docs/includes/pairlists.md b/docs/includes/pairlists.md index 7cd2369b1..149e784bd 100644 --- a/docs/includes/pairlists.md +++ b/docs/includes/pairlists.md @@ -19,7 +19,7 @@ Inactive markets are always removed from the resulting pairlist. Explicitly blac * [`PriceFilter`](#pricefilter) * [`ShuffleFilter`](#shufflefilter) * [`SpreadFilter`](#spreadfilter) -* [`VolatilityFilter`](#volatilityfilter) +* [`RangeStabilityFilter`](#rangestabilityfilter) !!! Tip "Testing pairlists" Pairlist configurations can be quite tricky to get right. Best use the [`test-pairlist`](utils.md#test-pairlist) utility sub-command to test your configuration quickly. @@ -119,26 +119,26 @@ Example: If `DOGE/BTC` maximum bid is 0.00000026 and minimum ask is 0.00000027, the ratio is calculated as: `1 - bid/ask ~= 0.037` which is `> 0.005` and this pair will be filtered out. -#### VolatilityFilter +#### RangeStabilityFilter -Removes pairs where the difference between lowest low and highest high over `volatility_over_days` days is below `min_volatility`. 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`. Since this is a filter that requires additional data, the results are cached for `refresh_period`. In the below example: -If volatility over the last 10 days is <1%, remove the pair from the whitelist. +If the trading range over the last 10 days is <1%, remove the pair from the whitelist. ```json "pairlists": [ { - "method": "VolatilityFilter", - "volatility_over_days": 10, - "min_volatility": 0.01, + "method": "RangeStabilityFilter", + "lookback_days": 10, + "min_rate_of_change": 0.01, "refresh_period": 1440 } ] ``` !!! Tip - This Filter can be used to automatically remove stable coin pairs, which have a very low volatility, 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. ### Full example of Pairlist Handlers @@ -160,9 +160,9 @@ The below example blacklists `BNB/BTC`, uses `VolumePairList` with `20` assets, {"method": "PriceFilter", "low_price_ratio": 0.01}, {"method": "SpreadFilter", "max_spread_ratio": 0.005}, { - "method": "VolatilityFilter", - "volatility_over_days": 10, - "min_volatility": 0.01, + "method": "RangeStabilityFilter", + "lookback_days": 10, + "min_rate_of_change": 0.01, "refresh_period": 1440 }, {"method": "ShuffleFilter", "seed": 42} diff --git a/freqtrade/constants.py b/freqtrade/constants.py index 55d802587..2022556d2 100644 --- a/freqtrade/constants.py +++ b/freqtrade/constants.py @@ -25,7 +25,7 @@ HYPEROPT_LOSS_BUILTIN = ['ShortTradeDurHyperOptLoss', 'OnlyProfitHyperOptLoss', 'SortinoHyperOptLoss', 'SortinoHyperOptLossDaily'] AVAILABLE_PAIRLISTS = ['StaticPairList', 'VolumePairList', 'AgeFilter', 'PrecisionFilter', 'PriceFilter', - 'ShuffleFilter', 'SpreadFilter', 'VolatilityFilter'] + 'RangeStabilityFilter', 'ShuffleFilter', 'SpreadFilter'] AVAILABLE_DATAHANDLERS = ['json', 'jsongz', 'hdf5'] DRY_RUN_WALLET = 1000 DATETIME_PRINT_FORMAT = '%Y-%m-%d %H:%M:%S' diff --git a/freqtrade/pairlist/volatilityfilter.py b/freqtrade/pairlist/rangestabilityfilter.py similarity index 77% rename from freqtrade/pairlist/volatilityfilter.py rename to freqtrade/pairlist/rangestabilityfilter.py index 14ac0c617..f428bb113 100644 --- a/freqtrade/pairlist/volatilityfilter.py +++ b/freqtrade/pairlist/rangestabilityfilter.py @@ -15,23 +15,23 @@ from freqtrade.pairlist.IPairList import IPairList logger = logging.getLogger(__name__) -class VolatilityFilter(IPairList): +class RangeStabilityFilter(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('volatility_over_days', 10) - self._min_volatility = pairlistconfig.get('min_volatility', 0.01) + self._days = pairlistconfig.get('lookback_days', 10) + self._min_rate_of_change = pairlistconfig.get('min_rate_of_change', 0.01) self._refresh_period = pairlistconfig.get('refresh_period', 1440) self._pair_cache: TTLCache = TTLCache(maxsize=100, ttl=self._refresh_period) if self._days < 1: - raise OperationalException("VolatilityFilter requires volatility_over_days to be >= 1") + raise OperationalException("RangeStabilityFilter requires lookback_days to be >= 1") if self._days > exchange.ohlcv_candle_limit: - raise OperationalException("VolatilityFilter requires volatility_over_days to not " + raise OperationalException("RangeStabilityFilter requires lookback_days to not " "exceed exchange max request size " f"({exchange.ohlcv_candle_limit})") @@ -48,12 +48,12 @@ class VolatilityFilter(IPairList): """ Short whitelist method description - used for startup-messages """ - return (f"{self.name} - Filtering pairs with volatility below {self._min_volatility} " - f"over the last {plural(self._days, 'day')}.") + return (f"{self.name} - Filtering pairs with rate of change below " + f"{self._min_rate_of_change} over the last {plural(self._days, 'day')}.") def _validate_pair(self, ticker: Dict) -> bool: """ - Validate volatility + Validate trading range :param ticker: ticker dict as returned from ccxt.load_markets() :return: True if the pair can stay, False if it should be removed """ @@ -75,14 +75,14 @@ class VolatilityFilter(IPairList): 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._min_volatility: + if pct_change >= self._min_rate_of_change: result = True else: self.log_on_refresh(logger.info, f"Removed {pair} from whitelist, " - f"because volatility over {plural(self._days, 'day')} is " + f"because rate of change over {plural(self._days, 'day')} is " f"{pct_change:.3f}, which is below the " - f"threshold of {self._min_volatility}.") + f"threshold of {self._min_rate_of_change}.") result = False self._pair_cache[pair] = result diff --git a/tests/pairlist/test_pairlist.py b/tests/pairlist/test_pairlist.py index e9df5d3f4..d696e6d02 100644 --- a/tests/pairlist/test_pairlist.py +++ b/tests/pairlist/test_pairlist.py @@ -341,8 +341,8 @@ def test_VolumePairList_refresh_empty(mocker, markets_empty, whitelist_conf): {"method": "PriceFilter", "low_price_ratio": 0.02}], "USDT", ['ETH/USDT', 'NANO/USDT']), ([{"method": "StaticPairList"}, - {"method": "VolatilityFilter", "volatility_over_days": 10, - "min_volatility": 0.01, "refresh_period": 1440}], + {"method": "RangeStabilityFilter", "lookback_days": 10, + "min_rate_of_change": 0.01, "refresh_period": 1440}], "BTC", ['ETH/BTC', 'TKN/BTC', 'HOT/BTC']), ]) def test_VolumePairList_whitelist_gen(mocker, whitelist_conf, shitcoinmarkets, tickers, @@ -586,9 +586,9 @@ def test_agefilter_caching(mocker, markets, whitelist_conf_agefilter, tickers, o assert freqtrade.exchange.get_historic_ohlcv.call_count == previous_call_count -def test_volatilityfilter_checks(mocker, default_conf, markets, tickers): +def test_rangestabilityfilter_checks(mocker, default_conf, markets, tickers): default_conf['pairlists'] = [{'method': 'VolumePairList', 'number_assets': 10}, - {'method': 'VolatilityFilter', 'volatility_over_days': 99999}] + {'method': 'RangeStabilityFilter', 'lookback_days': 99999}] mocker.patch.multiple('freqtrade.exchange.Exchange', markets=PropertyMock(return_value=markets), @@ -597,27 +597,27 @@ def test_volatilityfilter_checks(mocker, default_conf, markets, tickers): ) with pytest.raises(OperationalException, - match=r'VolatilityFilter requires volatility_over_days to not exceed ' + match=r'RangeStabilityFilter requires lookback_days to not exceed ' r'exchange max request size \([0-9]+\)'): get_patched_freqtradebot(mocker, default_conf) default_conf['pairlists'] = [{'method': 'VolumePairList', 'number_assets': 10}, - {'method': 'VolatilityFilter', 'volatility_over_days': 0}] + {'method': 'RangeStabilityFilter', 'lookback_days': 0}] with pytest.raises(OperationalException, - match='VolatilityFilter requires volatility_over_days to be >= 1'): + match='RangeStabilityFilter requires lookback_days to be >= 1'): get_patched_freqtradebot(mocker, default_conf) -@pytest.mark.parametrize('min_volatility,expected_length', [ +@pytest.mark.parametrize('min_rate_of_change,expected_length', [ (0.01, 5), - (0.05, 0), # Setting volatility to 5% removes all pairs from the whitelist. + (0.05, 0), # Setting rate_of_change to 5% removes all pairs from the whitelist. ]) -def test_volatilityfilter_caching(mocker, markets, default_conf, tickers, ohlcv_history_list, - min_volatility, expected_length): +def test_rangestabilityfilter_caching(mocker, markets, default_conf, tickers, ohlcv_history_list, + min_rate_of_change, expected_length): default_conf['pairlists'] = [{'method': 'VolumePairList', 'number_assets': 10}, - {'method': 'VolatilityFilter', 'volatility_over_days': 2, - 'min_volatility': min_volatility}] + {'method': 'RangeStabilityFilter', 'lookback_days': 2, + 'min_rate_of_change': min_rate_of_change}] mocker.patch.multiple('freqtrade.exchange.Exchange', markets=PropertyMock(return_value=markets), @@ -677,9 +677,9 @@ def test_volatilityfilter_caching(mocker, markets, default_conf, tickers, ohlcv_ None, "PriceFilter requires max_price to be >= 0" ), # OperationalException expected - ({"method": "VolatilityFilter", "volatility_over_days": 10, "min_volatility": 0.01}, - "[{'VolatilityFilter': 'VolatilityFilter - Filtering pairs with volatility below 0.01 " - "over the last days.'}]", + ({"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 ), ])