From c1e938ccda226dd5f558fd91aea51b046da058b8 Mon Sep 17 00:00:00 2001 From: Matthias Date: Fri, 21 Jun 2024 14:01:25 +0200 Subject: [PATCH 1/6] Add "BacktestnigSupport" method to pairlists --- freqtrade/plugins/pairlist/IPairList.py | 13 +++++++++ freqtrade/plugins/pairlistmanager.py | 38 ++++++++++++++++++++++++- 2 files changed, 50 insertions(+), 1 deletion(-) diff --git a/freqtrade/plugins/pairlist/IPairList.py b/freqtrade/plugins/pairlist/IPairList.py index a2e70e649..c3d1a10ad 100644 --- a/freqtrade/plugins/pairlist/IPairList.py +++ b/freqtrade/plugins/pairlist/IPairList.py @@ -5,6 +5,7 @@ PairList Handler base class import logging from abc import ABC, abstractmethod from copy import deepcopy +from enum import Enum from typing import Any, Dict, List, Literal, Optional, TypedDict, Union from freqtrade.constants import Config @@ -51,8 +52,20 @@ PairlistParameter = Union[ ] +class SupportsBacktesting(str, Enum): + """ + Enum to indicate if a Pairlist Handler supports backtesting. + """ + + YES = "yes" + NO = "no" + NO_ACTION = "no_action" + TODAYS_DATA = "todays_data" + + class IPairList(LoggingMixin, ABC): is_pairlist_generator = False + supports_backtesting: SupportsBacktesting = SupportsBacktesting.NO def __init__( self, diff --git a/freqtrade/plugins/pairlistmanager.py b/freqtrade/plugins/pairlistmanager.py index a6afd5e64..30b3d74f8 100644 --- a/freqtrade/plugins/pairlistmanager.py +++ b/freqtrade/plugins/pairlistmanager.py @@ -11,10 +11,11 @@ from cachetools import TTLCache, cached from freqtrade.constants import Config, ListPairsWithTimeframes from freqtrade.data.dataprovider import DataProvider from freqtrade.enums import CandleType +from freqtrade.enums.runmode import RunMode from freqtrade.exceptions import OperationalException from freqtrade.exchange.types import Tickers from freqtrade.mixins import LoggingMixin -from freqtrade.plugins.pairlist.IPairList import IPairList +from freqtrade.plugins.pairlist.IPairList import IPairList, SupportsBacktesting from freqtrade.plugins.pairlist.pairlist_helpers import expand_pairlist from freqtrade.resolvers import PairListResolver @@ -57,9 +58,44 @@ class PairListManager(LoggingMixin): f"{invalid}." ) + self._check_backtest() + refresh_period = config.get("pairlist_refresh_period", 3600) LoggingMixin.__init__(self, logger, refresh_period) + def _check_backtest(self) -> None: + if self._config["runmode"] not in (RunMode.BACKTEST, RunMode.EDGE, RunMode.HYPEROPT): + return + + pairlist_errors: List[str] = [] + noaction_pairlists: List[str] = [] + biased_pairlists: List[str] = [] + for pairlist_handler in self._pairlist_handlers: + if pairlist_handler.supports_backtesting == SupportsBacktesting.NO: + pairlist_errors.append(pairlist_handler.name) + if pairlist_handler.supports_backtesting == SupportsBacktesting.NO_ACTION: + noaction_pairlists.append(pairlist_handler.name) + if pairlist_handler.supports_backtesting == SupportsBacktesting.TODAYS_DATA: + biased_pairlists.append(pairlist_handler.name) + + if noaction_pairlists: + logger.warning( + f"Pairlist Handlers {', '.join(noaction_pairlists)} do not generate " + "any changes during backtesting. While it's safe to leave them enabled, they will " + "not behave like in dry/live modes. " + ) + + if biased_pairlists: + logger.warning( + f"Pairlist Handlers {', '.join(biased_pairlists)} will introduce a lookahead bias " + "to your backtest results, as they use today's data - which inheritly suffers from " + "'winner bias'." + ) + if pairlist_errors: + raise OperationalException( + f"Pairlist Handlers {', '.join(pairlist_errors)} do not support backtesting." + ) + @property def whitelist(self) -> List[str]: """The current whitelist""" From ea58be2705cb6a685cd79365a89d5e38660653f8 Mon Sep 17 00:00:00 2001 From: Matthias Date: Fri, 21 Jun 2024 14:09:30 +0200 Subject: [PATCH 2/6] Classify individual pairlists for backtestability --- freqtrade/plugins/pairlist/AgeFilter.py | 4 +++- freqtrade/plugins/pairlist/FullTradesFilter.py | 4 +++- freqtrade/plugins/pairlist/MarketCapPairList.py | 3 ++- freqtrade/plugins/pairlist/OffsetFilter.py | 4 +++- freqtrade/plugins/pairlist/PerformanceFilter.py | 4 +++- freqtrade/plugins/pairlist/PrecisionFilter.py | 4 +++- freqtrade/plugins/pairlist/PriceFilter.py | 4 +++- freqtrade/plugins/pairlist/ProducerPairList.py | 3 ++- freqtrade/plugins/pairlist/RemotePairList.py | 4 +++- freqtrade/plugins/pairlist/ShuffleFilter.py | 4 +++- freqtrade/plugins/pairlist/SpreadFilter.py | 4 +++- freqtrade/plugins/pairlist/StaticPairList.py | 3 ++- freqtrade/plugins/pairlist/VolatilityFilter.py | 4 +++- freqtrade/plugins/pairlist/VolumePairList.py | 3 ++- freqtrade/plugins/pairlist/rangestabilityfilter.py | 4 +++- 15 files changed, 41 insertions(+), 15 deletions(-) diff --git a/freqtrade/plugins/pairlist/AgeFilter.py b/freqtrade/plugins/pairlist/AgeFilter.py index 917dad45c..88f0d23d8 100644 --- a/freqtrade/plugins/pairlist/AgeFilter.py +++ b/freqtrade/plugins/pairlist/AgeFilter.py @@ -13,7 +13,7 @@ from freqtrade.constants import ListPairsWithTimeframes from freqtrade.exceptions import OperationalException from freqtrade.exchange.types import Tickers from freqtrade.misc import plural -from freqtrade.plugins.pairlist.IPairList import IPairList, PairlistParameter +from freqtrade.plugins.pairlist.IPairList import IPairList, PairlistParameter, SupportsBacktesting from freqtrade.util import PeriodicCache, dt_floor_day, dt_now, dt_ts @@ -21,6 +21,8 @@ logger = logging.getLogger(__name__) class AgeFilter(IPairList): + supports_backtesting = SupportsBacktesting.NO + def __init__(self, *args, **kwargs) -> None: super().__init__(*args, **kwargs) diff --git a/freqtrade/plugins/pairlist/FullTradesFilter.py b/freqtrade/plugins/pairlist/FullTradesFilter.py index 13586611d..caa69cb1e 100644 --- a/freqtrade/plugins/pairlist/FullTradesFilter.py +++ b/freqtrade/plugins/pairlist/FullTradesFilter.py @@ -7,13 +7,15 @@ from typing import List from freqtrade.exchange.types import Tickers from freqtrade.persistence import Trade -from freqtrade.plugins.pairlist.IPairList import IPairList +from freqtrade.plugins.pairlist.IPairList import IPairList, SupportsBacktesting logger = logging.getLogger(__name__) class FullTradesFilter(IPairList): + supports_backtesting = SupportsBacktesting.NO_ACTION + @property def needstickers(self) -> bool: """ diff --git a/freqtrade/plugins/pairlist/MarketCapPairList.py b/freqtrade/plugins/pairlist/MarketCapPairList.py index 648766e20..9bbc7aa64 100644 --- a/freqtrade/plugins/pairlist/MarketCapPairList.py +++ b/freqtrade/plugins/pairlist/MarketCapPairList.py @@ -11,7 +11,7 @@ from cachetools import TTLCache from freqtrade.exceptions import OperationalException from freqtrade.exchange.types import Tickers -from freqtrade.plugins.pairlist.IPairList import IPairList, PairlistParameter +from freqtrade.plugins.pairlist.IPairList import IPairList, PairlistParameter, SupportsBacktesting from freqtrade.util.coin_gecko import FtCoinGeckoApi @@ -20,6 +20,7 @@ logger = logging.getLogger(__name__) class MarketCapPairList(IPairList): is_pairlist_generator = True + supports_backtesting = SupportsBacktesting.TODAYS_DATA def __init__(self, *args, **kwargs) -> None: super().__init__(*args, **kwargs) diff --git a/freqtrade/plugins/pairlist/OffsetFilter.py b/freqtrade/plugins/pairlist/OffsetFilter.py index bd981358e..5defaaf60 100644 --- a/freqtrade/plugins/pairlist/OffsetFilter.py +++ b/freqtrade/plugins/pairlist/OffsetFilter.py @@ -7,13 +7,15 @@ from typing import Dict, List from freqtrade.exceptions import OperationalException from freqtrade.exchange.types import Tickers -from freqtrade.plugins.pairlist.IPairList import IPairList, PairlistParameter +from freqtrade.plugins.pairlist.IPairList import IPairList, PairlistParameter, SupportsBacktesting logger = logging.getLogger(__name__) class OffsetFilter(IPairList): + supports_backtesting = SupportsBacktesting.YES + def __init__(self, *args, **kwargs) -> None: super().__init__(*args, **kwargs) diff --git a/freqtrade/plugins/pairlist/PerformanceFilter.py b/freqtrade/plugins/pairlist/PerformanceFilter.py index c10ae7394..77a2caf56 100644 --- a/freqtrade/plugins/pairlist/PerformanceFilter.py +++ b/freqtrade/plugins/pairlist/PerformanceFilter.py @@ -9,13 +9,15 @@ import pandas as pd from freqtrade.exchange.types import Tickers from freqtrade.persistence import Trade -from freqtrade.plugins.pairlist.IPairList import IPairList, PairlistParameter +from freqtrade.plugins.pairlist.IPairList import IPairList, PairlistParameter, SupportsBacktesting logger = logging.getLogger(__name__) class PerformanceFilter(IPairList): + supports_backtesting = SupportsBacktesting.NO_ACTION + def __init__(self, *args, **kwargs) -> None: super().__init__(*args, **kwargs) diff --git a/freqtrade/plugins/pairlist/PrecisionFilter.py b/freqtrade/plugins/pairlist/PrecisionFilter.py index b2f767a67..50a5fe121 100644 --- a/freqtrade/plugins/pairlist/PrecisionFilter.py +++ b/freqtrade/plugins/pairlist/PrecisionFilter.py @@ -8,13 +8,15 @@ from typing import Optional from freqtrade.exceptions import OperationalException from freqtrade.exchange import ROUND_UP from freqtrade.exchange.types import Ticker -from freqtrade.plugins.pairlist.IPairList import IPairList +from freqtrade.plugins.pairlist.IPairList import IPairList, SupportsBacktesting logger = logging.getLogger(__name__) class PrecisionFilter(IPairList): + supports_backtesting = SupportsBacktesting.TODAYS_DATA + def __init__(self, *args, **kwargs) -> None: super().__init__(*args, **kwargs) diff --git a/freqtrade/plugins/pairlist/PriceFilter.py b/freqtrade/plugins/pairlist/PriceFilter.py index d651533ce..c8405dc9b 100644 --- a/freqtrade/plugins/pairlist/PriceFilter.py +++ b/freqtrade/plugins/pairlist/PriceFilter.py @@ -7,13 +7,15 @@ from typing import Dict, Optional from freqtrade.exceptions import OperationalException from freqtrade.exchange.types import Ticker -from freqtrade.plugins.pairlist.IPairList import IPairList, PairlistParameter +from freqtrade.plugins.pairlist.IPairList import IPairList, PairlistParameter, SupportsBacktesting logger = logging.getLogger(__name__) class PriceFilter(IPairList): + supports_backtesting = SupportsBacktesting.TODAYS_DATA + def __init__(self, *args, **kwargs) -> None: super().__init__(*args, **kwargs) diff --git a/freqtrade/plugins/pairlist/ProducerPairList.py b/freqtrade/plugins/pairlist/ProducerPairList.py index 09a0c49d2..18af7a734 100644 --- a/freqtrade/plugins/pairlist/ProducerPairList.py +++ b/freqtrade/plugins/pairlist/ProducerPairList.py @@ -9,7 +9,7 @@ from typing import Dict, List, Optional from freqtrade.exceptions import OperationalException from freqtrade.exchange.types import Tickers -from freqtrade.plugins.pairlist.IPairList import IPairList, PairlistParameter +from freqtrade.plugins.pairlist.IPairList import IPairList, PairlistParameter, SupportsBacktesting logger = logging.getLogger(__name__) @@ -31,6 +31,7 @@ class ProducerPairList(IPairList): """ is_pairlist_generator = True + supports_backtesting = SupportsBacktesting.NO def __init__(self, *args, **kwargs) -> None: super().__init__(*args, **kwargs) diff --git a/freqtrade/plugins/pairlist/RemotePairList.py b/freqtrade/plugins/pairlist/RemotePairList.py index 26fadb9ae..25c5a56d5 100644 --- a/freqtrade/plugins/pairlist/RemotePairList.py +++ b/freqtrade/plugins/pairlist/RemotePairList.py @@ -16,7 +16,7 @@ from freqtrade import __version__ from freqtrade.configuration.load_config import CONFIG_PARSE_MODE from freqtrade.exceptions import OperationalException from freqtrade.exchange.types import Tickers -from freqtrade.plugins.pairlist.IPairList import IPairList, PairlistParameter +from freqtrade.plugins.pairlist.IPairList import IPairList, PairlistParameter, SupportsBacktesting from freqtrade.plugins.pairlist.pairlist_helpers import expand_pairlist @@ -25,6 +25,8 @@ logger = logging.getLogger(__name__) class RemotePairList(IPairList): is_pairlist_generator = True + # Potential winner bias + supports_backtesting = SupportsBacktesting.TODAYS_DATA def __init__(self, *args, **kwargs) -> None: super().__init__(*args, **kwargs) diff --git a/freqtrade/plugins/pairlist/ShuffleFilter.py b/freqtrade/plugins/pairlist/ShuffleFilter.py index 59ac1ac7c..3882ec8a8 100644 --- a/freqtrade/plugins/pairlist/ShuffleFilter.py +++ b/freqtrade/plugins/pairlist/ShuffleFilter.py @@ -9,7 +9,7 @@ from typing import Dict, List, Literal from freqtrade.enums import RunMode from freqtrade.exchange import timeframe_to_seconds from freqtrade.exchange.types import Tickers -from freqtrade.plugins.pairlist.IPairList import IPairList, PairlistParameter +from freqtrade.plugins.pairlist.IPairList import IPairList, PairlistParameter, SupportsBacktesting from freqtrade.util.periodic_cache import PeriodicCache @@ -19,6 +19,8 @@ ShuffleValues = Literal["candle", "iteration"] class ShuffleFilter(IPairList): + supports_backtesting = SupportsBacktesting.YES + def __init__(self, *args, **kwargs) -> None: super().__init__(*args, **kwargs) diff --git a/freqtrade/plugins/pairlist/SpreadFilter.py b/freqtrade/plugins/pairlist/SpreadFilter.py index 736903abd..00109abb3 100644 --- a/freqtrade/plugins/pairlist/SpreadFilter.py +++ b/freqtrade/plugins/pairlist/SpreadFilter.py @@ -7,13 +7,15 @@ from typing import Dict, Optional from freqtrade.exceptions import OperationalException from freqtrade.exchange.types import Ticker -from freqtrade.plugins.pairlist.IPairList import IPairList, PairlistParameter +from freqtrade.plugins.pairlist.IPairList import IPairList, PairlistParameter, SupportsBacktesting logger = logging.getLogger(__name__) class SpreadFilter(IPairList): + supports_backtesting = SupportsBacktesting.NO + def __init__(self, *args, **kwargs) -> None: super().__init__(*args, **kwargs) diff --git a/freqtrade/plugins/pairlist/StaticPairList.py b/freqtrade/plugins/pairlist/StaticPairList.py index 922d0fd94..c4f322353 100644 --- a/freqtrade/plugins/pairlist/StaticPairList.py +++ b/freqtrade/plugins/pairlist/StaticPairList.py @@ -9,7 +9,7 @@ from copy import deepcopy from typing import Dict, List from freqtrade.exchange.types import Tickers -from freqtrade.plugins.pairlist.IPairList import IPairList, PairlistParameter +from freqtrade.plugins.pairlist.IPairList import IPairList, PairlistParameter, SupportsBacktesting logger = logging.getLogger(__name__) @@ -17,6 +17,7 @@ logger = logging.getLogger(__name__) class StaticPairList(IPairList): is_pairlist_generator = True + supports_backtesting = SupportsBacktesting.YES def __init__(self, *args, **kwargs) -> None: super().__init__(*args, **kwargs) diff --git a/freqtrade/plugins/pairlist/VolatilityFilter.py b/freqtrade/plugins/pairlist/VolatilityFilter.py index f5af2d0a7..2d11e45ef 100644 --- a/freqtrade/plugins/pairlist/VolatilityFilter.py +++ b/freqtrade/plugins/pairlist/VolatilityFilter.py @@ -15,7 +15,7 @@ from freqtrade.constants import ListPairsWithTimeframes from freqtrade.exceptions import OperationalException from freqtrade.exchange.types import Tickers from freqtrade.misc import plural -from freqtrade.plugins.pairlist.IPairList import IPairList, PairlistParameter +from freqtrade.plugins.pairlist.IPairList import IPairList, PairlistParameter, SupportsBacktesting from freqtrade.util import dt_floor_day, dt_now, dt_ts @@ -27,6 +27,8 @@ class VolatilityFilter(IPairList): Filters pairs by volatility """ + supports_backtesting = SupportsBacktesting.NO + def __init__(self, *args, **kwargs) -> None: super().__init__(*args, **kwargs) diff --git a/freqtrade/plugins/pairlist/VolumePairList.py b/freqtrade/plugins/pairlist/VolumePairList.py index ea172f140..7cc91f743 100644 --- a/freqtrade/plugins/pairlist/VolumePairList.py +++ b/freqtrade/plugins/pairlist/VolumePairList.py @@ -14,7 +14,7 @@ from freqtrade.constants import ListPairsWithTimeframes from freqtrade.exceptions import OperationalException from freqtrade.exchange import timeframe_to_minutes, timeframe_to_prev_date from freqtrade.exchange.types import Tickers -from freqtrade.plugins.pairlist.IPairList import IPairList, PairlistParameter +from freqtrade.plugins.pairlist.IPairList import IPairList, PairlistParameter, SupportsBacktesting from freqtrade.util import dt_now, format_ms_time @@ -26,6 +26,7 @@ SORT_VALUES = ["quoteVolume"] class VolumePairList(IPairList): is_pairlist_generator = True + supports_backtesting = SupportsBacktesting.NO def __init__(self, *args, **kwargs) -> None: super().__init__(*args, **kwargs) diff --git a/freqtrade/plugins/pairlist/rangestabilityfilter.py b/freqtrade/plugins/pairlist/rangestabilityfilter.py index 473e003b6..175e5b18a 100644 --- a/freqtrade/plugins/pairlist/rangestabilityfilter.py +++ b/freqtrade/plugins/pairlist/rangestabilityfilter.py @@ -13,7 +13,7 @@ from freqtrade.constants import ListPairsWithTimeframes from freqtrade.exceptions import OperationalException from freqtrade.exchange.types import Tickers from freqtrade.misc import plural -from freqtrade.plugins.pairlist.IPairList import IPairList, PairlistParameter +from freqtrade.plugins.pairlist.IPairList import IPairList, PairlistParameter, SupportsBacktesting from freqtrade.util import dt_floor_day, dt_now, dt_ts @@ -21,6 +21,8 @@ logger = logging.getLogger(__name__) class RangeStabilityFilter(IPairList): + supports_backtesting = SupportsBacktesting.NO + def __init__(self, *args, **kwargs) -> None: super().__init__(*args, **kwargs) From 23256466e7a650a0472ff6781186de87efdc223b Mon Sep 17 00:00:00 2001 From: Matthias Date: Fri, 21 Jun 2024 14:15:33 +0200 Subject: [PATCH 3/6] Update pairlist tests accordingly --- tests/plugins/test_pairlist.py | 5 ++++- 1 file changed, 4 insertions(+), 1 deletion(-) diff --git a/tests/plugins/test_pairlist.py b/tests/plugins/test_pairlist.py index f6c58a1e7..8be89d23d 100644 --- a/tests/plugins/test_pairlist.py +++ b/tests/plugins/test_pairlist.py @@ -38,6 +38,7 @@ TESTABLE_PAIRLISTS = [p for p in AVAILABLE_PAIRLISTS if p not in ["RemotePairLis @pytest.fixture(scope="function") def whitelist_conf(default_conf): + default_conf["runmode"] = "dry_run" default_conf["stake_currency"] = "BTC" default_conf["exchange"]["pair_whitelist"] = [ "ETH/BTC", @@ -68,6 +69,7 @@ def whitelist_conf(default_conf): @pytest.fixture(scope="function") def whitelist_conf_2(default_conf): + default_conf["runmode"] = "dry_run" default_conf["stake_currency"] = "BTC" default_conf["exchange"]["pair_whitelist"] = [ "ETH/BTC", @@ -94,6 +96,7 @@ def whitelist_conf_2(default_conf): @pytest.fixture(scope="function") def whitelist_conf_agefilter(default_conf): + default_conf["runmode"] = "dry_run" default_conf["stake_currency"] = "BTC" default_conf["exchange"]["pair_whitelist"] = [ "ETH/BTC", @@ -773,7 +776,7 @@ def test_VolumePairList_whitelist_gen( whitelist_result, caplog, ) -> None: - whitelist_conf["runmode"] = "backtest" + whitelist_conf["runmode"] = "util_exchange" whitelist_conf["pairlists"] = pairlists whitelist_conf["stake_currency"] = base_currency From dd42a79234db9e8a644b9b7690fd36501edf774a Mon Sep 17 00:00:00 2001 From: Matthias Date: Fri, 21 Jun 2024 14:32:51 +0200 Subject: [PATCH 4/6] Remove most explicit "supports backtesting" checks --- freqtrade/optimize/backtesting.py | 2 -- tests/optimize/test_backtesting.py | 17 ++--------------- 2 files changed, 2 insertions(+), 17 deletions(-) diff --git a/freqtrade/optimize/backtesting.py b/freqtrade/optimize/backtesting.py index 03b026744..a71ec170d 100644 --- a/freqtrade/optimize/backtesting.py +++ b/freqtrade/optimize/backtesting.py @@ -217,8 +217,6 @@ class Backtesting: raise OperationalException( "VolumePairList not allowed for backtesting. Please use StaticPairList instead." ) - if "PerformanceFilter" in self.pairlists.name_list: - raise OperationalException("PerformanceFilter not allowed for backtesting.") if len(self.strategylist) > 1 and "PrecisionFilter" in self.pairlists.name_list: raise OperationalException( diff --git a/tests/optimize/test_backtesting.py b/tests/optimize/test_backtesting.py index 6e182e6e8..e9829a8cc 100644 --- a/tests/optimize/test_backtesting.py +++ b/tests/optimize/test_backtesting.py @@ -429,7 +429,7 @@ def test_backtesting_start_no_data(default_conf, mocker, caplog, testdatadir) -> backtesting.start() -def test_backtesting_no_pair_left(default_conf, mocker, caplog, testdatadir) -> None: +def test_backtesting_no_pair_left(default_conf, mocker) -> None: mocker.patch(f"{EXMS}.exchange_has", MagicMock(return_value=True)) mocker.patch( "freqtrade.data.history.history_utils.load_pair_history", @@ -449,13 +449,6 @@ def test_backtesting_no_pair_left(default_conf, mocker, caplog, testdatadir) -> with pytest.raises(OperationalException, match="No pair in whitelist."): Backtesting(default_conf) - default_conf["pairlists"] = [{"method": "VolumePairList", "number_assets": 5}] - with pytest.raises( - OperationalException, - match=r"VolumePairList not allowed for backtesting\..*StaticPairList.*", - ): - Backtesting(default_conf) - default_conf.update( { "pairlists": [{"method": "StaticPairList"}], @@ -469,7 +462,7 @@ def test_backtesting_no_pair_left(default_conf, mocker, caplog, testdatadir) -> Backtesting(default_conf) -def test_backtesting_pairlist_list(default_conf, mocker, caplog, testdatadir, tickers) -> None: +def test_backtesting_pairlist_list(default_conf, mocker, tickers) -> None: mocker.patch(f"{EXMS}.exchange_has", MagicMock(return_value=True)) mocker.patch(f"{EXMS}.get_tickers", tickers) mocker.patch(f"{EXMS}.price_to_precision", lambda s, x, y: y) @@ -495,12 +488,6 @@ def test_backtesting_pairlist_list(default_conf, mocker, caplog, testdatadir, ti ): Backtesting(default_conf) - default_conf["pairlists"] = [{"method": "StaticPairList"}, {"method": "PerformanceFilter"}] - with pytest.raises( - OperationalException, match="PerformanceFilter not allowed for backtesting." - ): - Backtesting(default_conf) - default_conf["pairlists"] = [ {"method": "StaticPairList"}, {"method": "PrecisionFilter"}, From 282198a81c4d004088ba32eb36b013ec61756052 Mon Sep 17 00:00:00 2001 From: Matthias Date: Fri, 21 Jun 2024 17:43:27 +0200 Subject: [PATCH 5/6] update enum value to Biased --- freqtrade/plugins/pairlist/IPairList.py | 2 +- freqtrade/plugins/pairlist/MarketCapPairList.py | 2 +- freqtrade/plugins/pairlist/PrecisionFilter.py | 2 +- freqtrade/plugins/pairlist/PriceFilter.py | 2 +- freqtrade/plugins/pairlist/RemotePairList.py | 2 +- freqtrade/plugins/pairlistmanager.py | 2 +- 6 files changed, 6 insertions(+), 6 deletions(-) diff --git a/freqtrade/plugins/pairlist/IPairList.py b/freqtrade/plugins/pairlist/IPairList.py index c3d1a10ad..e84700f8f 100644 --- a/freqtrade/plugins/pairlist/IPairList.py +++ b/freqtrade/plugins/pairlist/IPairList.py @@ -60,7 +60,7 @@ class SupportsBacktesting(str, Enum): YES = "yes" NO = "no" NO_ACTION = "no_action" - TODAYS_DATA = "todays_data" + BIASED = "biased" class IPairList(LoggingMixin, ABC): diff --git a/freqtrade/plugins/pairlist/MarketCapPairList.py b/freqtrade/plugins/pairlist/MarketCapPairList.py index 9bbc7aa64..677abed4b 100644 --- a/freqtrade/plugins/pairlist/MarketCapPairList.py +++ b/freqtrade/plugins/pairlist/MarketCapPairList.py @@ -20,7 +20,7 @@ logger = logging.getLogger(__name__) class MarketCapPairList(IPairList): is_pairlist_generator = True - supports_backtesting = SupportsBacktesting.TODAYS_DATA + supports_backtesting = SupportsBacktesting.BIASED def __init__(self, *args, **kwargs) -> None: super().__init__(*args, **kwargs) diff --git a/freqtrade/plugins/pairlist/PrecisionFilter.py b/freqtrade/plugins/pairlist/PrecisionFilter.py index 50a5fe121..660ff8fea 100644 --- a/freqtrade/plugins/pairlist/PrecisionFilter.py +++ b/freqtrade/plugins/pairlist/PrecisionFilter.py @@ -15,7 +15,7 @@ logger = logging.getLogger(__name__) class PrecisionFilter(IPairList): - supports_backtesting = SupportsBacktesting.TODAYS_DATA + supports_backtesting = SupportsBacktesting.BIASED def __init__(self, *args, **kwargs) -> None: super().__init__(*args, **kwargs) diff --git a/freqtrade/plugins/pairlist/PriceFilter.py b/freqtrade/plugins/pairlist/PriceFilter.py index c8405dc9b..3da7d8443 100644 --- a/freqtrade/plugins/pairlist/PriceFilter.py +++ b/freqtrade/plugins/pairlist/PriceFilter.py @@ -14,7 +14,7 @@ logger = logging.getLogger(__name__) class PriceFilter(IPairList): - supports_backtesting = SupportsBacktesting.TODAYS_DATA + supports_backtesting = SupportsBacktesting.BIASED def __init__(self, *args, **kwargs) -> None: super().__init__(*args, **kwargs) diff --git a/freqtrade/plugins/pairlist/RemotePairList.py b/freqtrade/plugins/pairlist/RemotePairList.py index 25c5a56d5..317aad20b 100644 --- a/freqtrade/plugins/pairlist/RemotePairList.py +++ b/freqtrade/plugins/pairlist/RemotePairList.py @@ -26,7 +26,7 @@ logger = logging.getLogger(__name__) class RemotePairList(IPairList): is_pairlist_generator = True # Potential winner bias - supports_backtesting = SupportsBacktesting.TODAYS_DATA + supports_backtesting = SupportsBacktesting.BIASED def __init__(self, *args, **kwargs) -> None: super().__init__(*args, **kwargs) diff --git a/freqtrade/plugins/pairlistmanager.py b/freqtrade/plugins/pairlistmanager.py index 30b3d74f8..803a60d18 100644 --- a/freqtrade/plugins/pairlistmanager.py +++ b/freqtrade/plugins/pairlistmanager.py @@ -75,7 +75,7 @@ class PairListManager(LoggingMixin): pairlist_errors.append(pairlist_handler.name) if pairlist_handler.supports_backtesting == SupportsBacktesting.NO_ACTION: noaction_pairlists.append(pairlist_handler.name) - if pairlist_handler.supports_backtesting == SupportsBacktesting.TODAYS_DATA: + if pairlist_handler.supports_backtesting == SupportsBacktesting.BIASED: biased_pairlists.append(pairlist_handler.name) if noaction_pairlists: From 605f53a60241f9bdebe40b122f41e6f4994f380a Mon Sep 17 00:00:00 2001 From: Matthias Date: Fri, 21 Jun 2024 17:57:38 +0200 Subject: [PATCH 6/6] Add test for new warning modes --- tests/plugins/test_pairlist.py | 62 ++++++++++++++++++++++++++++++++++ 1 file changed, 62 insertions(+) diff --git a/tests/plugins/test_pairlist.py b/tests/plugins/test_pairlist.py index 8be89d23d..37ebdc58b 100644 --- a/tests/plugins/test_pairlist.py +++ b/tests/plugins/test_pairlist.py @@ -2390,3 +2390,65 @@ def test_MarketCapPairList_exceptions(mocker, default_conf_usdt): OperationalException, match="This filter only support marketcap rank up to 250." ): PairListManager(exchange, default_conf_usdt) + + +@pytest.mark.parametrize( + "pairlists,expected_error,expected_warning", + [ + ( + [{"method": "StaticPairList"}], + None, # Error + None, # Warning + ), + ( + [{"method": "VolumePairList", "number_assets": 10}], + "VolumePairList", # Error + None, # Warning + ), + ( + [{"method": "MarketCapPairList", "number_assets": 10}], + None, # Error + r"MarketCapPairList.*lookahead.*", # Warning + ), + ( + [{"method": "StaticPairList"}, {"method": "FullTradesFilter"}], + None, # Error + r"FullTradesFilter do not generate.*", # Warning + ), + ( # combi, fails and warns + [ + {"method": "VolumePairList", "number_assets": 10}, + {"method": "MarketCapPairList", "number_assets": 10}, + ], + "VolumePairList", # Error + r"MarketCapPairList.*lookahead.*", # Warning + ), + ], +) +def test_backtesting_modes( + mocker, default_conf_usdt, pairlists, expected_error, expected_warning, caplog, markets, tickers +): + default_conf_usdt["runmode"] = "dry_run" + default_conf_usdt["pairlists"] = pairlists + + mocker.patch.multiple( + EXMS, + markets=PropertyMock(return_value=markets), + exchange_has=MagicMock(return_value=True), + get_tickers=tickers, + ) + exchange = get_patched_exchange(mocker, default_conf_usdt) + + # Dry run mode - works always + PairListManager(exchange, default_conf_usdt) + + default_conf_usdt["runmode"] = "backtest" + if expected_error: + with pytest.raises(OperationalException, match=f"Pairlist Handlers {expected_error}.*"): + PairListManager(exchange, default_conf_usdt) + + if not expected_error: + PairListManager(exchange, default_conf_usdt) + + if expected_warning: + assert log_has_re(f"Pairlist Handlers {expected_warning}", caplog)