Merge pull request #3313 from hroff-1902/refactor-pairlists

Cleanup in pairlists
This commit is contained in:
Matthias 2020-05-17 20:15:39 +02:00 committed by GitHub
commit 9d63fada24
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
8 changed files with 133 additions and 97 deletions

View File

@ -1,9 +1,6 @@
""" """
Static List provider PairList base class
"""
Provides lists as configured in config.json
"""
import logging import logging
from abc import ABC, abstractmethod, abstractproperty from abc import ABC, abstractmethod, abstractproperty
from copy import deepcopy from copy import deepcopy
@ -13,6 +10,7 @@ from cachetools import TTLCache, cached
from freqtrade.exchange import market_is_active from freqtrade.exchange import market_is_active
logger = logging.getLogger(__name__) logger = logging.getLogger(__name__)

View File

@ -1,14 +1,26 @@
"""
Precision pair list filter
"""
import logging import logging
from copy import deepcopy from copy import deepcopy
from typing import Dict, List from typing import Any, Dict, List
from freqtrade.pairlist.IPairList import IPairList from freqtrade.pairlist.IPairList import IPairList
logger = logging.getLogger(__name__) logger = logging.getLogger(__name__)
class PrecisionFilter(IPairList): class PrecisionFilter(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)
# Precalculate sanitized stoploss value to avoid recalculation for every pair
self._stoploss = 1 - abs(self._config['stoploss'])
@property @property
def needstickers(self) -> bool: def needstickers(self) -> bool:
""" """
@ -31,34 +43,32 @@ class PrecisionFilter(IPairList):
: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 1 - stoploss) (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 = ticker['ask'] * stoploss stop_price = ticker['ask'] * stoploss
# Adjust stop-prices to precision # Adjust stop-prices to precision
sp = self._exchange.price_to_precision(ticker["symbol"], stop_price) sp = self._exchange.price_to_precision(ticker["symbol"], stop_price)
stop_gap_price = self._exchange.price_to_precision(ticker["symbol"], stop_price * 0.99) stop_gap_price = self._exchange.price_to_precision(ticker["symbol"], stop_price * 0.99)
logger.debug(f"{ticker['symbol']} - {sp} : {stop_gap_price}") logger.debug(f"{ticker['symbol']} - {sp} : {stop_gap_price}")
if sp <= stop_gap_price: if sp <= stop_gap_price:
self.log_on_refresh(logger.info, self.log_on_refresh(logger.info,
f"Removed {ticker['symbol']} from whitelist, " f"Removed {ticker['symbol']} from whitelist, "
f"because stop price {sp} would be <= stop limit {stop_gap_price}") f"because stop price {sp} would be <= stop limit {stop_gap_price}")
return False return False
return True return True
def filter_pairlist(self, pairlist: List[str], tickers: Dict) -> List[str]: def filter_pairlist(self, pairlist: List[str], tickers: Dict) -> List[str]:
""" """
Filters and sorts pairlists and assigns and returns them again. Filters and sorts pairlists and assigns and returns them again.
""" """
stoploss = self._config.get('stoploss')
if stoploss is not None:
# Precalculate sanitized stoploss value to avoid recalculation for every pair
stoploss = 1 - abs(stoploss)
# Copy list since we're modifying this list # Copy list since we're modifying this list
for p in deepcopy(pairlist): for p in deepcopy(pairlist):
ticker = tickers.get(p)
# Filter out assets which would not allow setting a stoploss # Filter out assets which would not allow setting a stoploss
if not ticker or (stoploss and not self._validate_precision_filter(ticker, stoploss)): if not self._validate_precision_filter(tickers[p], self._stoploss):
pairlist.remove(p) pairlist.remove(p)
continue
return pairlist return pairlist

View File

@ -1,9 +1,13 @@
"""
Price pair list filter
"""
import logging import logging
from copy import deepcopy from copy import deepcopy
from typing import Any, Dict, List from typing import Any, Dict, List
from freqtrade.pairlist.IPairList import IPairList from freqtrade.pairlist.IPairList import IPairList
logger = logging.getLogger(__name__) logger = logging.getLogger(__name__)
@ -38,14 +42,12 @@ class PriceFilter(IPairList):
: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
""" """
if ticker['last'] is None: if ticker['last'] is None:
self.log_on_refresh(logger.info, self.log_on_refresh(logger.info,
f"Removed {ticker['symbol']} from whitelist, because " f"Removed {ticker['symbol']} from whitelist, because "
"ticker['last'] is empty (Usually no trade in the last 24h).") "ticker['last'] is empty (Usually no trade in the last 24h).")
return False return False
compare = ticker['last'] + self._exchange.price_get_one_pip(ticker['symbol'], compare = self._exchange.price_get_one_pip(ticker['symbol'], ticker['last'])
ticker['last']) changeperc = compare / ticker['last']
changeperc = (compare - ticker['last']) / ticker['last']
if changeperc > self._low_price_ratio: if changeperc > self._low_price_ratio:
self.log_on_refresh(logger.info, f"Removed {ticker['symbol']} from whitelist, " self.log_on_refresh(logger.info, f"Removed {ticker['symbol']} from whitelist, "
f"because 1 unit is {changeperc * 100:.3f}%") f"because 1 unit is {changeperc * 100:.3f}%")
@ -60,14 +62,11 @@ class PriceFilter(IPairList):
:param tickers: Tickers (from exchange.get_tickers()). May be cached. :param tickers: Tickers (from exchange.get_tickers()). May be cached.
:return: new whitelist :return: new whitelist
""" """
# Copy list since we're modifying this list if self._low_price_ratio:
for p in deepcopy(pairlist): # Copy list since we're modifying this list
ticker = tickers.get(p) for p in deepcopy(pairlist):
if not ticker: # Filter out assets which would not allow setting a stoploss
pairlist.remove(p) if not self._validate_ticker_lowprice(tickers[p]):
pairlist.remove(p)
# Filter out assets which would not allow setting a stoploss
if self._low_price_ratio and not self._validate_ticker_lowprice(ticker):
pairlist.remove(p)
return pairlist return pairlist

View File

@ -1,9 +1,13 @@
"""
Spread pair list filter
"""
import logging import logging
from copy import deepcopy from copy import deepcopy
from typing import Dict, List from typing import Dict, List
from freqtrade.pairlist.IPairList import IPairList from freqtrade.pairlist.IPairList import IPairList
logger = logging.getLogger(__name__) logger = logging.getLogger(__name__)
@ -31,8 +35,24 @@ class SpreadFilter(IPairList):
return (f"{self.name} - Filtering pairs with ask/bid diff above " return (f"{self.name} - Filtering pairs with ask/bid diff above "
f"{self._max_spread_ratio * 100}%.") f"{self._max_spread_ratio * 100}%.")
def filter_pairlist(self, pairlist: List[str], tickers: Dict) -> List[str]: def _validate_spread(self, ticker: dict) -> bool:
"""
Validate spread for the ticker
:param ticker: ticker dict as returned from ccxt.load_markets()
:return: True if the pair can stay, False if it should be removed
"""
if 'bid' in ticker and 'ask' in ticker:
spread = 1 - ticker['bid'] / ticker['ask']
if spread > self._max_spread_ratio:
self.log_on_refresh(logger.info, f"Removed {ticker['symbol']} from whitelist, "
f"because spread {spread * 100:.3f}% >"
f"{self._max_spread_ratio * 100}%")
return False
else:
return True
return False
def filter_pairlist(self, pairlist: List[str], tickers: Dict) -> List[str]:
""" """
Filters and sorts pairlist and returns the whitelist again. Filters and sorts pairlist and returns the whitelist again.
Called on each bot iteration - please use internal caching if necessary Called on each bot iteration - please use internal caching if necessary
@ -41,19 +61,10 @@ class SpreadFilter(IPairList):
:return: new whitelist :return: new whitelist
""" """
# Copy list since we're modifying this list # Copy list since we're modifying this list
spread = None
for p in deepcopy(pairlist): for p in deepcopy(pairlist):
ticker = tickers.get(p) ticker = tickers[p]
assert ticker is not None # Filter out assets
if 'bid' in ticker and 'ask' in ticker: if not self._validate_spread(ticker):
spread = 1 - ticker['bid'] / ticker['ask']
if not ticker or spread > self._max_spread_ratio:
self.log_on_refresh(logger.info, f"Removed {ticker['symbol']} from whitelist, "
f"because spread {spread * 100:.3f}% >"
f"{self._max_spread_ratio * 100}%")
pairlist.remove(p)
else:
pairlist.remove(p) pairlist.remove(p)
return pairlist return pairlist

View File

@ -1,14 +1,14 @@
""" """
Static List provider Static Pair List provider
Provides lists as configured in config.json Provides pair white list as it configured in config
"""
"""
import logging import logging
from typing import Dict, List from typing import Dict, List
from freqtrade.pairlist.IPairList import IPairList from freqtrade.pairlist.IPairList import IPairList
logger = logging.getLogger(__name__) logger = logging.getLogger(__name__)

View File

@ -1,9 +1,8 @@
""" """
Volume PairList provider Volume PairList provider
Provides lists as configured in config.json Provides dynamic pair list based on trade volumes
"""
"""
import logging import logging
from datetime import datetime from datetime import datetime
from typing import Any, Dict, List from typing import Any, Dict, List
@ -11,8 +10,10 @@ from typing import Any, Dict, List
from freqtrade.exceptions import OperationalException from freqtrade.exceptions import OperationalException
from freqtrade.pairlist.IPairList import IPairList from freqtrade.pairlist.IPairList import IPairList
logger = logging.getLogger(__name__) logger = logging.getLogger(__name__)
SORT_VALUES = ['askVolume', 'bidVolume', 'quoteVolume'] SORT_VALUES = ['askVolume', 'bidVolume', 'quoteVolume']
@ -26,6 +27,8 @@ class VolumePairList(IPairList):
raise OperationalException( raise OperationalException(
f'`number_assets` not specified. Please check your configuration ' f'`number_assets` not specified. Please check your configuration '
'for "pairlist.config.number_assets"') 'for "pairlist.config.number_assets"')
self._stake_currency = config['stake_currency']
self._number_pairs = self._pairlistconfig['number_assets'] self._number_pairs = self._pairlistconfig['number_assets']
self._sort_key = self._pairlistconfig.get('sort_key', 'quoteVolume') self._sort_key = self._pairlistconfig.get('sort_key', 'quoteVolume')
self._min_value = self._pairlistconfig.get('min_value', 0) self._min_value = self._pairlistconfig.get('min_value', 0)
@ -36,9 +39,11 @@ class VolumePairList(IPairList):
'Exchange does not support dynamic whitelist.' 'Exchange does not support dynamic whitelist.'
'Please edit your config and restart the bot' 'Please edit your config and restart the bot'
) )
if not self._validate_keys(self._sort_key): if not self._validate_keys(self._sort_key):
raise OperationalException( raise OperationalException(
f'key {self._sort_key} not in {SORT_VALUES}') f'key {self._sort_key} not in {SORT_VALUES}')
if self._sort_key != 'quoteVolume': if self._sort_key != 'quoteVolume':
logger.warning( logger.warning(
"DEPRECATED: using any key other than quoteVolume for VolumePairList is deprecated." "DEPRECATED: using any key other than quoteVolume for VolumePairList is deprecated."
@ -76,42 +81,42 @@ class VolumePairList(IPairList):
(self._last_refresh + self.refresh_period < datetime.now().timestamp())): (self._last_refresh + self.refresh_period < datetime.now().timestamp())):
self._last_refresh = int(datetime.now().timestamp()) self._last_refresh = int(datetime.now().timestamp())
pairs = self._gen_pair_whitelist(pairlist, tickers, pairs = self._gen_pair_whitelist(pairlist, tickers)
self._config['stake_currency'],
self._sort_key, self._min_value)
else: else:
pairs = pairlist pairs = pairlist
self.log_on_refresh(logger.info, f"Searching {self._number_pairs} pairs: {pairs}") self.log_on_refresh(logger.info, f"Searching {self._number_pairs} pairs: {pairs}")
return pairs return pairs
def _gen_pair_whitelist(self, pairlist: List[str], tickers: Dict, def _gen_pair_whitelist(self, pairlist: List[str], tickers: Dict) -> List[str]:
base_currency: str, key: str, min_val: int) -> List[str]:
""" """
Updates the whitelist with with a dynamically generated list Updates the whitelist with with a dynamically generated list
:param base_currency: base currency as str :param pairlist: pairlist to filter or sort
:param key: sort key (defaults to 'quoteVolume')
:param tickers: Tickers (from exchange.get_tickers()). :param tickers: Tickers (from exchange.get_tickers()).
:return: List of pairs :return: List of pairs
""" """
if self._pairlist_pos == 0: if self._pairlist_pos == 0:
# If VolumePairList is the first in the list, use fresh pairlist # If VolumePairList is the first in the list, use fresh pairlist
# Check if pair quote currency equals to the stake currency. # Check if pair quote currency equals to the stake currency.
filtered_tickers = [v for k, v in tickers.items() filtered_tickers = [
if (self._exchange.get_pair_quote_currency(k) == base_currency v for k, v in tickers.items()
and v[key] is not None)] if (self._exchange.get_pair_quote_currency(k) == self._stake_currency
and v[self._sort_key] is not None)]
else: else:
# If other pairlist is in front, use the incomming pairlist. # If other pairlist is in front, use the incoming pairlist.
filtered_tickers = [v for k, v in tickers.items() if k in pairlist] filtered_tickers = [v for k, v in tickers.items() if k in pairlist]
if min_val > 0: if self._min_value > 0:
filtered_tickers = list(filter(lambda t: t[key] > min_val, filtered_tickers)) filtered_tickers = [
v for v in filtered_tickers if v[self._sort_key] > self._min_value]
sorted_tickers = sorted(filtered_tickers, reverse=True, key=lambda t: t[key]) sorted_tickers = sorted(filtered_tickers, reverse=True, key=lambda t: t[self._sort_key])
# Validate whitelist to only have active market pairs # Validate whitelist to only have active market pairs
pairs = self._whitelist_for_active_markets([s['symbol'] for s in sorted_tickers]) pairs = self._whitelist_for_active_markets([s['symbol'] for s in sorted_tickers])
pairs = self._verify_blacklist(pairs, aswarning=False) pairs = self._verify_blacklist(pairs, aswarning=False)
# Limit to X number of pairs # Limit pairlist to the requested number of pairs
pairs = pairs[:self._number_pairs] pairs = pairs[:self._number_pairs]
return pairs return pairs

View File

@ -1,10 +1,8 @@
""" """
Static List provider PairList manager class
"""
Provides lists as configured in config.json
"""
import logging import logging
from copy import deepcopy
from typing import Dict, List from typing import Dict, List
from cachetools import TTLCache, cached from cachetools import TTLCache, cached
@ -13,6 +11,7 @@ from freqtrade.exceptions import OperationalException
from freqtrade.pairlist.IPairList import IPairList from freqtrade.pairlist.IPairList import IPairList
from freqtrade.resolvers import PairListResolver from freqtrade.resolvers import PairListResolver
logger = logging.getLogger(__name__) logger = logging.getLogger(__name__)
@ -78,19 +77,33 @@ class PairListManager():
""" """
Run pairlist through all configured pairlists. Run pairlist through all configured pairlists.
""" """
# Tickers should be cached to avoid calling the exchange on each call.
pairlist = self._whitelist.copy()
# tickers should be cached to avoid calling the exchange on each call.
tickers: Dict = {} tickers: Dict = {}
if self._tickers_needed: if self._tickers_needed:
tickers = self._get_cached_tickers() tickers = self._get_cached_tickers()
# Adjust whitelist if filters are using tickers
pairlist = self._prepare_whitelist(self._whitelist.copy(), tickers)
# Process all pairlists in chain # Process all pairlists in chain
for pl in self._pairlists: for pl in self._pairlists:
pairlist = pl.filter_pairlist(pairlist, tickers) pairlist = pl.filter_pairlist(pairlist, tickers)
# Validation against blacklist happens after the pairlists to ensure blacklist is respected. # Validation against blacklist happens after the pairlists to ensure
# blacklist is respected.
pairlist = IPairList.verify_blacklist(pairlist, self.blacklist, True) pairlist = IPairList.verify_blacklist(pairlist, self.blacklist, True)
self._whitelist = pairlist self._whitelist = pairlist
def _prepare_whitelist(self, pairlist: List[str], tickers) -> List[str]:
"""
Prepare sanitized pairlist for Pairlist Filters that use tickers data - remove
pairs that do not have ticker available
"""
if self._tickers_needed:
# Copy list since we're modifying this list
for p in deepcopy(pairlist):
if p not in tickers:
pairlist.remove(p)
return pairlist

View File

@ -69,44 +69,44 @@ def test_log_on_refresh(mocker, static_pl_conf, markets, tickers):
def test_load_pairlist_noexist(mocker, markets, default_conf): def test_load_pairlist_noexist(mocker, markets, default_conf):
bot = get_patched_freqtradebot(mocker, default_conf) freqtrade = get_patched_freqtradebot(mocker, default_conf)
mocker.patch('freqtrade.exchange.Exchange.markets', PropertyMock(return_value=markets)) mocker.patch('freqtrade.exchange.Exchange.markets', PropertyMock(return_value=markets))
plm = PairListManager(bot.exchange, default_conf) plm = PairListManager(freqtrade.exchange, default_conf)
with pytest.raises(OperationalException, with pytest.raises(OperationalException,
match=r"Impossible to load Pairlist 'NonexistingPairList'. " match=r"Impossible to load Pairlist 'NonexistingPairList'. "
r"This class does not exist or contains Python code errors."): r"This class does not exist or contains Python code errors."):
PairListResolver.load_pairlist('NonexistingPairList', bot.exchange, plm, PairListResolver.load_pairlist('NonexistingPairList', freqtrade.exchange, plm,
default_conf, {}, 1) default_conf, {}, 1)
def test_refresh_market_pair_not_in_whitelist(mocker, markets, static_pl_conf): def test_refresh_market_pair_not_in_whitelist(mocker, markets, static_pl_conf):
freqtradebot = get_patched_freqtradebot(mocker, static_pl_conf) freqtrade = get_patched_freqtradebot(mocker, static_pl_conf)
mocker.patch('freqtrade.exchange.Exchange.markets', PropertyMock(return_value=markets)) mocker.patch('freqtrade.exchange.Exchange.markets', PropertyMock(return_value=markets))
freqtradebot.pairlists.refresh_pairlist() freqtrade.pairlists.refresh_pairlist()
# List ordered by BaseVolume # List ordered by BaseVolume
whitelist = ['ETH/BTC', 'TKN/BTC'] whitelist = ['ETH/BTC', 'TKN/BTC']
# Ensure all except those in whitelist are removed # Ensure all except those in whitelist are removed
assert set(whitelist) == set(freqtradebot.pairlists.whitelist) assert set(whitelist) == set(freqtrade.pairlists.whitelist)
# Ensure config dict hasn't been changed # Ensure config dict hasn't been changed
assert (static_pl_conf['exchange']['pair_whitelist'] == assert (static_pl_conf['exchange']['pair_whitelist'] ==
freqtradebot.config['exchange']['pair_whitelist']) freqtrade.config['exchange']['pair_whitelist'])
def test_refresh_static_pairlist(mocker, markets, static_pl_conf): def test_refresh_static_pairlist(mocker, markets, static_pl_conf):
freqtradebot = get_patched_freqtradebot(mocker, static_pl_conf) freqtrade = get_patched_freqtradebot(mocker, static_pl_conf)
mocker.patch.multiple( mocker.patch.multiple(
'freqtrade.exchange.Exchange', 'freqtrade.exchange.Exchange',
exchange_has=MagicMock(return_value=True), exchange_has=MagicMock(return_value=True),
markets=PropertyMock(return_value=markets), markets=PropertyMock(return_value=markets),
) )
freqtradebot.pairlists.refresh_pairlist() freqtrade.pairlists.refresh_pairlist()
# List ordered by BaseVolume # List ordered by BaseVolume
whitelist = ['ETH/BTC', 'TKN/BTC'] whitelist = ['ETH/BTC', 'TKN/BTC']
# Ensure all except those in whitelist are removed # Ensure all except those in whitelist are removed
assert set(whitelist) == set(freqtradebot.pairlists.whitelist) assert set(whitelist) == set(freqtrade.pairlists.whitelist)
assert static_pl_conf['exchange']['pair_blacklist'] == freqtradebot.pairlists.blacklist assert static_pl_conf['exchange']['pair_blacklist'] == freqtrade.pairlists.blacklist
def test_refresh_pairlist_dynamic(mocker, shitcoinmarkets, tickers, whitelist_conf): def test_refresh_pairlist_dynamic(mocker, shitcoinmarkets, tickers, whitelist_conf):
@ -116,7 +116,7 @@ def test_refresh_pairlist_dynamic(mocker, shitcoinmarkets, tickers, whitelist_co
get_tickers=tickers, get_tickers=tickers,
exchange_has=MagicMock(return_value=True), exchange_has=MagicMock(return_value=True),
) )
bot = get_patched_freqtradebot(mocker, whitelist_conf) freqtrade = get_patched_freqtradebot(mocker, whitelist_conf)
# Remock markets with shitcoinmarkets since get_patched_freqtradebot uses the markets fixture # Remock markets with shitcoinmarkets since get_patched_freqtradebot uses the markets fixture
mocker.patch.multiple( mocker.patch.multiple(
'freqtrade.exchange.Exchange', 'freqtrade.exchange.Exchange',
@ -124,9 +124,9 @@ def test_refresh_pairlist_dynamic(mocker, shitcoinmarkets, tickers, whitelist_co
) )
# argument: use the whitelist dynamically by exchange-volume # argument: use the whitelist dynamically by exchange-volume
whitelist = ['ETH/BTC', 'TKN/BTC', 'LTC/BTC', 'XRP/BTC', 'HOT/BTC'] whitelist = ['ETH/BTC', 'TKN/BTC', 'LTC/BTC', 'XRP/BTC', 'HOT/BTC']
bot.pairlists.refresh_pairlist() freqtrade.pairlists.refresh_pairlist()
assert whitelist == bot.pairlists.whitelist assert whitelist == freqtrade.pairlists.whitelist
whitelist_conf['pairlists'] = [{'method': 'VolumePairList', whitelist_conf['pairlists'] = [{'method': 'VolumePairList',
'config': {} 'config': {}
@ -136,7 +136,7 @@ def test_refresh_pairlist_dynamic(mocker, shitcoinmarkets, tickers, whitelist_co
with pytest.raises(OperationalException, with pytest.raises(OperationalException,
match=r'`number_assets` not specified. Please check your configuration ' match=r'`number_assets` not specified. Please check your configuration '
r'for "pairlist.config.number_assets"'): r'for "pairlist.config.number_assets"'):
PairListManager(bot.exchange, whitelist_conf) PairListManager(freqtrade.exchange, whitelist_conf)
def test_VolumePairList_refresh_empty(mocker, markets_empty, whitelist_conf): def test_VolumePairList_refresh_empty(mocker, markets_empty, whitelist_conf):
@ -144,13 +144,13 @@ def test_VolumePairList_refresh_empty(mocker, markets_empty, whitelist_conf):
'freqtrade.exchange.Exchange', 'freqtrade.exchange.Exchange',
exchange_has=MagicMock(return_value=True), exchange_has=MagicMock(return_value=True),
) )
freqtradebot = get_patched_freqtradebot(mocker, whitelist_conf) freqtrade = get_patched_freqtradebot(mocker, whitelist_conf)
mocker.patch('freqtrade.exchange.Exchange.markets', PropertyMock(return_value=markets_empty)) mocker.patch('freqtrade.exchange.Exchange.markets', PropertyMock(return_value=markets_empty))
# argument: use the whitelist dynamically by exchange-volume # argument: use the whitelist dynamically by exchange-volume
whitelist = [] whitelist = []
whitelist_conf['exchange']['pair_whitelist'] = [] whitelist_conf['exchange']['pair_whitelist'] = []
freqtradebot.pairlists.refresh_pairlist() freqtrade.pairlists.refresh_pairlist()
pairslist = whitelist_conf['exchange']['pair_whitelist'] pairslist = whitelist_conf['exchange']['pair_whitelist']
assert set(whitelist) == set(pairslist) assert set(whitelist) == set(pairslist)
@ -206,6 +206,7 @@ def test_VolumePairList_whitelist_gen(mocker, whitelist_conf, shitcoinmarkets, t
pairlists, base_currency, whitelist_result, pairlists, base_currency, whitelist_result,
caplog) -> None: caplog) -> None:
whitelist_conf['pairlists'] = pairlists whitelist_conf['pairlists'] = pairlists
whitelist_conf['stake_currency'] = base_currency
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)
@ -215,7 +216,6 @@ def test_VolumePairList_whitelist_gen(mocker, whitelist_conf, shitcoinmarkets, t
markets=PropertyMock(return_value=shitcoinmarkets), markets=PropertyMock(return_value=shitcoinmarkets),
) )
freqtrade.config['stake_currency'] = base_currency
freqtrade.pairlists.refresh_pairlist() freqtrade.pairlists.refresh_pairlist()
whitelist = freqtrade.pairlists.whitelist whitelist = freqtrade.pairlists.whitelist
@ -312,18 +312,18 @@ def test_volumepairlist_caching(mocker, markets, whitelist_conf, tickers):
exchange_has=MagicMock(return_value=True), exchange_has=MagicMock(return_value=True),
get_tickers=tickers get_tickers=tickers
) )
bot = get_patched_freqtradebot(mocker, whitelist_conf) freqtrade = get_patched_freqtradebot(mocker, whitelist_conf)
assert bot.pairlists._pairlists[0]._last_refresh == 0 assert freqtrade.pairlists._pairlists[0]._last_refresh == 0
assert tickers.call_count == 0 assert tickers.call_count == 0
bot.pairlists.refresh_pairlist() freqtrade.pairlists.refresh_pairlist()
assert tickers.call_count == 1 assert tickers.call_count == 1
assert bot.pairlists._pairlists[0]._last_refresh != 0 assert freqtrade.pairlists._pairlists[0]._last_refresh != 0
lrf = bot.pairlists._pairlists[0]._last_refresh lrf = freqtrade.pairlists._pairlists[0]._last_refresh
bot.pairlists.refresh_pairlist() freqtrade.pairlists.refresh_pairlist()
assert tickers.call_count == 1 assert tickers.call_count == 1
# Time should not be updated. # Time should not be updated.
assert bot.pairlists._pairlists[0]._last_refresh == lrf assert freqtrade.pairlists._pairlists[0]._last_refresh == lrf
def test_pairlistmanager_no_pairlist(mocker, markets, whitelist_conf, caplog): def test_pairlistmanager_no_pairlist(mocker, markets, whitelist_conf, caplog):