[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()* ### *@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, 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. 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" ??? 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 return dataframe
# Define BTC/STAKE informative pair. A custom formatter may be specified for formatting # Define BTC/STAKE informative pair. A custom formatter may be specified for formatting
# column names. Format string supports these format variables: # column names. A callable `fmt(**kwargs) -> str` may be specified, to implement custom
# * {asset} - full name of the asset, for example 'BTC/USDT'. # formatting. Available in populate_indicators and other methods as 'rsi_upper'.
# * {base} - base currency in lower case, for example 'eth'. @informative('1h', 'BTC/{stake}', '{column}')
# * {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}')
def populate_indicators_btc_1h_2(self, dataframe: DataFrame, metadata: dict) -> DataFrame: def populate_indicators_btc_1h_2(self, dataframe: DataFrame, metadata: dict) -> DataFrame:
dataframe['rsi_upper'] = ta.RSI(dataframe, timeperiod=14) dataframe['rsi_upper'] = ta.RSI(dataframe, timeperiod=14)
return dataframe 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 !!! 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 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). manually as described [in the DataProvider section](#complete-data-provider-sample).

View File

@ -6,7 +6,7 @@ import logging
import warnings import warnings
from abc import ABC, abstractmethod from abc import ABC, abstractmethod
from datetime import datetime, timedelta, timezone 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 import arrow
from pandas import DataFrame 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.exchange.exchange import timeframe_to_next_date
from freqtrade.persistence import PairLocks, Trade from freqtrade.persistence import PairLocks, Trade
from freqtrade.strategy.hyper import HyperStrategyMixin 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) _format_pair_name)
from freqtrade.strategy.strategy_wrapper import strategy_safe_wrapper from freqtrade.strategy.strategy_wrapper import strategy_safe_wrapper
from freqtrade.wallets import Wallets from freqtrade.wallets import Wallets
@ -138,20 +139,23 @@ class IStrategy(ABC, HyperStrategyMixin):
# Gather informative pairs from @informative-decorated methods. # Gather informative pairs from @informative-decorated methods.
self._ft_informative: Dict[ self._ft_informative: Dict[
Tuple[str, str], Tuple[InformativeData, Tuple[str, str], Tuple[InformativeData, PopulateIndicators]] = {}
Callable[[Any, DataFrame, dict], DataFrame]]] = {}
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', []) ft_informative = getattr(cls_method, '_ft_informative', None)
if not isinstance(ft_informative, list): if not isinstance(ft_informative, 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)
for informative_data in ft_informative: for informative_data in ft_informative:
asset = informative_data.asset asset = informative_data.asset
timeframe = informative_data.timeframe 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: if asset:
pair = _format_pair_name(self.config, asset) pair = _format_pair_name(self.config, asset)
if (pair, timeframe) in self._ft_informative: if (pair, timeframe) in self._ft_informative:
@ -165,10 +169,6 @@ class IStrategy(ABC, HyperStrategyMixin):
f'not be defined more than once!') f'not be defined more than once!')
self._ft_informative[(pair, timeframe)] = (informative_data, cls_method) 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 @abstractmethod
def populate_indicators(self, dataframe: DataFrame, metadata: dict) -> DataFrame: 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 from freqtrade.exchange import timeframe_to_minutes
PopulateIndicators = Callable[[Any, DataFrame, dict], DataFrame]
class InformativeData(NamedTuple): class InformativeData(NamedTuple):
asset: Optional[str] asset: Optional[str]
timeframe: str timeframe: str
@ -118,8 +121,7 @@ def stoploss_from_absolute(stop_rate: float, current_rate: float) -> float:
def informative(timeframe: str, asset: str = '', def informative(timeframe: str, asset: str = '',
fmt: Optional[Union[str, Callable[[KwArg(str)], str]]] = None, fmt: Optional[Union[str, Callable[[KwArg(str)], str]]] = None,
ffill: bool = True) -> Callable[[Callable[[Any, DataFrame, dict], DataFrame]], ffill: bool = True) -> Callable[[PopulateIndicators], PopulateIndicators]:
Callable[[Any, DataFrame, dict], DataFrame]]:
""" """
A decorator for populate_indicators_Nn(self, dataframe, metadata), allowing these functions to A decorator for populate_indicators_Nn(self, dataframe, metadata), allowing these functions to
define informative indicators. define informative indicators.
@ -131,24 +133,32 @@ def informative(timeframe: str, asset: str = '',
dataframe['rsi'] = ta.RSI(dataframe, timeperiod=14) dataframe['rsi'] = ta.RSI(dataframe, timeperiod=14)
return dataframe 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 :param asset: Informative asset, for example BTC, BTC/USDT, ETH/BTC. Do not specify to use
current pair. current pair.
:param fmt: Column format (str) or column formatter (callable(name, asset, timeframe)). When not :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} specified, defaults to:
otherwise. * {base}_{column}_{timeframe} if asset is specified and quote currency does match stake
* {asset}: name of informative asset, provided in lower-case, with / replaced with _. Stake curerncy.
currency is not included in this string. * {base}_{quote}_{column}_{timeframe} if asset is specified and quote currency does not match
* {name}: user-specified dataframe column name. stake curerncy.
* {timeframe}: informative timeframe. * {column}_{timeframe} if asset is not specified.
:param ffill: ffill dataframe after mering informative pair. 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 _asset = asset
_timeframe = timeframe _timeframe = timeframe
_fmt = fmt _fmt = fmt
_ffill = ffill _ffill = ffill
def decorator(fn: Callable[[Any, DataFrame, dict], DataFrame]): def decorator(fn: PopulateIndicators):
informative_pairs = getattr(fn, '_ft_informative', []) informative_pairs = getattr(fn, '_ft_informative', [])
informative_pairs.append(InformativeData(_asset, _timeframe, _fmt, _ffill)) informative_pairs.append(InformativeData(_asset, _timeframe, _fmt, _ffill))
setattr(fn, '_ft_informative', informative_pairs) setattr(fn, '_ft_informative', informative_pairs)