[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

@ -681,9 +681,47 @@ In some situations it may be confusing to deal with stops relative to current ra
### *@informative()*
``` python
def informative(timeframe: str, asset: str = '',
fmt: Optional[Union[str, Callable[[KwArg(str)], str]]] = None,
ffill: bool = True) -> Callable[[PopulateIndicators], PopulateIndicators]:
"""
A decorator for populate_indicators_Nn(self, dataframe, metadata), allowing these functions to
define informative indicators.
Example usage:
@informative('1h')
def populate_indicators_1h(self, dataframe: DataFrame, metadata: dict) -> DataFrame:
dataframe['rsi'] = ta.RSI(dataframe, timeperiod=14)
return dataframe
: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:
* {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.
"""
```
In most common case it is possible to easily define informative pairs by using a decorator. All decorated `populate_indicators_*` methods run in isolation,
not having access to data from other informative pairs, in the end all informative dataframes are merged and passed to main `populate_indicators()` method.
When hyperopting, please follow instructions of [optimizing an indicator parameter](hyperopt.md#optimizing-an-indicator-parameter).
When hyperopting, use of hyperoptable parameter `.value` attribute is not supported. Please use `.range` attribute. See [optimizing an indicator parameter](hyperopt.md#optimizing-an-indicator-parameter)
for more information.
??? Example "Fast and easy way to define informative pairs"
@ -725,17 +763,9 @@ When hyperopting, please follow instructions of [optimizing an indicator paramet
return dataframe
# Define BTC/STAKE informative pair. A custom formatter may be specified for formatting
# column names. 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.
# A callable `fmt(**kwargs) -> str` may be specified, to implement custom formatting.
# Available in populate_indicators and other methods as 'rsi_upper'.
@informative('1h', 'BTC/{stake}', '{name}')
# column names. A callable `fmt(**kwargs) -> str` may be specified, to implement custom
# formatting. Available in populate_indicators and other methods as 'rsi_upper'.
@informative('1h', 'BTC/{stake}', '{column}')
def populate_indicators_btc_1h_2(self, dataframe: DataFrame, metadata: dict) -> DataFrame:
dataframe['rsi_upper'] = ta.RSI(dataframe, timeperiod=14)
return dataframe
@ -749,8 +779,6 @@ When hyperopting, please follow instructions of [optimizing an indicator paramet
```
See docstring of `@informative()` decorator for more information.
!!! Note
Do not use `@informative` decorator if you need to use data of one informative pair when generating another informative pair. Instead, define informative pairs
manually as described [in the DataProvider section](#complete-data-provider-sample).

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)