From dfa61b7ad2297a94b0b26309ca74c64a7c1d08a7 Mon Sep 17 00:00:00 2001 From: Rokas Kupstys Date: Tue, 7 Sep 2021 15:40:53 +0300 Subject: [PATCH] [SQUASH] Fix informatives for each pair not being created because dataprovider was not available. Fix not being able to have informative dataframe of a pair in whitelist. --- docs/strategy-customization.md | 4 +-- freqtrade/freqtradebot.py | 10 ++++--- freqtrade/optimize/backtesting.py | 3 +- freqtrade/optimize/edge_cli.py | 3 ++ freqtrade/strategy/interface.py | 38 +++++++++++++++---------- freqtrade/strategy/strategy_helper.py | 13 ++++----- tests/strategy/test_strategy_helpers.py | 9 +++--- 7 files changed, 47 insertions(+), 33 deletions(-) diff --git a/docs/strategy-customization.md b/docs/strategy-customization.md index f2bf6cf7c..a994d9acd 100644 --- a/docs/strategy-customization.md +++ b/docs/strategy-customization.md @@ -702,9 +702,9 @@ def informative(timeframe: str, asset: str = '', :param fmt: Column format (str) or column formatter (callable(name, asset, timeframe)). When not specified, defaults to: * {base}_{column}_{timeframe} if asset is specified and quote currency does match stake - curerncy. + currency. * {base}_{quote}_{column}_{timeframe} if asset is specified and quote currency does not match - stake curerncy. + stake currency. * {column}_{timeframe} if asset is not specified. Format string supports these format variables: * {asset} - full name of the asset, for example 'BTC/USDT'. diff --git a/freqtrade/freqtradebot.py b/freqtrade/freqtradebot.py index bdc438c9a..b79916639 100644 --- a/freqtrade/freqtradebot.py +++ b/freqtrade/freqtradebot.py @@ -83,10 +83,12 @@ class FreqtradeBot(LoggingMixin): self.dataprovider = DataProvider(self.config, self.exchange, self.pairlists) - # Attach Dataprovider to Strategy baseclass - IStrategy.dp = self.dataprovider - # Attach Wallets to Strategy baseclass - IStrategy.wallets = self.wallets + # Attach Dataprovider to strategy instance + self.strategy.dp = self.dataprovider + # Attach Wallets to strategy instance + self.strategy.wallets = self.wallets + # Late initialization (may depend on dp/wallets) + self.strategy._initialize() # Initializing Edge only if enabled self.edge = Edge(self.config, self.exchange, self.strategy) if \ diff --git a/freqtrade/optimize/backtesting.py b/freqtrade/optimize/backtesting.py index 3e06bfa1b..ef491ae5e 100644 --- a/freqtrade/optimize/backtesting.py +++ b/freqtrade/optimize/backtesting.py @@ -154,11 +154,12 @@ class Backtesting: self.strategy: IStrategy = strategy strategy.dp = self.dataprovider # Attach Wallets to Strategy baseclass - IStrategy.wallets = self.wallets + strategy.wallets = self.wallets # Set stoploss_on_exchange to false for backtesting, # since a "perfect" stoploss-sell is assumed anyway # And the regular "stoploss" function would not apply to that case self.strategy.order_types['stoploss_on_exchange'] = False + strategy._initialize() def _load_protections(self, strategy: IStrategy): if self.config.get('enable_protections', False): diff --git a/freqtrade/optimize/edge_cli.py b/freqtrade/optimize/edge_cli.py index 417faa685..abb5ca635 100644 --- a/freqtrade/optimize/edge_cli.py +++ b/freqtrade/optimize/edge_cli.py @@ -8,6 +8,7 @@ from typing import Any, Dict from freqtrade import constants from freqtrade.configuration import TimeRange, validate_config_consistency +from freqtrade.data.dataprovider import DataProvider from freqtrade.edge import Edge from freqtrade.optimize.optimize_reports import generate_edge_table from freqtrade.resolvers import ExchangeResolver, StrategyResolver @@ -33,6 +34,8 @@ class EdgeCli: self.config['stake_amount'] = constants.UNLIMITED_STAKE_AMOUNT self.exchange = ExchangeResolver.load_exchange(self.config['exchange']['name'], self.config) self.strategy = StrategyResolver.load_strategy(self.config) + self.strategy.dp = DataProvider(config, None) + self.strategy._initialize() validate_config_consistency(self.config) diff --git a/freqtrade/strategy/interface.py b/freqtrade/strategy/interface.py index 0546deb01..951979212 100644 --- a/freqtrade/strategy/interface.py +++ b/freqtrade/strategy/interface.py @@ -137,9 +137,13 @@ class IStrategy(ABC, HyperStrategyMixin): self._last_candle_seen_per_pair: Dict[str, datetime] = {} super().__init__(config) + def _initialize(self): + """ + Late initialization tasks, which may depend on availability of dataprovider/wallets/etc. + """ # Gather informative pairs from @informative-decorated methods. self._ft_informative: Dict[ - Tuple[str, str], Tuple[InformativeData, PopulateIndicators]] = {} + Tuple[str, str], List[Tuple[InformativeData, PopulateIndicators]]] = {} for attr_name in dir(self.__class__): cls_method = getattr(self.__class__, attr_name) if not callable(cls_method): @@ -158,16 +162,19 @@ class IStrategy(ABC, HyperStrategyMixin): 'strategy timeframe!') if asset: pair = _format_pair_name(self.config, asset) - if (pair, timeframe) in self._ft_informative: - raise OperationalException(f'Informative pair {pair} {timeframe} can not ' - f'be defined more than once!') - self._ft_informative[(pair, timeframe)] = (informative_data, cls_method) - elif self.dp is not None: + try: + self._ft_informative[(pair, timeframe)].append( + (informative_data, cls_method)) + except KeyError: + self._ft_informative[(pair, timeframe)] = [(informative_data, cls_method)] + else: for pair in self.dp.current_whitelist(): - if (pair, timeframe) in self._ft_informative: - raise OperationalException(f'Informative pair {pair} {timeframe} can ' - f'not be defined more than once!') - self._ft_informative[(pair, timeframe)] = (informative_data, cls_method) + try: + self._ft_informative[(pair, timeframe)].append( + (informative_data, cls_method)) + except KeyError: + self._ft_informative[(pair, timeframe)] = \ + [(informative_data, cls_method)] @abstractmethod def populate_indicators(self, dataframe: DataFrame, metadata: dict) -> DataFrame: @@ -838,11 +845,12 @@ class IStrategy(ABC, HyperStrategyMixin): logger.debug(f"Populating indicators for pair {metadata.get('pair')}.") # call populate_indicators_Nm() which were tagged with @informative decorator. - for (pair, timeframe), (informative_data, populate_fn) in self._ft_informative.items(): - if not informative_data.asset and pair != metadata['pair']: - continue - dataframe = _create_and_merge_informative_pair( - self, dataframe, metadata, informative_data, populate_fn) + for (pair, timeframe), informatives in self._ft_informative.items(): + for (informative_data, populate_fn) in informatives: + if not informative_data.asset and pair != metadata['pair']: + continue + dataframe = _create_and_merge_informative_pair( + self, dataframe, metadata, informative_data, populate_fn) if self._populate_fun_len == 2: warnings.warn("deprecated - check out the Sample strategy to see " diff --git a/freqtrade/strategy/strategy_helper.py b/freqtrade/strategy/strategy_helper.py index 64d9bdea8..a4023f953 100644 --- a/freqtrade/strategy/strategy_helper.py +++ b/freqtrade/strategy/strategy_helper.py @@ -139,9 +139,9 @@ def informative(timeframe: str, asset: str = '', :param fmt: Column format (str) or column formatter (callable(name, asset, timeframe)). When not specified, defaults to: * {base}_{column}_{timeframe} if asset is specified and quote currency does match stake - curerncy. + currency. * {base}_{quote}_{column}_{timeframe} if asset is specified and quote currency does not match - stake curerncy. + stake currency. * {column}_{timeframe} if asset is not specified. Format string supports these format variables: * {asset} - full name of the asset, for example 'BTC/USDT'. @@ -203,11 +203,10 @@ def _create_and_merge_informative_pair(strategy, dataframe: DataFrame, # fmt='{base}_{quote}_{column}_{timeframe}' format or similar. if not fmt: fmt = '{column}_{timeframe}' # Informatives of current pair - if asset != metadata['pair']: - if quote == config['stake_currency']: - fmt = '{base}_' + fmt # Informatives of other pair - else: - fmt = '{base}_{quote}_' + fmt # Informatives of different quote currency + if quote != config['stake_currency']: + fmt = '{quote}_' + fmt # Informatives of different quote currency + if informative_data.asset: + fmt = '{base}_' + fmt # Informatives of other pair inf_metadata = {'pair': asset, 'timeframe': timeframe} inf_dataframe = dp.get_pair_dataframe(asset, timeframe) diff --git a/tests/strategy/test_strategy_helpers.py b/tests/strategy/test_strategy_helpers.py index 7784f3f77..0ee554ede 100644 --- a/tests/strategy/test_strategy_helpers.py +++ b/tests/strategy/test_strategy_helpers.py @@ -155,11 +155,12 @@ def test_informative_decorator(mocker, default_conf): } from .strats.informative_decorator_strategy import InformativeDecoratorTest default_conf['stake_currency'] = 'USDT' - InformativeDecoratorTest.dp = DataProvider({}, None, None) - mocker.patch.object(InformativeDecoratorTest.dp, 'current_whitelist', return_value=[ - 'XRP/USDT', 'LTC/USDT' - ]) strategy = InformativeDecoratorTest(config=default_conf) + strategy.dp = DataProvider({}, None, None) + mocker.patch.object(strategy.dp, 'current_whitelist', return_value=[ + 'XRP/USDT', 'LTC/USDT', 'BTC/USDT' + ]) + strategy._initialize() assert len(strategy._ft_informative) == 8 informative_pairs = [('XRP/USDT', '1h'), ('LTC/USDT', '1h'), ('XRP/USDT', '30m'),