From 5dc78a0c66f385edd14db16a806e1f75bd453e83 Mon Sep 17 00:00:00 2001 From: Rokas Kupstys Date: Fri, 10 Sep 2021 09:36:52 +0300 Subject: [PATCH] [SQUASH] Get rid of _initialize() and fix informatives for dynamic pairlists. --- freqtrade/freqtradebot.py | 2 - freqtrade/optimize/backtesting.py | 1 - freqtrade/optimize/edge_cli.py | 1 - freqtrade/strategy/interface.py | 56 ++++++++----------------- freqtrade/strategy/strategy_helper.py | 17 ++++---- tests/strategy/test_strategy_helpers.py | 3 +- 6 files changed, 27 insertions(+), 53 deletions(-) diff --git a/freqtrade/freqtradebot.py b/freqtrade/freqtradebot.py index b79916639..1cb8988ff 100644 --- a/freqtrade/freqtradebot.py +++ b/freqtrade/freqtradebot.py @@ -87,8 +87,6 @@ class FreqtradeBot(LoggingMixin): 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 ef491ae5e..79c861ee8 100644 --- a/freqtrade/optimize/backtesting.py +++ b/freqtrade/optimize/backtesting.py @@ -159,7 +159,6 @@ class 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 abb5ca635..f211da750 100644 --- a/freqtrade/optimize/edge_cli.py +++ b/freqtrade/optimize/edge_cli.py @@ -35,7 +35,6 @@ class EdgeCli: 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 6e312e15d..00c56f5df 100644 --- a/freqtrade/strategy/interface.py +++ b/freqtrade/strategy/interface.py @@ -121,7 +121,7 @@ class IStrategy(ABC, HyperStrategyMixin): # Class level variables (intentional) containing # the dataprovider (dp) (access to other candles, historic data, ...) # and wallets - access to the current balance. - dp: Optional[DataProvider] = None + dp: DataProvider wallets: Optional[Wallets] = None # Filled from configuration stake_currency: str @@ -137,44 +137,23 @@ 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], List[Tuple[InformativeData, PopulateIndicators]]] = {} + self._ft_informative: List[Tuple[InformativeData, PopulateIndicators]] = [] for attr_name in dir(self.__class__): cls_method = getattr(self.__class__, attr_name) if not callable(cls_method): continue - ft_informative = getattr(cls_method, '_ft_informative', None) - if not isinstance(ft_informative, list): + informative_data_list = getattr(cls_method, '_ft_informative', None) + if not isinstance(informative_data_list, list): # Type check is required because mocker would return a mock object that evaluates to # True, confusing this code. continue strategy_timeframe_minutes = timeframe_to_minutes(self.timeframe) - for informative_data in ft_informative: - asset = informative_data.asset - timeframe = informative_data.timeframe - if timeframe_to_minutes(timeframe) < strategy_timeframe_minutes: + for informative_data in informative_data_list: + if timeframe_to_minutes(informative_data.timeframe) < strategy_timeframe_minutes: raise OperationalException('Informative timeframe must be equal or higher than ' 'strategy timeframe!') - if asset: - pair = _format_pair_name(self.config, asset) - 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(): - try: - self._ft_informative[(pair, timeframe)].append( - (informative_data, cls_method)) - except KeyError: - self._ft_informative[(pair, timeframe)] = \ - [(informative_data, cls_method)] + self._ft_informative.append((informative_data, cls_method)) @abstractmethod def populate_indicators(self, dataframe: DataFrame, metadata: dict) -> DataFrame: @@ -424,8 +403,13 @@ class IStrategy(ABC, HyperStrategyMixin): Internal method which gathers all informative pairs (user or automatically defined). """ informative_pairs = self.informative_pairs() - if hasattr(self, '_ft_informative'): - informative_pairs += list(self._ft_informative.keys()) + for inf_data, _ in self._ft_informative: + if inf_data.asset: + pair_tf = (_format_pair_name(self.config, inf_data.asset), inf_data.timeframe) + informative_pairs.append(pair_tf) + else: + for pair in self.dp.current_whitelist(): + informative_pairs.append((pair, inf_data.timeframe)) return list(set(informative_pairs)) def get_strategy_name(self) -> str: @@ -845,14 +829,10 @@ class IStrategy(ABC, HyperStrategyMixin): """ logger.debug(f"Populating indicators for pair {metadata.get('pair')}.") - if hasattr(self, '_ft_informative'): - # call populate_indicators_Nm() which were tagged with @informative decorator. - 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) + # call populate_indicators_Nm() which were tagged with @informative decorator. + for inf_data, populate_fn in self._ft_informative: + dataframe = _create_and_merge_informative_pair( + self, dataframe, metadata, inf_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 a4023f953..15c6d8a69 100644 --- a/freqtrade/strategy/strategy_helper.py +++ b/freqtrade/strategy/strategy_helper.py @@ -171,14 +171,13 @@ def _format_pair_name(config, pair: str) -> str: stake=config['stake_currency']).upper() -def _create_and_merge_informative_pair(strategy, dataframe: DataFrame, - metadata: dict, informative_data: InformativeData, - populate_indicators: Callable[[Any, DataFrame, dict], - DataFrame]): - asset = informative_data.asset or '' - timeframe = informative_data.timeframe - fmt = informative_data.fmt - ffill = informative_data.ffill +def _create_and_merge_informative_pair(strategy, dataframe: DataFrame, metadata: dict, + inf_data: InformativeData, + populate_indicators: PopulateIndicators): + asset = inf_data.asset or '' + timeframe = inf_data.timeframe + fmt = inf_data.fmt + ffill = inf_data.ffill config = strategy.config dp = strategy.dp @@ -205,7 +204,7 @@ def _create_and_merge_informative_pair(strategy, dataframe: DataFrame, fmt = '{column}_{timeframe}' # Informatives of current pair if quote != config['stake_currency']: fmt = '{quote}_' + fmt # Informatives of different quote currency - if informative_data.asset: + if inf_data.asset: fmt = '{base}_' + fmt # Informatives of other pair inf_metadata = {'pair': asset, 'timeframe': timeframe} diff --git a/tests/strategy/test_strategy_helpers.py b/tests/strategy/test_strategy_helpers.py index 0ee554ede..95ca0416f 100644 --- a/tests/strategy/test_strategy_helpers.py +++ b/tests/strategy/test_strategy_helpers.py @@ -160,9 +160,8 @@ def test_informative_decorator(mocker, default_conf): mocker.patch.object(strategy.dp, 'current_whitelist', return_value=[ 'XRP/USDT', 'LTC/USDT', 'BTC/USDT' ]) - strategy._initialize() - assert len(strategy._ft_informative) == 8 + assert len(strategy._ft_informative) == 6 # Equal to number of decorators used informative_pairs = [('XRP/USDT', '1h'), ('LTC/USDT', '1h'), ('XRP/USDT', '30m'), ('LTC/USDT', '30m'), ('BTC/USDT', '1h'), ('BTC/USDT', '30m'), ('BTC/USDT', '5m'), ('ETH/BTC', '1h'), ('ETH/USDT', '30m')]