Implement pair_blacklist functionality (#257)
* Adding an optional black_list of pairs not to be traded * applying the blacklist also when not using --dynamic-whitelist * fix error retrieving pair in conf * Refactoring the handling of whitelist among the various functions * unit test to verify that black listed pairs are being removed from the pair_whitelist * Fixing newly added unit tests in develop * fixing flake8 code review * fix code review from @garcq
This commit is contained in:
		| @@ -26,19 +26,17 @@ logger = logging.getLogger('freqtrade') | |||||||
| _CONF = {} | _CONF = {} | ||||||
|  |  | ||||||
|  |  | ||||||
| def refresh_whitelist(whitelist: Optional[List[str]] = None) -> None: | def refresh_whitelist(whitelist: List[str]) -> List[str]: | ||||||
|     """ |     """ | ||||||
|     Check wallet health and remove pair from whitelist if necessary |     Check wallet health and remove pair from whitelist if necessary | ||||||
|     :param whitelist: a new whitelist (optional) |     :param whitelist: the pair the user might want to trade | ||||||
|     :return: None |     :return: the list of pairs the user wants to trade without the one unavailable or black_listed | ||||||
|     """ |     """ | ||||||
|     whitelist = whitelist or _CONF['exchange']['pair_whitelist'] |  | ||||||
|  |  | ||||||
|     sanitized_whitelist = [] |     sanitized_whitelist = [] | ||||||
|     health = exchange.get_wallet_health() |     health = exchange.get_wallet_health() | ||||||
|     for status in health: |     for status in health: | ||||||
|         pair = '{}_{}'.format(_CONF['stake_currency'], status['Currency']) |         pair = '{}_{}'.format(_CONF['stake_currency'], status['Currency']) | ||||||
|         if pair not in whitelist: |         if pair not in whitelist or pair in _CONF['exchange'].get('pair_blacklist', []): | ||||||
|             continue |             continue | ||||||
|         if status['IsActive']: |         if status['IsActive']: | ||||||
|             sanitized_whitelist.append(pair) |             sanitized_whitelist.append(pair) | ||||||
| @@ -47,27 +45,29 @@ def refresh_whitelist(whitelist: Optional[List[str]] = None) -> None: | |||||||
|                 'Ignoring %s from whitelist (reason: %s).', |                 'Ignoring %s from whitelist (reason: %s).', | ||||||
|                 pair, status.get('Notice') or 'wallet is not active' |                 pair, status.get('Notice') or 'wallet is not active' | ||||||
|             ) |             ) | ||||||
|     if _CONF['exchange']['pair_whitelist'] != sanitized_whitelist: |     return sanitized_whitelist | ||||||
|         logger.debug('Using refreshed pair whitelist: %s ...', sanitized_whitelist) |  | ||||||
|         _CONF['exchange']['pair_whitelist'] = sanitized_whitelist |  | ||||||
|  |  | ||||||
|  |  | ||||||
| def _process(dynamic_whitelist: Optional[int] = 0) -> bool: | def _process(nb_assets: Optional[int] = 0) -> bool: | ||||||
|     """ |     """ | ||||||
|     Queries the persistence layer for open trades and handles them, |     Queries the persistence layer for open trades and handles them, | ||||||
|     otherwise a new trade is created. |     otherwise a new trade is created. | ||||||
|     :param: dynamic_whitelist: True is a dynamic whitelist should be generated (optional) |     :param: nb_assets: the maximum number of pairs to be traded at the same time | ||||||
|     :return: True if a trade has been created or closed, False otherwise |     :return: True if a trade has been created or closed, False otherwise | ||||||
|     """ |     """ | ||||||
|     state_changed = False |     state_changed = False | ||||||
|     try: |     try: | ||||||
|         # Refresh whitelist based on wallet maintenance |         # Refresh whitelist based on wallet maintenance | ||||||
|         refresh_whitelist( |         sanitized_list = refresh_whitelist( | ||||||
|             gen_pair_whitelist( |             gen_pair_whitelist( | ||||||
|                 _CONF['stake_currency'], |                 _CONF['stake_currency'] | ||||||
|                 topn=dynamic_whitelist |             ) if nb_assets else _CONF['exchange']['pair_whitelist'] | ||||||
|             ) if dynamic_whitelist else None |  | ||||||
|         ) |         ) | ||||||
|  |  | ||||||
|  |         # Keep only the subsets of pairs wanted (up to nb_assets) | ||||||
|  |         final_list = sanitized_list[:nb_assets] if nb_assets else sanitized_list | ||||||
|  |         _CONF['exchange']['pair_whitelist'] = final_list | ||||||
|  |  | ||||||
|         # Query trades from persistence layer |         # Query trades from persistence layer | ||||||
|         trades = Trade.query.filter(Trade.is_open.is_(True)).all() |         trades = Trade.query.filter(Trade.is_open.is_(True)).all() | ||||||
|         if len(trades) < _CONF['max_open_trades']: |         if len(trades) < _CONF['max_open_trades']: | ||||||
| @@ -295,11 +295,10 @@ def init(config: dict, db_url: Optional[str] = None) -> None: | |||||||
|  |  | ||||||
|  |  | ||||||
| @cached(TTLCache(maxsize=1, ttl=1800)) | @cached(TTLCache(maxsize=1, ttl=1800)) | ||||||
| def gen_pair_whitelist(base_currency: str, topn: int = 20, key: str = 'BaseVolume') -> List[str]: | def gen_pair_whitelist(base_currency: str, key: str = 'BaseVolume') -> 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 base_currency: base currency as str | ||||||
|     :param topn: maximum number of returned results, must be greater than 0 |  | ||||||
|     :param key: sort key (defaults to 'BaseVolume') |     :param key: sort key (defaults to 'BaseVolume') | ||||||
|     :return: List of pairs |     :return: List of pairs | ||||||
|     """ |     """ | ||||||
| @@ -309,10 +308,7 @@ def gen_pair_whitelist(base_currency: str, topn: int = 20, key: str = 'BaseVolum | |||||||
|         reverse=True |         reverse=True | ||||||
|     ) |     ) | ||||||
|  |  | ||||||
|     if topn <= 0: |     return [s['MarketName'].replace('-', '_') for s in summaries] | ||||||
|         topn = 20 |  | ||||||
|  |  | ||||||
|     return [s['MarketName'].replace('-', '_') for s in summaries[:topn]] |  | ||||||
|  |  | ||||||
|  |  | ||||||
| def cleanup() -> None: | def cleanup() -> None: | ||||||
|   | |||||||
| @@ -268,6 +268,14 @@ CONF_SCHEMA = { | |||||||
|                         'pattern': '^[0-9A-Z]+_[0-9A-Z]+$' |                         'pattern': '^[0-9A-Z]+_[0-9A-Z]+$' | ||||||
|                     }, |                     }, | ||||||
|                     'uniqueItems': True |                     'uniqueItems': True | ||||||
|  |                 }, | ||||||
|  |                 'pair_blacklist': { | ||||||
|  |                     'type': 'array', | ||||||
|  |                     'items': { | ||||||
|  |                         'type': 'string', | ||||||
|  |                         'pattern': '^[0-9A-Z]+_[0-9A-Z]+$' | ||||||
|  |                     }, | ||||||
|  |                     'uniqueItems': True | ||||||
|                 } |                 } | ||||||
|             }, |             }, | ||||||
|             'required': ['name', 'key', 'secret', 'pair_whitelist'] |             'required': ['name', 'key', 'secret', 'pair_whitelist'] | ||||||
|   | |||||||
| @@ -41,12 +41,10 @@ def test_refresh_whitelist(mocker): | |||||||
|     mocker.patch.dict('freqtrade.main._CONF', conf) |     mocker.patch.dict('freqtrade.main._CONF', conf) | ||||||
|     mocker.patch.multiple('freqtrade.main.exchange', |     mocker.patch.multiple('freqtrade.main.exchange', | ||||||
|                           get_wallet_health=get_health) |                           get_wallet_health=get_health) | ||||||
|     # no argument: use the whitelist provided by config |     refreshedwhitelist = refresh_whitelist(conf['exchange']['pair_whitelist']) | ||||||
|     refresh_whitelist() |  | ||||||
|     whitelist = ['BTC_ETH', 'BTC_TKN'] |     whitelist = ['BTC_ETH', 'BTC_TKN'] | ||||||
|     pairslist = conf['exchange']['pair_whitelist'] |  | ||||||
|     # Ensure all except those in whitelist are removed |     # Ensure all except those in whitelist are removed | ||||||
|     assert set(whitelist) == set(pairslist) |     assert set(whitelist) == set(refreshedwhitelist) | ||||||
|  |  | ||||||
|  |  | ||||||
| def test_refresh_whitelist_dynamic(mocker): | def test_refresh_whitelist_dynamic(mocker): | ||||||
| @@ -56,9 +54,8 @@ def test_refresh_whitelist_dynamic(mocker): | |||||||
|                           get_wallet_health=get_health) |                           get_wallet_health=get_health) | ||||||
|     # argument: use the whitelist dynamically by exchange-volume |     # argument: use the whitelist dynamically by exchange-volume | ||||||
|     whitelist = ['BTC_ETH', 'BTC_TKN'] |     whitelist = ['BTC_ETH', 'BTC_TKN'] | ||||||
|     refresh_whitelist(whitelist) |     refreshedwhitelist = refresh_whitelist(whitelist) | ||||||
|     pairslist = conf['exchange']['pair_whitelist'] |     assert set(whitelist) == set(refreshedwhitelist) | ||||||
|     assert set(whitelist) == set(pairslist) |  | ||||||
|  |  | ||||||
|  |  | ||||||
| def test_refresh_whitelist_dynamic_empty(mocker): | def test_refresh_whitelist_dynamic_empty(mocker): | ||||||
|   | |||||||
| @@ -180,6 +180,23 @@ def test_create_trade_no_pairs(default_conf, ticker, mocker): | |||||||
|         create_trade(default_conf['stake_amount']) |         create_trade(default_conf['stake_amount']) | ||||||
|  |  | ||||||
|  |  | ||||||
|  | def test_create_trade_no_pairs_after_blacklist(default_conf, ticker, mocker): | ||||||
|  |     mocker.patch.dict('freqtrade.main._CONF', default_conf) | ||||||
|  |     mocker.patch('freqtrade.main.get_signal', side_effect=lambda s, t: True) | ||||||
|  |     mocker.patch.multiple('freqtrade.rpc', init=MagicMock(), send_msg=MagicMock()) | ||||||
|  |     mocker.patch.multiple('freqtrade.main.exchange', | ||||||
|  |                           validate_pairs=MagicMock(), | ||||||
|  |                           get_ticker=ticker, | ||||||
|  |                           buy=MagicMock(return_value='mocked_limit_buy')) | ||||||
|  |  | ||||||
|  |     with pytest.raises(DependencyException, match=r'.*No pair in whitelist.*'): | ||||||
|  |         conf = copy.deepcopy(default_conf) | ||||||
|  |         conf['exchange']['pair_whitelist'] = ["BTC_ETH"] | ||||||
|  |         conf['exchange']['pair_blacklist'] = ["BTC_ETH"] | ||||||
|  |         mocker.patch.dict('freqtrade.main._CONF', conf) | ||||||
|  |         create_trade(default_conf['stake_amount']) | ||||||
|  |  | ||||||
|  |  | ||||||
| def test_handle_trade(default_conf, limit_buy_order, limit_sell_order, mocker): | def test_handle_trade(default_conf, limit_buy_order, limit_sell_order, mocker): | ||||||
|     mocker.patch.dict('freqtrade.main._CONF', default_conf) |     mocker.patch.dict('freqtrade.main._CONF', default_conf) | ||||||
|     mocker.patch('freqtrade.main.get_signal', side_effect=lambda s, t: True) |     mocker.patch('freqtrade.main.get_signal', side_effect=lambda s, t: True) | ||||||
|   | |||||||
		Reference in New Issue
	
	Block a user