diff --git a/config_full.json.example b/config_full.json.example index 5ae8021d5..5935de392 100644 --- a/config_full.json.example +++ b/config_full.json.example @@ -56,7 +56,7 @@ "number_assets": 20, "sort_key": "quoteVolume", "precision_filter": true, - "low_price_percent_filter": null + "low_price_percent_filter": 0 } }, "exchange": { diff --git a/docs/configuration.md b/docs/configuration.md index c7e0dac31..7ccb6840a 100644 --- a/docs/configuration.md +++ b/docs/configuration.md @@ -427,7 +427,9 @@ section of the configuration. * `VolumePairList` does not consider `pair_whitelist`, but builds this automatically based the pairlist configuration. * Pairs in `pair_blacklist` are not considered for VolumePairList, even if all other filters would match. * `low_price_percent_filter` allows filtering of pairs where a raise of 1 price unit is below the `low_price_percent_filter` ratio. + This option is disabled by default, and will only apply if set to <> 0. 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. + Example: @@ -442,7 +444,7 @@ Example: "number_assets": 20, "sort_key": "quoteVolume", "precision_filter": true, - "low_price_percent_filter": 0.03 + "low_price_percent_filter": 0.05 } }, ``` diff --git a/freqtrade/pairlist/VolumePairList.py b/freqtrade/pairlist/VolumePairList.py index 4a6768efa..7c17c6d57 100644 --- a/freqtrade/pairlist/VolumePairList.py +++ b/freqtrade/pairlist/VolumePairList.py @@ -5,11 +5,14 @@ Provides lists as configured in config.json """ import logging +from copy import deepcopy from typing import List + from cachetools import TTLCache, cached -from freqtrade.pairlist.IPairList import IPairList from freqtrade import OperationalException +from freqtrade.pairlist.IPairList import IPairList + logger = logging.getLogger(__name__) SORT_VALUES = ['askVolume', 'bidVolume', 'quoteVolume'] @@ -28,7 +31,6 @@ class VolumePairList(IPairList): self._sort_key = self._whitelistconf.get('sort_key', 'quoteVolume') self._precision_filter = self._whitelistconf.get('precision_filter', True) self._low_price_percent_filter = self._whitelistconf.get('low_price_percent_filter', None) - print(self._whitelistconf) if not self._freqtrade.exchange.exchange_has('fetchTickers'): raise OperationalException( @@ -64,10 +66,10 @@ class VolumePairList(IPairList): low value pairs. :param ticker: ticker dict as returned from ccxt.load_markets() :param stoploss: stoploss value as set in the configuration - (already cleaned to be guaranteed negative) + (already cleaned to be 1 - stoploss) :return: True if the pair can stay, false if it should be removed """ - stop_price = (self._freqtrade.get_target_bid(ticker["symbol"], ticker) * stoploss) + stop_price = self._freqtrade.get_target_bid(ticker["symbol"], ticker) * stoploss # Adjust stop-prices to precision sp = self._freqtrade.exchange.symbol_price_prec(ticker["symbol"], stop_price) stop_gap_price = self._freqtrade.exchange.symbol_price_prec(ticker["symbol"], @@ -120,10 +122,11 @@ class VolumePairList(IPairList): # Precalculate sanitized stoploss value to avoid recalculation for every pair stoploss = 1 - abs(self._freqtrade.strategy.stoploss) - for t in valid_tickers: + # Copy list since we're modifying this list + for t in deepcopy(valid_tickers): # Filter out assets which would not allow setting a stoploss if (stoploss and self._precision_filter - and not self._validate_precision_filter(t, stoploss)): + and not self._validate_precision_filter(t, stoploss)): valid_tickers.remove(t) continue if self._low_price_percent_filter and not self._validate_precision_filter_lowprice(t,): diff --git a/tests/conftest.py b/tests/conftest.py index a291a6676..d551596f0 100644 --- a/tests/conftest.py +++ b/tests/conftest.py @@ -606,6 +606,34 @@ def shitcoinmarkets(markets): }, 'info': {}, }, + 'FUEL/BTC': { + 'id': 'FUELBTC', + 'symbol': 'FUEL/BTC', + 'base': 'FUEL', + 'quote': 'BTC', + 'active': True, + 'precision': { + 'base': 8, + 'quote': 8, + 'amount': 0, + 'price': 8 + }, + 'limits': { + 'amount': { + 'min': 1.0, + 'max': 90000000.0 + }, + 'price': { + 'min': 1e-08, + 'max': 1000.0 + }, + 'cost': { + 'min': 0.001, + 'max': None + } + }, + 'info': {}, + }, }) return shitmarkets @@ -926,6 +954,28 @@ def tickers(): 'quoteVolume': 143.78311994, 'info': {} }, + 'FUEL/BTC': { + 'symbol': 'FUEL/BTC', + 'timestamp': 1572340250771, + 'datetime': '2019-10-29T09:10:50.771Z', + 'high': 0.00000040, + 'low': 0.00000035, + 'bid': 0.00000036, + 'bidVolume': 8932318.0, + 'ask': 0.00000037, + 'askVolume': 10140774.0, + 'vwap': 0.00000037, + 'open': 0.00000039, + 'close': 0.00000037, + 'last': 0.00000037, + 'previousClose': 0.00000038, + 'change': -0.00000002, + 'percentage': -5.128, + 'average': None, + 'baseVolume': 168927742.0, + 'quoteVolume': 62.68220262, + 'info': {} + }, 'ETH/USDT': { 'symbol': 'ETH/USDT', 'timestamp': 1522014804118, diff --git a/tests/pairlist/test_pairlist.py b/tests/pairlist/test_pairlist.py index 6f050a77d..ac27d1c8e 100644 --- a/tests/pairlist/test_pairlist.py +++ b/tests/pairlist/test_pairlist.py @@ -26,7 +26,7 @@ def whitelist_conf(default_conf): 'BLK/BTC' ] default_conf['pairlist'] = {'method': 'StaticPairList', - 'config': {'number_assets': 3} + 'config': {'number_assets': 5} } return default_conf @@ -86,7 +86,7 @@ def test_refresh_pairlist_dynamic(mocker, shitcoinmarkets, tickers, whitelist_co markets=PropertyMock(return_value=shitcoinmarkets), ) # argument: use the whitelist dynamically by exchange-volume - whitelist = ['ETH/BTC', 'TKN/BTC', 'LTC/BTC', 'HOT/BTC'] + whitelist = ['ETH/BTC', 'TKN/BTC', 'LTC/BTC', 'HOT/BTC', 'FUEL/BTC'] freqtradebot.pairlists.refresh_pairlist() assert whitelist == freqtradebot.pairlists.whitelist @@ -113,30 +113,38 @@ def test_VolumePairList_refresh_empty(mocker, markets_empty, whitelist_conf): assert set(whitelist) == set(pairslist) -@pytest.mark.parametrize("precision_filter,base_currency,key,whitelist_result", [ - (False, "BTC", "quoteVolume", ['ETH/BTC', 'TKN/BTC', 'LTC/BTC', 'HOT/BTC']), - (False, "BTC", "bidVolume", ['LTC/BTC', 'TKN/BTC', 'ETH/BTC', 'HOT/BTC']), - (False, "USDT", "quoteVolume", ['ETH/USDT']), - (False, "ETH", "quoteVolume", []), # this replaces tests that were removed from test_exchange - (True, "BTC", "quoteVolume", ["LTC/BTC", "ETH/BTC", "TKN/BTC"]), - (True, "BTC", "bidVolume", ["LTC/BTC", "TKN/BTC", "ETH/BTC"]) +@pytest.mark.parametrize("precision_filter,low_price_filter,base_currency,key,whitelist_result", [ + (False, 0, "BTC", "quoteVolume", ['ETH/BTC', 'TKN/BTC', 'LTC/BTC', 'HOT/BTC', 'FUEL/BTC']), + (False, 0, "BTC", "bidVolume", ['LTC/BTC', 'TKN/BTC', 'ETH/BTC', 'HOT/BTC', 'FUEL/BTC']), + (False, 0, "USDT", "quoteVolume", ['ETH/USDT']), + (False, 0, "ETH", "quoteVolume", []), + (True, 0, "BTC", "quoteVolume", ["LTC/BTC", "ETH/BTC", "TKN/BTC", 'FUEL/BTC']), + (True, 0, "BTC", "bidVolume", ["LTC/BTC", "TKN/BTC", "ETH/BTC", 'FUEL/BTC']), + (False, 0.03, "BTC", "bidVolume", ['LTC/BTC', 'TKN/BTC', 'ETH/BTC', 'FUEL/BTC']), + # Hot is removed by precision_filter, Fuel by low_price_filter. + (True, 0.02, "BTC", "bidVolume", ['LTC/BTC', 'TKN/BTC', 'ETH/BTC']), ]) def test_VolumePairList_whitelist_gen(mocker, whitelist_conf, shitcoinmarkets, tickers, - precision_filter, base_currency, key, whitelist_result, - caplog) -> None: + precision_filter, low_price_filter, base_currency, key, + whitelist_result, caplog) -> None: whitelist_conf['pairlist']['method'] = 'VolumePairList' + whitelist_conf['pairlist']['config']['precision_filter'] = precision_filter + whitelist_conf['pairlist']['config']['low_price_percent_filter'] = low_price_filter + mocker.patch('freqtrade.exchange.Exchange.exchange_has', MagicMock(return_value=True)) freqtrade = get_patched_freqtradebot(mocker, whitelist_conf) mocker.patch('freqtrade.exchange.Exchange.markets', PropertyMock(return_value=shitcoinmarkets)) mocker.patch('freqtrade.exchange.Exchange.get_tickers', tickers) - mocker.patch('freqtrade.exchange.Exchange.symbol_price_prec', lambda s, p, r: round(r, 8)) - freqtrade.pairlists._precision_filter = precision_filter freqtrade.config['stake_currency'] = base_currency whitelist = freqtrade.pairlists._gen_pair_whitelist(base_currency=base_currency, key=key) assert sorted(whitelist) == sorted(whitelist_result) if precision_filter: - assert log_has_re(r'^Removed .* from whitelist, because stop price .* would be <= stop limit.*', caplog) + assert log_has_re(r'^Removed .* from whitelist, because stop price .* ' + r'would be <= stop limit.*', caplog) + + if low_price_filter: + assert log_has_re(r'^Removed .* from whitelist, because 1 unit is .*%$', caplog) def test_gen_pair_whitelist_not_supported(mocker, default_conf, tickers) -> None: