[SQUASH] Address PR comments.

This commit is contained in:
Rokas Kupstys
2021-09-05 09:54:05 +03:00
parent 1fdb656334
commit f2a1d9d2fc
3 changed files with 72 additions and 34 deletions

View File

@@ -6,7 +6,7 @@ import logging
import warnings
from abc import ABC, abstractmethod
from datetime import datetime, timedelta, timezone
from typing import Any, Callable, Dict, List, Optional, Tuple, Union
from typing import Dict, List, Optional, Tuple, Union
import arrow
from pandas import DataFrame
@@ -19,7 +19,8 @@ from freqtrade.exchange import timeframe_to_minutes, timeframe_to_seconds
from freqtrade.exchange.exchange import timeframe_to_next_date
from freqtrade.persistence import PairLocks, Trade
from freqtrade.strategy.hyper import HyperStrategyMixin
from freqtrade.strategy.strategy_helper import (InformativeData, _create_and_merge_informative_pair,
from freqtrade.strategy.strategy_helper import (InformativeData, PopulateIndicators,
_create_and_merge_informative_pair,
_format_pair_name)
from freqtrade.strategy.strategy_wrapper import strategy_safe_wrapper
from freqtrade.wallets import Wallets
@@ -138,20 +139,23 @@ class IStrategy(ABC, HyperStrategyMixin):
# Gather informative pairs from @informative-decorated methods.
self._ft_informative: Dict[
Tuple[str, str], Tuple[InformativeData,
Callable[[Any, DataFrame, dict], DataFrame]]] = {}
Tuple[str, str], 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', [])
ft_informative = getattr(cls_method, '_ft_informative', None)
if not isinstance(ft_informative, 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:
raise OperationalException('Informative timeframe must be equal or higher than '
'strategy timeframe!')
if asset:
pair = _format_pair_name(self.config, asset)
if (pair, timeframe) in self._ft_informative:
@@ -165,10 +169,6 @@ class IStrategy(ABC, HyperStrategyMixin):
f'not be defined more than once!')
self._ft_informative[(pair, timeframe)] = (informative_data, cls_method)
def _format_pair(self, pair: str) -> str:
return pair.format(stake_currency=self.config['stake_currency'],
stake=self.config['stake_currency']).upper()
@abstractmethod
def populate_indicators(self, dataframe: DataFrame, metadata: dict) -> DataFrame:
"""

View File

@@ -8,6 +8,9 @@ from freqtrade.exceptions import OperationalException
from freqtrade.exchange import timeframe_to_minutes
PopulateIndicators = Callable[[Any, DataFrame, dict], DataFrame]
class InformativeData(NamedTuple):
asset: Optional[str]
timeframe: str
@@ -118,8 +121,7 @@ def stoploss_from_absolute(stop_rate: float, current_rate: float) -> float:
def informative(timeframe: str, asset: str = '',
fmt: Optional[Union[str, Callable[[KwArg(str)], str]]] = None,
ffill: bool = True) -> Callable[[Callable[[Any, DataFrame, dict], DataFrame]],
Callable[[Any, DataFrame, dict], DataFrame]]:
ffill: bool = True) -> Callable[[PopulateIndicators], PopulateIndicators]:
"""
A decorator for populate_indicators_Nn(self, dataframe, metadata), allowing these functions to
define informative indicators.
@@ -131,24 +133,32 @@ def informative(timeframe: str, asset: str = '',
dataframe['rsi'] = ta.RSI(dataframe, timeperiod=14)
return dataframe
:param timeframe: Informative timeframe. Must always be higher than strategy timeframe.
:param timeframe: Informative timeframe. Must always be equal or higher than strategy timeframe.
:param asset: Informative asset, for example BTC, BTC/USDT, ETH/BTC. Do not specify to use
current pair.
:param fmt: Column format (str) or column formatter (callable(name, asset, timeframe)). When not
specified, defaults to {asset}_{name}_{timeframe} if asset is specified, or {name}_{timeframe}
otherwise.
* {asset}: name of informative asset, provided in lower-case, with / replaced with _. Stake
currency is not included in this string.
* {name}: user-specified dataframe column name.
* {timeframe}: informative timeframe.
:param ffill: ffill dataframe after mering informative pair.
specified, defaults to:
* {base}_{column}_{timeframe} if asset is specified and quote currency does match stake
curerncy.
* {base}_{quote}_{column}_{timeframe} if asset is specified and quote currency does not match
stake curerncy.
* {column}_{timeframe} if asset is not specified.
Format string supports these format variables:
* {asset} - full name of the asset, for example 'BTC/USDT'.
* {base} - base currency in lower case, for example 'eth'.
* {BASE} - same as {base}, except in upper case.
* {quote} - quote currency in lower case, for example 'usdt'.
* {QUOTE} - same as {quote}, except in upper case.
* {column} - name of dataframe column.
* {timeframe} - timeframe of informative dataframe.
:param ffill: ffill dataframe after merging informative pair.
"""
_asset = asset
_timeframe = timeframe
_fmt = fmt
_ffill = ffill
def decorator(fn: Callable[[Any, DataFrame, dict], DataFrame]):
def decorator(fn: PopulateIndicators):
informative_pairs = getattr(fn, '_ft_informative', [])
informative_pairs.append(InformativeData(_asset, _timeframe, _fmt, _ffill))
setattr(fn, '_ft_informative', informative_pairs)