Merge pull request #3619 from hroff-1902/cleanup_agefilter

Cleanup AgeFilter, PriceFilter
This commit is contained in:
Matthias 2020-08-16 09:13:11 +02:00 committed by GitHub
commit cffac3f7b6
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
4 changed files with 58 additions and 24 deletions

View File

@ -663,24 +663,28 @@ Filters low-value coins which would not allow setting stoplosses.
#### PriceFilter #### PriceFilter
The `PriceFilter` allows filtering of pairs by price. Currently the following price filters are supported: The `PriceFilter` allows filtering of pairs by price. Currently the following price filters are supported:
* `min_price` * `min_price`
* `max_price` * `max_price`
* `low_price_ratio` * `low_price_ratio`
The `min_price` setting removes pairs where the price is below the specified price. This is useful if you wish to avoid trading very low-priced pairs. The `min_price` setting removes pairs where the price is below the specified price. This is useful if you wish to avoid trading very low-priced pairs.
This option is disabled by default, and will only apply if set to <> 0. This option is disabled by default, and will only apply if set to > 0.
The `max_price` setting removes pairs where the price is above the specified price. This is useful if you wish to trade only low-priced pairs. The `max_price` setting removes pairs where the price is above the specified price. This is useful if you wish to trade only low-priced pairs.
This option is disabled by default, and will only apply if set to <> 0. This option is disabled by default, and will only apply if set to > 0.
The `low_price_ratio` setting removes pairs where a raise of 1 price unit (pip) is above the `low_price_ratio` ratio. The `low_price_ratio` setting removes pairs where a raise of 1 price unit (pip) is above the `low_price_ratio` ratio.
This option is disabled by default, and will only apply if set to <> 0. This option is disabled by default, and will only apply if set to > 0.
For `PriceFiler` at least one of its `min_price`, `max_price` or `low_price_ratio` settings must be applied.
Calculation example: Calculation example:
Min price precision is 8 decimals. If price is 0.00000011 - one step would be 0.00000012 - which is almost 10% higher than the previous value. Min price precision for SHITCOIN/BTC is 8 decimals. If its price is 0.00000011 - one price step above would be 0.00000012, which is ~9% higher than the previous price value. You may filter out this pair by using PriceFilter with `low_price_ratio` set to 0.09 (9%) or with `min_price` set to 0.00000011, correspondingly.
These pairs are dangerous since it may be impossible to place the desired stoploss - and often result in high losses. !!! Warning "Low priced pairs"
Low priced pairs with high "1 pip movements" are dangerous since they are often illiquid and it may also be impossible to place the desired stoploss, which can often result in high losses since price needs to be rounded to the next tradable price - so instead of having a stoploss of -5%, you could end up with a stoploss of -9% simply due to price rounding.
#### ShuffleFilter #### ShuffleFilter

View File

@ -26,12 +26,11 @@ class AgeFilter(IPairList):
self._min_days_listed = pairlistconfig.get('min_days_listed', 10) self._min_days_listed = pairlistconfig.get('min_days_listed', 10)
if self._min_days_listed < 1: if self._min_days_listed < 1:
raise OperationalException("AgeFilter requires min_days_listed must be >= 1") raise OperationalException("AgeFilter requires min_days_listed to be >= 1")
if self._min_days_listed > exchange.ohlcv_candle_limit: if self._min_days_listed > exchange.ohlcv_candle_limit:
raise OperationalException("AgeFilter requires min_days_listed must not exceed " raise OperationalException("AgeFilter requires min_days_listed to not exceed "
"exchange max request size " "exchange max request size "
f"({exchange.ohlcv_candle_limit})") f"({exchange.ohlcv_candle_limit})")
self._enabled = self._min_days_listed >= 1
@property @property
def needstickers(self) -> bool: def needstickers(self) -> bool:

View File

@ -4,6 +4,7 @@ Price pair list filter
import logging import logging
from typing import Any, Dict from typing import Any, Dict
from freqtrade.exceptions import OperationalException
from freqtrade.pairlist.IPairList import IPairList from freqtrade.pairlist.IPairList import IPairList
@ -18,11 +19,17 @@ class PriceFilter(IPairList):
super().__init__(exchange, pairlistmanager, config, pairlistconfig, pairlist_pos) super().__init__(exchange, pairlistmanager, config, pairlistconfig, pairlist_pos)
self._low_price_ratio = pairlistconfig.get('low_price_ratio', 0) self._low_price_ratio = pairlistconfig.get('low_price_ratio', 0)
if self._low_price_ratio < 0:
raise OperationalException("PriceFilter requires low_price_ratio to be >= 0")
self._min_price = pairlistconfig.get('min_price', 0) self._min_price = pairlistconfig.get('min_price', 0)
if self._min_price < 0:
raise OperationalException("PriceFilter requires min_price to be >= 0")
self._max_price = pairlistconfig.get('max_price', 0) self._max_price = pairlistconfig.get('max_price', 0)
self._enabled = ((self._low_price_ratio != 0) or if self._max_price < 0:
(self._min_price != 0) or raise OperationalException("PriceFilter requires max_price to be >= 0")
(self._max_price != 0)) self._enabled = ((self._low_price_ratio > 0) or
(self._min_price > 0) or
(self._max_price > 0))
@property @property
def needstickers(self) -> bool: def needstickers(self) -> bool:

View File

@ -549,7 +549,7 @@ def test_agefilter_min_days_listed_too_small(mocker, default_conf, markets, tick
) )
with pytest.raises(OperationalException, with pytest.raises(OperationalException,
match=r'AgeFilter requires min_days_listed must be >= 1'): match=r'AgeFilter requires min_days_listed to be >= 1'):
get_patched_freqtradebot(mocker, default_conf) get_patched_freqtradebot(mocker, default_conf)
@ -564,7 +564,7 @@ def test_agefilter_min_days_listed_too_large(mocker, default_conf, markets, tick
) )
with pytest.raises(OperationalException, with pytest.raises(OperationalException,
match=r'AgeFilter requires min_days_listed must not exceed ' match=r'AgeFilter requires min_days_listed to not exceed '
r'exchange max request size \([0-9]+\)'): r'exchange max request size \([0-9]+\)'):
get_patched_freqtradebot(mocker, default_conf) get_patched_freqtradebot(mocker, default_conf)
@ -592,34 +592,58 @@ def test_agefilter_caching(mocker, markets, whitelist_conf_3, tickers, ohlcv_his
assert freqtrade.exchange.get_historic_ohlcv.call_count == previous_call_count assert freqtrade.exchange.get_historic_ohlcv.call_count == previous_call_count
@pytest.mark.parametrize("pairlistconfig,expected", [ @pytest.mark.parametrize("pairlistconfig,desc_expected,exception_expected", [
({"method": "PriceFilter", "low_price_ratio": 0.001, "min_price": 0.00000010, ({"method": "PriceFilter", "low_price_ratio": 0.001, "min_price": 0.00000010,
"max_price": 1.0}, "[{'PriceFilter': 'PriceFilter - Filtering pairs priced below " "max_price": 1.0},
"0.1% or below 0.00000010 or above 1.00000000.'}]" "[{'PriceFilter': 'PriceFilter - Filtering pairs priced below "
"0.1% or below 0.00000010 or above 1.00000000.'}]",
None
), ),
({"method": "PriceFilter", "low_price_ratio": 0.001, "min_price": 0.00000010}, ({"method": "PriceFilter", "low_price_ratio": 0.001, "min_price": 0.00000010},
"[{'PriceFilter': 'PriceFilter - Filtering pairs priced below 0.1% or below 0.00000010.'}]" "[{'PriceFilter': 'PriceFilter - Filtering pairs priced below 0.1% or below 0.00000010.'}]",
None
), ),
({"method": "PriceFilter", "low_price_ratio": 0.001, "max_price": 1.00010000}, ({"method": "PriceFilter", "low_price_ratio": 0.001, "max_price": 1.00010000},
"[{'PriceFilter': 'PriceFilter - Filtering pairs priced below 0.1% or above 1.00010000.'}]" "[{'PriceFilter': 'PriceFilter - Filtering pairs priced below 0.1% or above 1.00010000.'}]",
None
), ),
({"method": "PriceFilter", "min_price": 0.00002000}, ({"method": "PriceFilter", "min_price": 0.00002000},
"[{'PriceFilter': 'PriceFilter - Filtering pairs priced below 0.00002000.'}]" "[{'PriceFilter': 'PriceFilter - Filtering pairs priced below 0.00002000.'}]",
None
), ),
({"method": "PriceFilter"}, ({"method": "PriceFilter"},
"[{'PriceFilter': 'PriceFilter - No price filters configured.'}]" "[{'PriceFilter': 'PriceFilter - No price filters configured.'}]",
None
), ),
({"method": "PriceFilter", "low_price_ratio": -0.001},
None,
"PriceFilter requires low_price_ratio to be >= 0"
), # OperationalException expected
({"method": "PriceFilter", "min_price": -0.00000010},
None,
"PriceFilter requires min_price to be >= 0"
), # OperationalException expected
({"method": "PriceFilter", "max_price": -1.00010000},
None,
"PriceFilter requires max_price to be >= 0"
), # OperationalException expected
]) ])
def test_pricefilter_desc(mocker, whitelist_conf, markets, pairlistconfig, expected): def test_pricefilter_desc(mocker, whitelist_conf, markets, pairlistconfig,
desc_expected, exception_expected):
mocker.patch.multiple('freqtrade.exchange.Exchange', mocker.patch.multiple('freqtrade.exchange.Exchange',
markets=PropertyMock(return_value=markets), markets=PropertyMock(return_value=markets),
exchange_has=MagicMock(return_value=True) exchange_has=MagicMock(return_value=True)
) )
whitelist_conf['pairlists'] = [pairlistconfig] whitelist_conf['pairlists'] = [pairlistconfig]
if desc_expected is not None:
freqtrade = get_patched_freqtradebot(mocker, whitelist_conf) freqtrade = get_patched_freqtradebot(mocker, whitelist_conf)
short_desc = str(freqtrade.pairlists.short_desc()) short_desc = str(freqtrade.pairlists.short_desc())
assert short_desc == expected assert short_desc == desc_expected
else: # OperationalException expected
with pytest.raises(OperationalException,
match=exception_expected):
freqtrade = get_patched_freqtradebot(mocker, whitelist_conf)
def test_pairlistmanager_no_pairlist(mocker, markets, whitelist_conf, caplog): def test_pairlistmanager_no_pairlist(mocker, markets, whitelist_conf, caplog):