From 92130837a980cb9caeccd91c8fbc2d6dfe67da9b Mon Sep 17 00:00:00 2001 From: Matthias Date: Wed, 27 Oct 2021 19:58:29 +0200 Subject: [PATCH] Improve and clarify informative pairs documentation --- docs/strategy-customization.md | 254 +++++++++++++++++---------------- 1 file changed, 128 insertions(+), 126 deletions(-) diff --git a/docs/strategy-customization.md b/docs/strategy-customization.md index 0bfc0a2f6..d0c9e5f4f 100644 --- a/docs/strategy-customization.md +++ b/docs/strategy-customization.md @@ -312,7 +312,7 @@ Currently this is `pair`, which can be accessed using `metadata['pair']` - and w The Metadata-dict should not be modified and does not persist information across multiple calls. Instead, have a look at the section [Storing information](strategy-advanced.md#Storing-information) -## Additional data (informative_pairs) +## Informative Pairs ### Get data for non-tradeable pairs @@ -341,6 +341,133 @@ A full sample can be found [in the DataProvider section](#complete-data-provider *** +### Informative pairs decorator (`@informative()`) + +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, 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. + +??? info "Full documentation" + ``` 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}_{quote}_{column}_{timeframe} if asset is specified. + * {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. + """ + ``` + +??? Example "Fast and easy way to define informative pairs" + + Most of the time we do not need power and flexibility offered by `merge_informative_pair()`, therefore we can use a decorator to quickly define informative pairs. + + ``` python + + from datetime import datetime + from freqtrade.persistence import Trade + from freqtrade.strategy import IStrategy, informative + + class AwesomeStrategy(IStrategy): + + # This method is not required. + # def informative_pairs(self): ... + + # Define informative upper timeframe for each pair. Decorators can be stacked on same + # method. Available in populate_indicators as 'rsi_30m' and 'rsi_1h'. + @informative('30m') + @informative('1h') + def populate_indicators_1h(self, dataframe: DataFrame, metadata: dict) -> DataFrame: + dataframe['rsi'] = ta.RSI(dataframe, timeperiod=14) + return dataframe + + # Define BTC/STAKE informative pair. Available in populate_indicators and other methods as + # 'btc_rsi_1h'. Current stake currency should be specified as {stake} format variable + # instead of hardcoding actual stake currency. Available in populate_indicators and other + # methods as 'btc_usdt_rsi_1h' (when stake currency is USDT). + @informative('1h', 'BTC/{stake}') + def populate_indicators_btc_1h(self, dataframe: DataFrame, metadata: dict) -> DataFrame: + dataframe['rsi'] = ta.RSI(dataframe, timeperiod=14) + return dataframe + + # Define BTC/ETH informative pair. You must specify quote currency if it is different from + # stake currency. Available in populate_indicators and other methods as 'eth_btc_rsi_1h'. + @informative('1h', 'ETH/BTC') + def populate_indicators_eth_btc_1h(self, dataframe: DataFrame, metadata: dict) -> DataFrame: + dataframe['rsi'] = ta.RSI(dataframe, timeperiod=14) + return dataframe + + # Define BTC/STAKE informative pair. A custom formatter may be specified for formatting + # 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 + + def populate_indicators(self, dataframe: DataFrame, metadata: dict) -> DataFrame: + # Strategy timeframe indicators for current pair. + dataframe['rsi'] = ta.RSI(dataframe, timeperiod=14) + # Informative pairs are available in this method. + dataframe['rsi_less'] = dataframe['rsi'] < dataframe['rsi_1h'] + return dataframe + + ``` + +!!! 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). + +!!! Note + Use string formatting when accessing informative dataframes of other pairs. This will allow easily changing stake currency in config without having to adjust strategy code. + + ``` python + def populate_buy_trend(self, dataframe: DataFrame, metadata: dict) -> DataFrame: + stake = self.config['stake_currency'] + dataframe.loc[ + ( + (dataframe[f'btc_{stake}_rsi_1h'] < 35) + & + (dataframe['volume'] > 0) + ), + ['buy', 'buy_tag']] = (1, 'buy_signal_rsi') + + return dataframe + ``` + + Alternatively column renaming may be used to remove stake currency from column names: `@informative('1h', 'BTC/{stake}', fmt='{base}_{column}_{timeframe}')`. + +!!! Warning "Duplicate method names" + Methods tagged with `@informative()` decorator must always have unique names! Re-using same name (for example when copy-pasting already defined informative method) + will overwrite previously defined method and not produce any errors due to limitations of Python programming language. In such cases you will find that indicators + created in earlier-defined methods are not available in the dataframe. Carefully review method names and make sure they are unique! + + ## Additional data (DataProvider) The strategy provides access to the `DataProvider`. This allows you to get additional data to use in your strategy. @@ -686,131 +813,6 @@ 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}_{quote}_{column}_{timeframe} if asset is specified. - * {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, 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" - - Most of the time we do not need power and flexibility offered by `merge_informative_pair()`, therefore we can use a decorator to quickly define informative pairs. - - ``` python - - from datetime import datetime - from freqtrade.persistence import Trade - from freqtrade.strategy import IStrategy, informative - - class AwesomeStrategy(IStrategy): - - # This method is not required. - # def informative_pairs(self): ... - - # Define informative upper timeframe for each pair. Decorators can be stacked on same - # method. Available in populate_indicators as 'rsi_30m' and 'rsi_1h'. - @informative('30m') - @informative('1h') - def populate_indicators_1h(self, dataframe: DataFrame, metadata: dict) -> DataFrame: - dataframe['rsi'] = ta.RSI(dataframe, timeperiod=14) - return dataframe - - # Define BTC/STAKE informative pair. Available in populate_indicators and other methods as - # 'btc_rsi_1h'. Current stake currency should be specified as {stake} format variable - # instead of hardcoding actual stake currency. Available in populate_indicators and other - # methods as 'btc_usdt_rsi_1h' (when stake currency is USDT). - @informative('1h', 'BTC/{stake}') - def populate_indicators_btc_1h(self, dataframe: DataFrame, metadata: dict) -> DataFrame: - dataframe['rsi'] = ta.RSI(dataframe, timeperiod=14) - return dataframe - - # Define BTC/ETH informative pair. You must specify quote currency if it is different from - # stake currency. Available in populate_indicators and other methods as 'eth_btc_rsi_1h'. - @informative('1h', 'ETH/BTC') - def populate_indicators_eth_btc_1h(self, dataframe: DataFrame, metadata: dict) -> DataFrame: - dataframe['rsi'] = ta.RSI(dataframe, timeperiod=14) - return dataframe - - # Define BTC/STAKE informative pair. A custom formatter may be specified for formatting - # 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 - - def populate_indicators(self, dataframe: DataFrame, metadata: dict) -> DataFrame: - # Strategy timeframe indicators for current pair. - dataframe['rsi'] = ta.RSI(dataframe, timeperiod=14) - # Informative pairs are available in this method. - dataframe['rsi_less'] = dataframe['rsi'] < dataframe['rsi_1h'] - return dataframe - - ``` - -!!! 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). - -!!! Note - Use string formatting when accessing informative dataframes of other pairs. This will allow easily changing stake currency in config without having to adjust strategy code. - - ``` python - def populate_buy_trend(self, dataframe: DataFrame, metadata: dict) -> DataFrame: - stake = self.config['stake_currency'] - dataframe.loc[ - ( - (dataframe[f'btc_{stake}_rsi_1h'] < 35) - & - (dataframe['volume'] > 0) - ), - ['buy', 'buy_tag']] = (1, 'buy_signal_rsi') - - return dataframe - ``` - - Alternatively column renaming may be used to remove stake currency from column names: `@informative('1h', 'BTC/{stake}', fmt='{base}_{column}_{timeframe}')`. - -!!! Warning "Duplicate method names" - Methods tagged with `@informative()` decorator must always have unique names! Re-using same name (for example when copy-pasting already defined informative method) - will overwrite previously defined method and not produce any errors due to limitations of Python programming language. In such cases you will find that indicators - created in earlier-defined methods are not available in the dataframe. Carefully review method names and make sure they are unique! - ## Additional data (Wallets) The strategy provides access to the `Wallets` object. This contains the current balances on the exchange.