[SQUASH] Get rid of _initialize() and fix informatives for dynamic pairlists.

This commit is contained in:
Rokas Kupstys 2021-09-10 09:36:52 +03:00
parent f81df19b93
commit 5dc78a0c66
6 changed files with 27 additions and 53 deletions

View File

@ -87,8 +87,6 @@ class FreqtradeBot(LoggingMixin):
self.strategy.dp = self.dataprovider self.strategy.dp = self.dataprovider
# Attach Wallets to strategy instance # Attach Wallets to strategy instance
self.strategy.wallets = self.wallets self.strategy.wallets = self.wallets
# Late initialization (may depend on dp/wallets)
self.strategy._initialize()
# Initializing Edge only if enabled # Initializing Edge only if enabled
self.edge = Edge(self.config, self.exchange, self.strategy) if \ self.edge = Edge(self.config, self.exchange, self.strategy) if \

View File

@ -159,7 +159,6 @@ class Backtesting:
# since a "perfect" stoploss-sell is assumed anyway # since a "perfect" stoploss-sell is assumed anyway
# And the regular "stoploss" function would not apply to that case # And the regular "stoploss" function would not apply to that case
self.strategy.order_types['stoploss_on_exchange'] = False self.strategy.order_types['stoploss_on_exchange'] = False
strategy._initialize()
def _load_protections(self, strategy: IStrategy): def _load_protections(self, strategy: IStrategy):
if self.config.get('enable_protections', False): if self.config.get('enable_protections', False):

View File

@ -35,7 +35,6 @@ class EdgeCli:
self.exchange = ExchangeResolver.load_exchange(self.config['exchange']['name'], self.config) self.exchange = ExchangeResolver.load_exchange(self.config['exchange']['name'], self.config)
self.strategy = StrategyResolver.load_strategy(self.config) self.strategy = StrategyResolver.load_strategy(self.config)
self.strategy.dp = DataProvider(config, None) self.strategy.dp = DataProvider(config, None)
self.strategy._initialize()
validate_config_consistency(self.config) validate_config_consistency(self.config)

View File

@ -121,7 +121,7 @@ class IStrategy(ABC, HyperStrategyMixin):
# Class level variables (intentional) containing # Class level variables (intentional) containing
# the dataprovider (dp) (access to other candles, historic data, ...) # the dataprovider (dp) (access to other candles, historic data, ...)
# and wallets - access to the current balance. # and wallets - access to the current balance.
dp: Optional[DataProvider] = None dp: DataProvider
wallets: Optional[Wallets] = None wallets: Optional[Wallets] = None
# Filled from configuration # Filled from configuration
stake_currency: str stake_currency: str
@ -137,44 +137,23 @@ class IStrategy(ABC, HyperStrategyMixin):
self._last_candle_seen_per_pair: Dict[str, datetime] = {} self._last_candle_seen_per_pair: Dict[str, datetime] = {}
super().__init__(config) 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. # Gather informative pairs from @informative-decorated methods.
self._ft_informative: Dict[ self._ft_informative: List[Tuple[InformativeData, PopulateIndicators]] = []
Tuple[str, str], List[Tuple[InformativeData, PopulateIndicators]]] = {}
for attr_name in dir(self.__class__): for attr_name in dir(self.__class__):
cls_method = getattr(self.__class__, attr_name) cls_method = getattr(self.__class__, attr_name)
if not callable(cls_method): if not callable(cls_method):
continue continue
ft_informative = getattr(cls_method, '_ft_informative', None) informative_data_list = getattr(cls_method, '_ft_informative', None)
if not isinstance(ft_informative, list): if not isinstance(informative_data_list, list):
# Type check is required because mocker would return a mock object that evaluates to # Type check is required because mocker would return a mock object that evaluates to
# True, confusing this code. # True, confusing this code.
continue continue
strategy_timeframe_minutes = timeframe_to_minutes(self.timeframe) strategy_timeframe_minutes = timeframe_to_minutes(self.timeframe)
for informative_data in ft_informative: for informative_data in informative_data_list:
asset = informative_data.asset if timeframe_to_minutes(informative_data.timeframe) < strategy_timeframe_minutes:
timeframe = informative_data.timeframe
if timeframe_to_minutes(timeframe) < strategy_timeframe_minutes:
raise OperationalException('Informative timeframe must be equal or higher than ' raise OperationalException('Informative timeframe must be equal or higher than '
'strategy timeframe!') 'strategy timeframe!')
if asset: self._ft_informative.append((informative_data, cls_method))
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)]
@abstractmethod @abstractmethod
def populate_indicators(self, dataframe: DataFrame, metadata: dict) -> DataFrame: 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). Internal method which gathers all informative pairs (user or automatically defined).
""" """
informative_pairs = self.informative_pairs() informative_pairs = self.informative_pairs()
if hasattr(self, '_ft_informative'): for inf_data, _ in self._ft_informative:
informative_pairs += list(self._ft_informative.keys()) 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)) return list(set(informative_pairs))
def get_strategy_name(self) -> str: def get_strategy_name(self) -> str:
@ -845,14 +829,10 @@ class IStrategy(ABC, HyperStrategyMixin):
""" """
logger.debug(f"Populating indicators for pair {metadata.get('pair')}.") 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.
# call populate_indicators_Nm() which were tagged with @informative decorator. for inf_data, populate_fn in self._ft_informative:
for (pair, timeframe), informatives in self._ft_informative.items(): dataframe = _create_and_merge_informative_pair(
for (informative_data, populate_fn) in informatives: self, dataframe, metadata, inf_data, populate_fn)
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: if self._populate_fun_len == 2:
warnings.warn("deprecated - check out the Sample strategy to see " warnings.warn("deprecated - check out the Sample strategy to see "

View File

@ -171,14 +171,13 @@ def _format_pair_name(config, pair: str) -> str:
stake=config['stake_currency']).upper() stake=config['stake_currency']).upper()
def _create_and_merge_informative_pair(strategy, dataframe: DataFrame, def _create_and_merge_informative_pair(strategy, dataframe: DataFrame, metadata: dict,
metadata: dict, informative_data: InformativeData, inf_data: InformativeData,
populate_indicators: Callable[[Any, DataFrame, dict], populate_indicators: PopulateIndicators):
DataFrame]): asset = inf_data.asset or ''
asset = informative_data.asset or '' timeframe = inf_data.timeframe
timeframe = informative_data.timeframe fmt = inf_data.fmt
fmt = informative_data.fmt ffill = inf_data.ffill
ffill = informative_data.ffill
config = strategy.config config = strategy.config
dp = strategy.dp dp = strategy.dp
@ -205,7 +204,7 @@ def _create_and_merge_informative_pair(strategy, dataframe: DataFrame,
fmt = '{column}_{timeframe}' # Informatives of current pair fmt = '{column}_{timeframe}' # Informatives of current pair
if quote != config['stake_currency']: if quote != config['stake_currency']:
fmt = '{quote}_' + fmt # Informatives of different quote currency fmt = '{quote}_' + fmt # Informatives of different quote currency
if informative_data.asset: if inf_data.asset:
fmt = '{base}_' + fmt # Informatives of other pair fmt = '{base}_' + fmt # Informatives of other pair
inf_metadata = {'pair': asset, 'timeframe': timeframe} inf_metadata = {'pair': asset, 'timeframe': timeframe}

View File

@ -160,9 +160,8 @@ def test_informative_decorator(mocker, default_conf):
mocker.patch.object(strategy.dp, 'current_whitelist', return_value=[ mocker.patch.object(strategy.dp, 'current_whitelist', return_value=[
'XRP/USDT', 'LTC/USDT', 'BTC/USDT' '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'), informative_pairs = [('XRP/USDT', '1h'), ('LTC/USDT', '1h'), ('XRP/USDT', '30m'),
('LTC/USDT', '30m'), ('BTC/USDT', '1h'), ('BTC/USDT', '30m'), ('LTC/USDT', '30m'), ('BTC/USDT', '1h'), ('BTC/USDT', '30m'),
('BTC/USDT', '5m'), ('ETH/BTC', '1h'), ('ETH/USDT', '30m')] ('BTC/USDT', '5m'), ('ETH/BTC', '1h'), ('ETH/USDT', '30m')]