From f6bde8bd9cbad8a50b75d5c26c16011b3d5448d0 Mon Sep 17 00:00:00 2001 From: hroff-1902 Date: Wed, 22 Jul 2020 21:43:15 +0300 Subject: [PATCH 01/11] Improve exception message wordings --- freqtrade/pairlist/AgeFilter.py | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/freqtrade/pairlist/AgeFilter.py b/freqtrade/pairlist/AgeFilter.py index 7b6b126c3..d199154ba 100644 --- a/freqtrade/pairlist/AgeFilter.py +++ b/freqtrade/pairlist/AgeFilter.py @@ -26,9 +26,9 @@ class AgeFilter(IPairList): self._min_days_listed = pairlistconfig.get('min_days_listed', 10) if self._min_days_listed < 1: - raise OperationalException("AgeFilter requires min_days_listed must be >= 1") + raise OperationalException("AgeFilter requires min_days_listed be >= 1") 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 be not exceeding " "exchange max request size " f"({exchange.ohlcv_candle_limit})") self._enabled = self._min_days_listed >= 1 From 5213abf510de8a3b43b671c9cfab65a6f81896b1 Mon Sep 17 00:00:00 2001 From: hroff-1902 Date: Wed, 22 Jul 2020 21:44:39 +0300 Subject: [PATCH 02/11] AgeFilter is always enabled --- freqtrade/pairlist/AgeFilter.py | 1 - 1 file changed, 1 deletion(-) diff --git a/freqtrade/pairlist/AgeFilter.py b/freqtrade/pairlist/AgeFilter.py index d199154ba..56e56ceeb 100644 --- a/freqtrade/pairlist/AgeFilter.py +++ b/freqtrade/pairlist/AgeFilter.py @@ -31,7 +31,6 @@ class AgeFilter(IPairList): raise OperationalException("AgeFilter requires min_days_listed be not exceeding " "exchange max request size " f"({exchange.ohlcv_candle_limit})") - self._enabled = self._min_days_listed >= 1 @property def needstickers(self) -> bool: From daee414d7a83e15123e025d4c6107ad0435f9d0f Mon Sep 17 00:00:00 2001 From: hroff-1902 Date: Wed, 22 Jul 2020 21:51:25 +0300 Subject: [PATCH 03/11] Fix docs formatting --- docs/configuration.md | 1 + 1 file changed, 1 insertion(+) diff --git a/docs/configuration.md b/docs/configuration.md index a200d6411..0e3d23927 100644 --- a/docs/configuration.md +++ b/docs/configuration.md @@ -663,6 +663,7 @@ Filters low-value coins which would not allow setting stoplosses. #### PriceFilter The `PriceFilter` allows filtering of pairs by price. Currently the following price filters are supported: + * `min_price` * `max_price` * `low_price_ratio` From a1e292f56a068a2183aa59e59036bb5ce8abe0c5 Mon Sep 17 00:00:00 2001 From: hroff-1902 Date: Wed, 22 Jul 2020 22:09:30 +0300 Subject: [PATCH 04/11] Improve docs --- docs/configuration.md | 12 +++++++----- 1 file changed, 7 insertions(+), 5 deletions(-) diff --git a/docs/configuration.md b/docs/configuration.md index 0e3d23927..f39a3c62d 100644 --- a/docs/configuration.md +++ b/docs/configuration.md @@ -669,19 +669,21 @@ The `PriceFilter` allows filtering of pairs by price. Currently the following pr * `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. -This option is disabled by default, and will only apply if set to <> 0. +This option is disabled by default (or set to 0), 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. -This option is disabled by default, and will only apply if set to <> 0. +This option is disabled by default (or set to 0), 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. -This option is disabled by default, and will only apply if set to <> 0. +This option is disabled by default (or set to 0), 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: -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. +Low priced pairs 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. Consider using PriceFilter with `low_price_ratio` set to a value which is less than the absolute value of your stoploss (for example, if your stoploss is -5% (-0.05), then the value for `low_price_ratio` can be 0.04 or even 0.02). #### ShuffleFilter From c78199d3d9fdd3c2a040a0fb476c053d60b6d7a1 Mon Sep 17 00:00:00 2001 From: hroff-1902 Date: Wed, 22 Jul 2020 22:21:30 +0300 Subject: [PATCH 05/11] Add checks for parameters of PriceFilter --- freqtrade/pairlist/PriceFilter.py | 13 ++++++++++--- 1 file changed, 10 insertions(+), 3 deletions(-) diff --git a/freqtrade/pairlist/PriceFilter.py b/freqtrade/pairlist/PriceFilter.py index b3b2f43dc..ae3ab9230 100644 --- a/freqtrade/pairlist/PriceFilter.py +++ b/freqtrade/pairlist/PriceFilter.py @@ -4,6 +4,7 @@ Price pair list filter import logging from typing import Any, Dict +from freqtrade.exceptions import OperationalException from freqtrade.pairlist.IPairList import IPairList @@ -18,11 +19,17 @@ class PriceFilter(IPairList): super().__init__(exchange, pairlistmanager, config, pairlistconfig, pairlist_pos) self._low_price_ratio = pairlistconfig.get('low_price_ratio', 0) + if self._low_price_ratio < 0: + raise OperationalException("PriceFilter requires low_price_ratio be >= 0") self._min_price = pairlistconfig.get('min_price', 0) + if self._min_price < 0: + raise OperationalException("PriceFilter requires min_price be >= 0") self._max_price = pairlistconfig.get('max_price', 0) - self._enabled = ((self._low_price_ratio != 0) or - (self._min_price != 0) or - (self._max_price != 0)) + if self._max_price < 0: + raise OperationalException("PriceFilter requires max_price be >= 0") + self._enabled = ((self._low_price_ratio > 0) or + (self._min_price > 0) or + (self._max_price > 0)) @property def needstickers(self) -> bool: From 5c2481082ef11633c56bd5d631550b746b18d271 Mon Sep 17 00:00:00 2001 From: hroff-1902 Date: Wed, 22 Jul 2020 22:46:30 +0300 Subject: [PATCH 06/11] Add tests for PriceFilter --- tests/pairlist/test_pairlist.py | 56 +++++++++++++++++++++++---------- 1 file changed, 40 insertions(+), 16 deletions(-) diff --git a/tests/pairlist/test_pairlist.py b/tests/pairlist/test_pairlist.py index efe4a784b..5a9472ef9 100644 --- a/tests/pairlist/test_pairlist.py +++ b/tests/pairlist/test_pairlist.py @@ -590,34 +590,58 @@ def test_agefilter_caching(mocker, markets, whitelist_conf_3, tickers, ohlcv_his 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, - "max_price": 1.0}, "[{'PriceFilter': 'PriceFilter - Filtering pairs priced below " - "0.1% or below 0.00000010 or above 1.00000000.'}]" - ), + "max_price": 1.0}, + "[{'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}, - "[{'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}, - "[{'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}, - "[{'PriceFilter': 'PriceFilter - Filtering pairs priced below 0.00002000.'}]" - ), + "[{'PriceFilter': 'PriceFilter - Filtering pairs priced below 0.00002000.'}]", + None + ), ({"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 be >= 0" + ), # OperationalException expected + ({"method": "PriceFilter", "min_price": -0.00000010}, + None, + "PriceFilter requires min_price be >= 0" + ), # OperationalException expected + ({"method": "PriceFilter", "max_price": -1.00010000}, + None, + "PriceFilter requires max_price 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', markets=PropertyMock(return_value=markets), exchange_has=MagicMock(return_value=True) ) whitelist_conf['pairlists'] = [pairlistconfig] - freqtrade = get_patched_freqtradebot(mocker, whitelist_conf) - short_desc = str(freqtrade.pairlists.short_desc()) - assert short_desc == expected + if desc_expected is not None: + freqtrade = get_patched_freqtradebot(mocker, whitelist_conf) + short_desc = str(freqtrade.pairlists.short_desc()) + 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): From 50767cd5694a79a4ac10ccba28f677470eb16725 Mon Sep 17 00:00:00 2001 From: hroff-1902 Date: Wed, 22 Jul 2020 22:48:29 +0300 Subject: [PATCH 07/11] Adjust tests for AgeFilter --- tests/pairlist/test_pairlist.py | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/tests/pairlist/test_pairlist.py b/tests/pairlist/test_pairlist.py index 5a9472ef9..27b13bf69 100644 --- a/tests/pairlist/test_pairlist.py +++ b/tests/pairlist/test_pairlist.py @@ -547,7 +547,7 @@ def test_agefilter_min_days_listed_too_small(mocker, default_conf, markets, tick ) with pytest.raises(OperationalException, - match=r'AgeFilter requires min_days_listed must be >= 1'): + match=r'AgeFilter requires min_days_listed be >= 1'): get_patched_freqtradebot(mocker, default_conf) @@ -562,7 +562,7 @@ def test_agefilter_min_days_listed_too_large(mocker, default_conf, markets, tick ) with pytest.raises(OperationalException, - match=r'AgeFilter requires min_days_listed must not exceed ' + match=r'AgeFilter requires min_days_listed be not exceeding ' r'exchange max request size \([0-9]+\)'): get_patched_freqtradebot(mocker, default_conf) From f48250b4142acbe91c43e18f246b9f5889263095 Mon Sep 17 00:00:00 2001 From: hroff-1902 Date: Wed, 22 Jul 2020 22:56:24 +0300 Subject: [PATCH 08/11] Make flake happy --- tests/pairlist/test_pairlist.py | 18 +++++++++--------- 1 file changed, 9 insertions(+), 9 deletions(-) diff --git a/tests/pairlist/test_pairlist.py b/tests/pairlist/test_pairlist.py index 27b13bf69..c235367be 100644 --- a/tests/pairlist/test_pairlist.py +++ b/tests/pairlist/test_pairlist.py @@ -596,35 +596,35 @@ def test_agefilter_caching(mocker, markets, whitelist_conf_3, tickers, ohlcv_his "[{'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}, "[{'PriceFilter': 'PriceFilter - Filtering pairs priced below 0.1% or below 0.00000010.'}]", None - ), + ), ({"method": "PriceFilter", "low_price_ratio": 0.001, "max_price": 1.00010000}, "[{'PriceFilter': 'PriceFilter - Filtering pairs priced below 0.1% or above 1.00010000.'}]", None - ), + ), ({"method": "PriceFilter", "min_price": 0.00002000}, "[{'PriceFilter': 'PriceFilter - Filtering pairs priced below 0.00002000.'}]", None - ), + ), ({"method": "PriceFilter"}, "[{'PriceFilter': 'PriceFilter - No price filters configured.'}]", None - ), + ), ({"method": "PriceFilter", "low_price_ratio": -0.001}, None, "PriceFilter requires low_price_ratio be >= 0" - ), # OperationalException expected + ), # OperationalException expected ({"method": "PriceFilter", "min_price": -0.00000010}, None, "PriceFilter requires min_price be >= 0" - ), # OperationalException expected + ), # OperationalException expected ({"method": "PriceFilter", "max_price": -1.00010000}, None, "PriceFilter requires max_price be >= 0" - ), # OperationalException expected + ), # OperationalException expected ]) def test_pricefilter_desc(mocker, whitelist_conf, markets, pairlistconfig, desc_expected, exception_expected): @@ -638,7 +638,7 @@ def test_pricefilter_desc(mocker, whitelist_conf, markets, pairlistconfig, freqtrade = get_patched_freqtradebot(mocker, whitelist_conf) short_desc = str(freqtrade.pairlists.short_desc()) assert short_desc == desc_expected - else: # # OperationalException expected + else: # OperationalException expected with pytest.raises(OperationalException, match=exception_expected): freqtrade = get_patched_freqtradebot(mocker, whitelist_conf) From 9dd2800b980d044a07febcc00e471b4691db6a6f Mon Sep 17 00:00:00 2001 From: Matthias Date: Sat, 15 Aug 2020 09:08:50 +0200 Subject: [PATCH 09/11] Apply some review changes --- docs/configuration.md | 6 +++--- freqtrade/pairlist/AgeFilter.py | 4 ++-- freqtrade/pairlist/PriceFilter.py | 6 +++--- 3 files changed, 8 insertions(+), 8 deletions(-) diff --git a/docs/configuration.md b/docs/configuration.md index f39a3c62d..5fc28f3bf 100644 --- a/docs/configuration.md +++ b/docs/configuration.md @@ -669,13 +669,13 @@ The `PriceFilter` allows filtering of pairs by price. Currently the following pr * `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. -This option is disabled by default (or set to 0), 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. -This option is disabled by default (or set to 0), 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. -This option is disabled by default (or set to 0), 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. diff --git a/freqtrade/pairlist/AgeFilter.py b/freqtrade/pairlist/AgeFilter.py index 56e56ceeb..64f01cb61 100644 --- a/freqtrade/pairlist/AgeFilter.py +++ b/freqtrade/pairlist/AgeFilter.py @@ -26,9 +26,9 @@ class AgeFilter(IPairList): self._min_days_listed = pairlistconfig.get('min_days_listed', 10) if self._min_days_listed < 1: - raise OperationalException("AgeFilter requires min_days_listed be >= 1") + raise OperationalException("AgeFilter requires min_days_listed to be >= 1") if self._min_days_listed > exchange.ohlcv_candle_limit: - raise OperationalException("AgeFilter requires min_days_listed be not exceeding " + raise OperationalException("AgeFilter requires min_days_listed to not exceed " "exchange max request size " f"({exchange.ohlcv_candle_limit})") diff --git a/freqtrade/pairlist/PriceFilter.py b/freqtrade/pairlist/PriceFilter.py index ae3ab9230..8cd57ee1d 100644 --- a/freqtrade/pairlist/PriceFilter.py +++ b/freqtrade/pairlist/PriceFilter.py @@ -20,13 +20,13 @@ class PriceFilter(IPairList): self._low_price_ratio = pairlistconfig.get('low_price_ratio', 0) if self._low_price_ratio < 0: - raise OperationalException("PriceFilter requires low_price_ratio be >= 0") + raise OperationalException("PriceFilter requires low_price_ratio to be >= 0") self._min_price = pairlistconfig.get('min_price', 0) if self._min_price < 0: - raise OperationalException("PriceFilter requires min_price be >= 0") + raise OperationalException("PriceFilter requires min_price to be >= 0") self._max_price = pairlistconfig.get('max_price', 0) if self._max_price < 0: - raise OperationalException("PriceFilter requires max_price be >= 0") + raise OperationalException("PriceFilter requires max_price to be >= 0") self._enabled = ((self._low_price_ratio > 0) or (self._min_price > 0) or (self._max_price > 0)) From 142f87b68ce4bbe61d8f52581a3a44aa67ebdfae Mon Sep 17 00:00:00 2001 From: Matthias Date: Sat, 15 Aug 2020 09:11:46 +0200 Subject: [PATCH 10/11] Adjust tests to new wordings --- tests/pairlist/test_pairlist.py | 10 +++++----- 1 file changed, 5 insertions(+), 5 deletions(-) diff --git a/tests/pairlist/test_pairlist.py b/tests/pairlist/test_pairlist.py index ad664abd2..9217abc46 100644 --- a/tests/pairlist/test_pairlist.py +++ b/tests/pairlist/test_pairlist.py @@ -549,7 +549,7 @@ def test_agefilter_min_days_listed_too_small(mocker, default_conf, markets, tick ) with pytest.raises(OperationalException, - match=r'AgeFilter requires min_days_listed be >= 1'): + match=r'AgeFilter requires min_days_listed to be >= 1'): 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, - match=r'AgeFilter requires min_days_listed be not exceeding ' + match=r'AgeFilter requires min_days_listed to not exceed ' r'exchange max request size \([0-9]+\)'): get_patched_freqtradebot(mocker, default_conf) @@ -617,15 +617,15 @@ def test_agefilter_caching(mocker, markets, whitelist_conf_3, tickers, ohlcv_his ), ({"method": "PriceFilter", "low_price_ratio": -0.001}, None, - "PriceFilter requires low_price_ratio be >= 0" + "PriceFilter requires low_price_ratio to be >= 0" ), # OperationalException expected ({"method": "PriceFilter", "min_price": -0.00000010}, None, - "PriceFilter requires min_price be >= 0" + "PriceFilter requires min_price to be >= 0" ), # OperationalException expected ({"method": "PriceFilter", "max_price": -1.00010000}, None, - "PriceFilter requires max_price be >= 0" + "PriceFilter requires max_price to be >= 0" ), # OperationalException expected ]) def test_pricefilter_desc(mocker, whitelist_conf, markets, pairlistconfig, From cc91d5138910ce1d355a7eedb801274f05d0f7a0 Mon Sep 17 00:00:00 2001 From: Matthias Date: Sat, 15 Aug 2020 09:18:00 +0200 Subject: [PATCH 11/11] Fix wording in configuration.md --- docs/configuration.md | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/docs/configuration.md b/docs/configuration.md index 5fc28f3bf..a366cde12 100644 --- a/docs/configuration.md +++ b/docs/configuration.md @@ -683,7 +683,8 @@ Calculation example: 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. -Low priced pairs 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. Consider using PriceFilter with `low_price_ratio` set to a value which is less than the absolute value of your stoploss (for example, if your stoploss is -5% (-0.05), then the value for `low_price_ratio` can be 0.04 or even 0.02). +!!! 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