Final cleanups and added tests

This commit is contained in:
Matthias 2019-10-29 10:39:27 +01:00
parent d803d86f4d
commit de2cc58b0c
5 changed files with 85 additions and 22 deletions

View File

@ -56,7 +56,7 @@
"number_assets": 20, "number_assets": 20,
"sort_key": "quoteVolume", "sort_key": "quoteVolume",
"precision_filter": true, "precision_filter": true,
"low_price_percent_filter": null "low_price_percent_filter": 0
} }
}, },
"exchange": { "exchange": {

View File

@ -427,8 +427,10 @@ section of the configuration.
* `VolumePairList` does not consider `pair_whitelist`, but builds this automatically based the pairlist 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. * 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. * `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. 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: Example:
```json ```json
@ -442,7 +444,7 @@ Example:
"number_assets": 20, "number_assets": 20,
"sort_key": "quoteVolume", "sort_key": "quoteVolume",
"precision_filter": true, "precision_filter": true,
"low_price_percent_filter": 0.03 "low_price_percent_filter": 0.05
} }
}, },
``` ```

View File

@ -5,11 +5,14 @@ Provides lists as configured in config.json
""" """
import logging import logging
from copy import deepcopy
from typing import List from typing import List
from cachetools import TTLCache, cached from cachetools import TTLCache, cached
from freqtrade.pairlist.IPairList import IPairList
from freqtrade import OperationalException from freqtrade import OperationalException
from freqtrade.pairlist.IPairList import IPairList
logger = logging.getLogger(__name__) logger = logging.getLogger(__name__)
SORT_VALUES = ['askVolume', 'bidVolume', 'quoteVolume'] SORT_VALUES = ['askVolume', 'bidVolume', 'quoteVolume']
@ -28,7 +31,6 @@ class VolumePairList(IPairList):
self._sort_key = self._whitelistconf.get('sort_key', 'quoteVolume') self._sort_key = self._whitelistconf.get('sort_key', 'quoteVolume')
self._precision_filter = self._whitelistconf.get('precision_filter', True) self._precision_filter = self._whitelistconf.get('precision_filter', True)
self._low_price_percent_filter = self._whitelistconf.get('low_price_percent_filter', None) self._low_price_percent_filter = self._whitelistconf.get('low_price_percent_filter', None)
print(self._whitelistconf)
if not self._freqtrade.exchange.exchange_has('fetchTickers'): if not self._freqtrade.exchange.exchange_has('fetchTickers'):
raise OperationalException( raise OperationalException(
@ -64,10 +66,10 @@ class VolumePairList(IPairList):
low value pairs. low value pairs.
:param ticker: ticker dict as returned from ccxt.load_markets() :param ticker: ticker dict as returned from ccxt.load_markets()
:param stoploss: stoploss value as set in the configuration :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 :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 # Adjust stop-prices to precision
sp = self._freqtrade.exchange.symbol_price_prec(ticker["symbol"], stop_price) sp = self._freqtrade.exchange.symbol_price_prec(ticker["symbol"], stop_price)
stop_gap_price = self._freqtrade.exchange.symbol_price_prec(ticker["symbol"], stop_gap_price = self._freqtrade.exchange.symbol_price_prec(ticker["symbol"],
@ -120,7 +122,8 @@ class VolumePairList(IPairList):
# Precalculate sanitized stoploss value to avoid recalculation for every pair # Precalculate sanitized stoploss value to avoid recalculation for every pair
stoploss = 1 - abs(self._freqtrade.strategy.stoploss) 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 # Filter out assets which would not allow setting a stoploss
if (stoploss and self._precision_filter if (stoploss and self._precision_filter
and not self._validate_precision_filter(t, stoploss)): and not self._validate_precision_filter(t, stoploss)):

View File

@ -606,6 +606,34 @@ def shitcoinmarkets(markets):
}, },
'info': {}, '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 return shitmarkets
@ -926,6 +954,28 @@ def tickers():
'quoteVolume': 143.78311994, 'quoteVolume': 143.78311994,
'info': {} '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': { 'ETH/USDT': {
'symbol': 'ETH/USDT', 'symbol': 'ETH/USDT',
'timestamp': 1522014804118, 'timestamp': 1522014804118,

View File

@ -26,7 +26,7 @@ def whitelist_conf(default_conf):
'BLK/BTC' 'BLK/BTC'
] ]
default_conf['pairlist'] = {'method': 'StaticPairList', default_conf['pairlist'] = {'method': 'StaticPairList',
'config': {'number_assets': 3} 'config': {'number_assets': 5}
} }
return default_conf return default_conf
@ -86,7 +86,7 @@ def test_refresh_pairlist_dynamic(mocker, shitcoinmarkets, tickers, whitelist_co
markets=PropertyMock(return_value=shitcoinmarkets), markets=PropertyMock(return_value=shitcoinmarkets),
) )
# argument: use the whitelist dynamically by exchange-volume # 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() freqtradebot.pairlists.refresh_pairlist()
assert whitelist == freqtradebot.pairlists.whitelist assert whitelist == freqtradebot.pairlists.whitelist
@ -113,30 +113,38 @@ def test_VolumePairList_refresh_empty(mocker, markets_empty, whitelist_conf):
assert set(whitelist) == set(pairslist) assert set(whitelist) == set(pairslist)
@pytest.mark.parametrize("precision_filter,base_currency,key,whitelist_result", [ @pytest.mark.parametrize("precision_filter,low_price_filter,base_currency,key,whitelist_result", [
(False, "BTC", "quoteVolume", ['ETH/BTC', 'TKN/BTC', 'LTC/BTC', 'HOT/BTC']), (False, 0, "BTC", "quoteVolume", ['ETH/BTC', 'TKN/BTC', 'LTC/BTC', 'HOT/BTC', 'FUEL/BTC']),
(False, "BTC", "bidVolume", ['LTC/BTC', 'TKN/BTC', 'ETH/BTC', 'HOT/BTC']), (False, 0, "BTC", "bidVolume", ['LTC/BTC', 'TKN/BTC', 'ETH/BTC', 'HOT/BTC', 'FUEL/BTC']),
(False, "USDT", "quoteVolume", ['ETH/USDT']), (False, 0, "USDT", "quoteVolume", ['ETH/USDT']),
(False, "ETH", "quoteVolume", []), # this replaces tests that were removed from test_exchange (False, 0, "ETH", "quoteVolume", []),
(True, "BTC", "quoteVolume", ["LTC/BTC", "ETH/BTC", "TKN/BTC"]), (True, 0, "BTC", "quoteVolume", ["LTC/BTC", "ETH/BTC", "TKN/BTC", 'FUEL/BTC']),
(True, "BTC", "bidVolume", ["LTC/BTC", "TKN/BTC", "ETH/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, def test_VolumePairList_whitelist_gen(mocker, whitelist_conf, shitcoinmarkets, tickers,
precision_filter, base_currency, key, whitelist_result, precision_filter, low_price_filter, base_currency, key,
caplog) -> None: whitelist_result, caplog) -> None:
whitelist_conf['pairlist']['method'] = 'VolumePairList' 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)) mocker.patch('freqtrade.exchange.Exchange.exchange_has', MagicMock(return_value=True))
freqtrade = get_patched_freqtradebot(mocker, whitelist_conf) freqtrade = get_patched_freqtradebot(mocker, whitelist_conf)
mocker.patch('freqtrade.exchange.Exchange.markets', PropertyMock(return_value=shitcoinmarkets)) mocker.patch('freqtrade.exchange.Exchange.markets', PropertyMock(return_value=shitcoinmarkets))
mocker.patch('freqtrade.exchange.Exchange.get_tickers', tickers) 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 freqtrade.config['stake_currency'] = base_currency
whitelist = freqtrade.pairlists._gen_pair_whitelist(base_currency=base_currency, key=key) whitelist = freqtrade.pairlists._gen_pair_whitelist(base_currency=base_currency, key=key)
assert sorted(whitelist) == sorted(whitelist_result) assert sorted(whitelist) == sorted(whitelist_result)
if precision_filter: 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: def test_gen_pair_whitelist_not_supported(mocker, default_conf, tickers) -> None: