diff --git a/docs/includes/pairlists.md b/docs/includes/pairlists.md index e0aa3278c..1babeca1f 100644 --- a/docs/includes/pairlists.md +++ b/docs/includes/pairlists.md @@ -185,6 +185,7 @@ The RemotePairList is defined in the pairlists section of the configuration sett { "method": "RemotePairList", "mode": "whitelist", + "processing_mode": "filter", "pairlist_url": "https://example.com/pairlist", "number_assets": 10, "refresh_period": 1800, @@ -195,7 +196,13 @@ The RemotePairList is defined in the pairlists section of the configuration sett ] ``` -The `mode` option specifies if the pairlist should be used as a `blacklist` or as a `whitelist`. The default value is "whitelist". +The optional `mode` option specifies if the pairlist should be used as a `blacklist` or as a `whitelist`. The default value is "whitelist". + +The optional `processing_mode` option in the RemotePairList configuration determines how the retrieved pairlist is processed. It can have two values: "filter" or "append". + +In "filter" mode, the retrieved pairlist is used as a filter. Only the pairs present in both the original pairlist and the retrieved pairlist are included in the final pairlist. Other pairs are filtered out. + +In "append" mode, the retrieved pairlist is added to the original pairlist. All pairs from both lists are included in the final pairlist without any filtering. The `pairlist_url` option specifies the URL of the remote server where the pairlist is located, or the path to a local file (if file:/// is prepended). This allows the user to use either a remote server or a local file as the source for the pairlist. diff --git a/freqtrade/plugins/pairlist/RemotePairList.py b/freqtrade/plugins/pairlist/RemotePairList.py index a5c953d19..ba602efd1 100644 --- a/freqtrade/plugins/pairlist/RemotePairList.py +++ b/freqtrade/plugins/pairlist/RemotePairList.py @@ -42,6 +42,7 @@ class RemotePairList(IPairList): 'for "pairlist.config.pairlist_url"') self._mode = self._pairlistconfig.get('mode', 'whitelist') + self._processing_mode = self._pairlistconfig.get('processing_mode', 'filter') self._number_pairs = self._pairlistconfig['number_assets'] self._refresh_period: int = self._pairlistconfig.get('refresh_period', 1800) self._keep_pairlist_on_failure = self._pairlistconfig.get('keep_pairlist_on_failure', True) @@ -57,6 +58,23 @@ class RemotePairList(IPairList): '`mode` not configured correctly. Supported Modes ' 'are "whitelist","blacklist"') + if self._processing_mode not in ['filter', 'append']: + raise OperationalException( + '`processing_mode` not configured correctly. Supported Modes ' + 'are "filter","append"') + + pairlists = self._config['pairlists'] + + if len(pairlists) == 1 and self._mode == 'blacklist': + raise OperationalException( + 'At `blacklist` mode RemotePairList requires an additional ' + 'Pairlist and cannot be used on its own.') + + if pairlists[0]['method'] == "RemotePairList" and self._mode == 'blacklist': + raise OperationalException( + 'At `blacklist` mode RemotePairList can not be on the first ' + 'position of your pairlist.') + @property def needstickers(self) -> bool: """ @@ -269,7 +287,10 @@ class RemotePairList(IPairList): filtered = [] if self._mode == "whitelist": - merged_list = pairlist + rpl_pairlist + if self._processing_mode == "filter": + merged_list = [pair for pair in pairlist if pair in rpl_pairlist] + elif self._processing_mode == "append": + merged_list = pairlist + rpl_pairlist merged_list = sorted(set(merged_list), key=merged_list.index) else: for pair in pairlist: diff --git a/tests/plugins/test_remotepairlist.py b/tests/plugins/test_remotepairlist.py index 0dc343dc8..189fc0d5f 100644 --- a/tests/plugins/test_remotepairlist.py +++ b/tests/plugins/test_remotepairlist.py @@ -1,5 +1,5 @@ import json -from unittest.mock import MagicMock +from unittest.mock import MagicMock, PropertyMock import pytest import requests @@ -7,7 +7,7 @@ import requests from freqtrade.exceptions import OperationalException from freqtrade.plugins.pairlist.RemotePairList import RemotePairList from freqtrade.plugins.pairlistmanager import PairListManager -from tests.conftest import get_patched_exchange, get_patched_freqtradebot, log_has +from tests.conftest import EXMS, get_patched_exchange, get_patched_freqtradebot, log_has @pytest.fixture(scope="function") @@ -187,7 +187,6 @@ def test_fetch_pairlist_mock_response_valid(mocker, rpl_config): def test_remote_pairlist_init_wrong_mode(mocker, rpl_config): - rpl_config['pairlists'] = [ { "method": "RemotePairList", @@ -199,12 +198,34 @@ def test_remote_pairlist_init_wrong_mode(mocker, rpl_config): ] get_patched_exchange(mocker, rpl_config) - with pytest.raises(OperationalException, match=r'`mode` not configured correctly.' - r' Supported Modes are "whitelist","blacklist"'): + with pytest.raises( + OperationalException, + match=r'`mode` not configured correctly. Supported Modes are "whitelist","blacklist"' + ): get_patched_freqtradebot(mocker, rpl_config) -def test_remote_pairlist_blacklist(mocker, rpl_config, caplog): +def test_remote_pairlist_init_wrong_proc_mode(mocker, rpl_config): + rpl_config['pairlists'] = [ + { + "method": "RemotePairList", + "processing_mode": "filler", + "mode": "whitelist", + "number_assets": 20, + "pairlist_url": "http://example.com/pairlist", + "keep_pairlist_on_failure": True, + } + ] + + get_patched_exchange(mocker, rpl_config) + with pytest.raises( + OperationalException, + match=r'`processing_mode` not configured correctly. Supported Modes are "filter","append"' + ): + get_patched_freqtradebot(mocker, rpl_config) + + +def test_remote_pairlist_blacklist(mocker, rpl_config, caplog, markets, tickers): mock_response = MagicMock() @@ -218,6 +239,7 @@ def test_remote_pairlist_blacklist(mocker, rpl_config, caplog): } rpl_config['pairlists'] = [ + {'method': 'StaticPairList'}, { "method": "RemotePairList", "mode": "blacklist", @@ -226,6 +248,12 @@ def test_remote_pairlist_blacklist(mocker, rpl_config, caplog): } ] + mocker.patch.multiple(EXMS, + markets=PropertyMock(return_value=markets), + exchange_has=MagicMock(return_value=True), + get_tickers=tickers + ) + mocker.patch("freqtrade.plugins.pairlist.RemotePairList.requests.get", return_value=mock_response) @@ -234,7 +262,7 @@ def test_remote_pairlist_blacklist(mocker, rpl_config, caplog): pairlistmanager = PairListManager(exchange, rpl_config) remote_pairlist = RemotePairList(exchange, pairlistmanager, rpl_config, - rpl_config['pairlists'][0], 0) + rpl_config['pairlists'][1], 0) pairs, time_elapsed = remote_pairlist.fetch_pairlist()