Switch from pair(str) to metadata(dict)

This commit is contained in:
Matthias 2018-07-29 20:36:03 +02:00
parent 941879dc19
commit 787d6042de
7 changed files with 64 additions and 57 deletions

View File

@ -39,7 +39,6 @@ A strategy file contains all the information needed to build a good strategy:
- Sell strategy rules - Sell strategy rules
- Minimal ROI recommended - Minimal ROI recommended
- Stoploss recommended - Stoploss recommended
- Hyperopt parameter
The bot also include a sample strategy called `TestStrategy` you can update: `user_data/strategies/test_strategy.py`. The bot also include a sample strategy called `TestStrategy` you can update: `user_data/strategies/test_strategy.py`.
You can test it with the parameter: `--strategy TestStrategy` You can test it with the parameter: `--strategy TestStrategy`
@ -61,17 +60,16 @@ file as reference.**
### Buy strategy ### Buy strategy
Edit the method `populate_buy_trend()` into your strategy file to Edit the method `populate_buy_trend()` into your strategy file to update your buy strategy.
update your buy strategy.
Sample from `user_data/strategies/test_strategy.py`: Sample from `user_data/strategies/test_strategy.py`:
```python ```python
def populate_buy_trend(self, dataframe: DataFrame, pair: str) -> DataFrame: def populate_buy_trend(self, dataframe: DataFrame, metadata: dict) -> DataFrame:
""" """
Based on TA indicators, populates the buy signal for the given dataframe Based on TA indicators, populates the buy signal for the given dataframe
:param dataframe: DataFrame populated with indicators :param dataframe: DataFrame populated with indicators
:param pair: Pair currently analyzed :param metadata: Additional information, like the currently traded pair
:return: DataFrame with buy column :return: DataFrame with buy column
""" """
dataframe.loc[ dataframe.loc[
@ -93,11 +91,11 @@ Please note that the sell-signal is only used if `use_sell_signal` is set to tru
Sample from `user_data/strategies/test_strategy.py`: Sample from `user_data/strategies/test_strategy.py`:
```python ```python
def populate_sell_trend(self, dataframe: DataFrame, pair: str) -> DataFrame: def populate_sell_trend(self, dataframe: DataFrame, metadata: dict) -> DataFrame:
""" """
Based on TA indicators, populates the sell signal for the given dataframe Based on TA indicators, populates the sell signal for the given dataframe
:param dataframe: DataFrame populated with indicators :param dataframe: DataFrame populated with indicators
:param pair: Pair currently analyzed :param metadata: Additional information, like the currently traded pair
:return: DataFrame with buy column :return: DataFrame with buy column
""" """
dataframe.loc[ dataframe.loc[
@ -110,7 +108,7 @@ def populate_sell_trend(self, dataframe: DataFrame, pair: str) -> DataFrame:
return dataframe return dataframe
``` ```
## Add more Indicator ## Add more Indicators
As you have seen, buy and sell strategies need indicators. You can add more indicators by extending the list contained in the method `populate_indicators()` from your strategy file. As you have seen, buy and sell strategies need indicators. You can add more indicators by extending the list contained in the method `populate_indicators()` from your strategy file.
@ -119,9 +117,16 @@ You should only add the indicators used in either `populate_buy_trend()`, `popul
Sample: Sample:
```python ```python
def populate_indicators(self, dataframe: DataFrame, pair: str) -> DataFrame: def populate_indicators(self, dataframe: DataFrame, metadata: dict) -> DataFrame:
""" """
Adds several different TA indicators to the given DataFrame Adds several different TA indicators to the given DataFrame
Performance Note: For the best performance be frugal on the number of indicators
you are using. Let uncomment only the indicator you are using in your strategies
or your hyperopt configuration, otherwise you will waste your memory and CPU usage.
:param dataframe: Raw data from the exchange and parsed by parse_ticker_dataframe()
:param metadata: Additional information, like the currently traded pair
:return: a Dataframe with all mandatory indicators for the strategies
""" """
dataframe['sar'] = ta.SAR(dataframe) dataframe['sar'] = ta.SAR(dataframe)
dataframe['adx'] = ta.ADX(dataframe) dataframe['adx'] = ta.ADX(dataframe)
@ -152,6 +157,11 @@ def populate_indicators(self, dataframe: DataFrame, pair: str) -> DataFrame:
return dataframe return dataframe
``` ```
### Metadata dict
The metadata-dict (available for `populate_buy_trend`, `populate_sell_trend`, `populate_indicators`) contains additional information.
Currently this is `pair`, which can be accessed using `metadata['pair']` - and will return a pair in the format `XRP/BTC`.
### Want more indicator examples ### Want more indicator examples
Look into the [user_data/strategies/test_strategy.py](https://github.com/freqtrade/freqtrade/blob/develop/user_data/strategies/test_strategy.py). Look into the [user_data/strategies/test_strategy.py](https://github.com/freqtrade/freqtrade/blob/develop/user_data/strategies/test_strategy.py).

View File

@ -230,7 +230,7 @@ class Backtesting(object):
pair_data['buy'], pair_data['sell'] = 0, 0 # cleanup from previous run pair_data['buy'], pair_data['sell'] = 0, 0 # cleanup from previous run
ticker_data = self.advise_sell( ticker_data = self.advise_sell(
self.advise_buy(pair_data, pair), pair)[headers].copy() self.advise_buy(pair_data, {'pair': pair}), {'pair': pair})[headers].copy()
# to avoid using data from future, we buy/sell with signal from previous candle # to avoid using data from future, we buy/sell with signal from previous candle
ticker_data.loc[:, 'buy'] = ticker_data['buy'].shift(1) ticker_data.loc[:, 'buy'] = ticker_data['buy'].shift(1)

View File

@ -75,7 +75,7 @@ class Hyperopt(Backtesting):
return arg_dict return arg_dict
@staticmethod @staticmethod
def populate_indicators(dataframe: DataFrame, pair: str) -> DataFrame: def populate_indicators(dataframe: DataFrame, metadata: dict) -> DataFrame:
dataframe['adx'] = ta.ADX(dataframe) dataframe['adx'] = ta.ADX(dataframe)
macd = ta.MACD(dataframe) macd = ta.MACD(dataframe)
dataframe['macd'] = macd['macd'] dataframe['macd'] = macd['macd']
@ -228,7 +228,7 @@ class Hyperopt(Backtesting):
""" """
Define the buy strategy parameters to be used by hyperopt Define the buy strategy parameters to be used by hyperopt
""" """
def populate_buy_trend(dataframe: DataFrame, pair: str) -> DataFrame: def populate_buy_trend(dataframe: DataFrame, metadata: dict) -> DataFrame:
""" """
Buy strategy Hyperopt will build and use Buy strategy Hyperopt will build and use
""" """

View File

@ -28,7 +28,7 @@ class DefaultStrategy(IStrategy):
# Optimal ticker interval for the strategy # Optimal ticker interval for the strategy
ticker_interval = '5m' ticker_interval = '5m'
def populate_indicators(self, dataframe: DataFrame, pair: str) -> DataFrame: def populate_indicators(self, dataframe: DataFrame, metadata: dict) -> DataFrame:
""" """
Adds several different TA indicators to the given DataFrame Adds several different TA indicators to the given DataFrame
@ -36,7 +36,7 @@ class DefaultStrategy(IStrategy):
you are using. Let uncomment only the indicator you are using in your strategies you are using. Let uncomment only the indicator you are using in your strategies
or your hyperopt configuration, otherwise you will waste your memory and CPU usage. or your hyperopt configuration, otherwise you will waste your memory and CPU usage.
:param dataframe: Raw data from the exchange and parsed by parse_ticker_dataframe() :param dataframe: Raw data from the exchange and parsed by parse_ticker_dataframe()
:param pair: Pair currently analyzed :param metadata: Additional information, like the currently traded pair
:return: a Dataframe with all mandatory indicators for the strategies :return: a Dataframe with all mandatory indicators for the strategies
""" """
@ -199,11 +199,11 @@ class DefaultStrategy(IStrategy):
return dataframe return dataframe
def populate_buy_trend(self, dataframe: DataFrame, pair: str) -> DataFrame: def populate_buy_trend(self, dataframe: DataFrame, metadata: dict) -> DataFrame:
""" """
Based on TA indicators, populates the buy signal for the given dataframe Based on TA indicators, populates the buy signal for the given dataframe
:param dataframe: DataFrame :param dataframe: DataFrame
:param pair: Pair currently analyzed :param metadata: Additional information, like the currently traded pair
:return: DataFrame with buy column :return: DataFrame with buy column
""" """
dataframe.loc[ dataframe.loc[
@ -221,11 +221,11 @@ class DefaultStrategy(IStrategy):
return dataframe return dataframe
def populate_sell_trend(self, dataframe: DataFrame, pair: str) -> DataFrame: def populate_sell_trend(self, dataframe: DataFrame, metadata: dict) -> DataFrame:
""" """
Based on TA indicators, populates the sell signal for the given dataframe Based on TA indicators, populates the sell signal for the given dataframe
:param dataframe: DataFrame :param dataframe: DataFrame
:param pair: Pair currently analyzed :param metadata: Additional information, like the currently traded pair
:return: DataFrame with buy column :return: DataFrame with buy column
""" """
dataframe.loc[ dataframe.loc[

View File

@ -74,29 +74,29 @@ class IStrategy(ABC):
self.config = config self.config = config
@abstractmethod @abstractmethod
def populate_indicators(self, dataframe: DataFrame, pair: str) -> DataFrame: def populate_indicators(self, dataframe: DataFrame, metadata: dict) -> DataFrame:
""" """
Populate indicators that will be used in the Buy and Sell strategy Populate indicators that will be used in the Buy and Sell strategy
:param dataframe: Raw data from the exchange and parsed by parse_ticker_dataframe() :param dataframe: Raw data from the exchange and parsed by parse_ticker_dataframe()
:param pair: Pair currently analyzed :param metadata: Additional information, like the currently traded pair
:return: a Dataframe with all mandatory indicators for the strategies :return: a Dataframe with all mandatory indicators for the strategies
""" """
@abstractmethod @abstractmethod
def populate_buy_trend(self, dataframe: DataFrame, pair: str) -> DataFrame: def populate_buy_trend(self, dataframe: DataFrame, metadata: dict) -> DataFrame:
""" """
Based on TA indicators, populates the buy signal for the given dataframe Based on TA indicators, populates the buy signal for the given dataframe
:param dataframe: DataFrame :param dataframe: DataFrame
:param pair: Pair currently analyzed :param metadata: Additional information, like the currently traded pair
:return: DataFrame with buy column :return: DataFrame with buy column
""" """
@abstractmethod @abstractmethod
def populate_sell_trend(self, dataframe: DataFrame, pair: str) -> DataFrame: def populate_sell_trend(self, dataframe: DataFrame, metadata: dict) -> DataFrame:
""" """
Based on TA indicators, populates the sell signal for the given dataframe Based on TA indicators, populates the sell signal for the given dataframe
:param dataframe: DataFrame :param dataframe: DataFrame
:param pair: Pair currently analyzed :param metadata: Additional information, like the currently traded pair
:return: DataFrame with sell column :return: DataFrame with sell column
""" """
@ -106,16 +106,16 @@ class IStrategy(ABC):
""" """
return self.__class__.__name__ return self.__class__.__name__
def analyze_ticker(self, ticker_history: List[Dict], pair: str) -> DataFrame: def analyze_ticker(self, ticker_history: List[Dict], metadata: dict) -> DataFrame:
""" """
Parses the given ticker history and returns a populated DataFrame Parses the given ticker history and returns a populated DataFrame
add several TA indicators and buy signal to it add several TA indicators and buy signal to it
:return DataFrame with ticker data and indicator data :return DataFrame with ticker data and indicator data
""" """
dataframe = parse_ticker_dataframe(ticker_history) dataframe = parse_ticker_dataframe(ticker_history)
dataframe = self.advise_indicators(dataframe, pair) dataframe = self.advise_indicators(dataframe, metadata)
dataframe = self.advise_buy(dataframe, pair) dataframe = self.advise_buy(dataframe, metadata)
dataframe = self.advise_sell(dataframe, pair) dataframe = self.advise_sell(dataframe, metadata)
return dataframe return dataframe
def get_signal(self, pair: str, interval: str, ticker_hist: List[Dict]) -> Tuple[bool, bool]: def get_signal(self, pair: str, interval: str, ticker_hist: List[Dict]) -> Tuple[bool, bool]:
@ -130,7 +130,7 @@ class IStrategy(ABC):
return False, False return False, False
try: try:
dataframe = self.analyze_ticker(ticker_hist, pair) dataframe = self.analyze_ticker(ticker_hist, {'pair': pair})
except ValueError as error: except ValueError as error:
logger.warning( logger.warning(
'Unable to analyze ticker for pair %s: %s', 'Unable to analyze ticker for pair %s: %s',
@ -275,15 +275,15 @@ class IStrategy(ABC):
""" """
Creates a dataframe and populates indicators for given ticker data Creates a dataframe and populates indicators for given ticker data
""" """
return {pair: self.advise_indicators(parse_ticker_dataframe(pair_data), pair) return {pair: self.advise_indicators(parse_ticker_dataframe(pair_data), {'pair': pair})
for pair, pair_data in tickerdata.items()} for pair, pair_data in tickerdata.items()}
def advise_indicators(self, dataframe: DataFrame, pair: str) -> DataFrame: def advise_indicators(self, dataframe: DataFrame, metadata: dict) -> DataFrame:
""" """
Populate indicators that will be used in the Buy and Sell strategy Populate indicators that will be used in the Buy and Sell strategy
This method should not be overridden. This method should not be overridden.
:param dataframe: Raw data from the exchange and parsed by parse_ticker_dataframe() :param dataframe: Raw data from the exchange and parsed by parse_ticker_dataframe()
:param pair: The currently traded pair :param metadata: Additional information, like the currently traded pair
:return: a Dataframe with all mandatory indicators for the strategies :return: a Dataframe with all mandatory indicators for the strategies
""" """
if self._populate_fun_len == 2: if self._populate_fun_len == 2:
@ -291,14 +291,14 @@ class IStrategy(ABC):
"the current function headers!", DeprecationWarning) "the current function headers!", DeprecationWarning)
return self.populate_indicators(dataframe) # type: ignore return self.populate_indicators(dataframe) # type: ignore
else: else:
return self.populate_indicators(dataframe, pair) return self.populate_indicators(dataframe, metadata)
def advise_buy(self, dataframe: DataFrame, pair: str) -> DataFrame: def advise_buy(self, dataframe: DataFrame, metadata: dict) -> DataFrame:
""" """
Based on TA indicators, populates the buy signal for the given dataframe Based on TA indicators, populates the buy signal for the given dataframe
This method should not be overridden. This method should not be overridden.
:param dataframe: DataFrame :param dataframe: DataFrame
:param pair: The currently traded pair :param pair: Additional information, like the currently traded pair
:return: DataFrame with buy column :return: DataFrame with buy column
""" """
if self._buy_fun_len == 2: if self._buy_fun_len == 2:
@ -306,14 +306,14 @@ class IStrategy(ABC):
"the current function headers!", DeprecationWarning) "the current function headers!", DeprecationWarning)
return self.populate_buy_trend(dataframe) # type: ignore return self.populate_buy_trend(dataframe) # type: ignore
else: else:
return self.populate_buy_trend(dataframe, pair) return self.populate_buy_trend(dataframe, metadata)
def advise_sell(self, dataframe: DataFrame, pair: str) -> DataFrame: def advise_sell(self, dataframe: DataFrame, metadata: dict) -> DataFrame:
""" """
Based on TA indicators, populates the sell signal for the given dataframe Based on TA indicators, populates the sell signal for the given dataframe
This method should not be overridden. This method should not be overridden.
:param dataframe: DataFrame :param dataframe: DataFrame
:param pair: The currently traded pair :param pair: Additional information, like the currently traded pair
:return: DataFrame with sell column :return: DataFrame with sell column
""" """
if self._sell_fun_len == 2: if self._sell_fun_len == 2:
@ -321,4 +321,4 @@ class IStrategy(ABC):
"the current function headers!", DeprecationWarning) "the current function headers!", DeprecationWarning)
return self.populate_sell_trend(dataframe) # type: ignore return self.populate_sell_trend(dataframe) # type: ignore
else: else:
return self.populate_sell_trend(dataframe, pair) return self.populate_sell_trend(dataframe, metadata)

View File

@ -60,10 +60,7 @@ def test_search_strategy():
def test_load_strategy(result): def test_load_strategy(result):
resolver = StrategyResolver({'strategy': 'TestStrategy'}) resolver = StrategyResolver({'strategy': 'TestStrategy'})
pair = 'ETH/BTC' pair = 'ETH/BTC'
assert len(resolver.strategy.populate_indicators.__annotations__) == 3 assert 'adx' in resolver.strategy.advise_indicators(result, metadata=pair)
assert 'dataframe' in resolver.strategy.populate_indicators.__annotations__
assert 'pair' in resolver.strategy.populate_indicators.__annotations__
assert 'adx' in resolver.strategy.advise_indicators(result, pair=pair)
def test_load_strategy_invalid_directory(result, caplog): def test_load_strategy_invalid_directory(result, caplog):
@ -92,7 +89,7 @@ def test_strategy(result):
config = {'strategy': 'DefaultStrategy'} config = {'strategy': 'DefaultStrategy'}
resolver = StrategyResolver(config) resolver = StrategyResolver(config)
pair = 'ETH/BTC' metadata = {'pair': 'ETH/BTC'}
assert resolver.strategy.minimal_roi[0] == 0.04 assert resolver.strategy.minimal_roi[0] == 0.04
assert config["minimal_roi"]['0'] == 0.04 assert config["minimal_roi"]['0'] == 0.04
@ -102,13 +99,13 @@ def test_strategy(result):
assert resolver.strategy.ticker_interval == '5m' assert resolver.strategy.ticker_interval == '5m'
assert config['ticker_interval'] == '5m' assert config['ticker_interval'] == '5m'
df_indicators = resolver.strategy.advise_indicators(result, pair=pair) df_indicators = resolver.strategy.advise_indicators(result, metadata=metadata)
assert 'adx' in df_indicators assert 'adx' in df_indicators
dataframe = resolver.strategy.advise_buy(df_indicators, pair=pair) dataframe = resolver.strategy.advise_buy(df_indicators, metadata=metadata)
assert 'buy' in dataframe.columns assert 'buy' in dataframe.columns
dataframe = resolver.strategy.advise_sell(df_indicators, pair='ETH/BTC') dataframe = resolver.strategy.advise_sell(df_indicators, metadata=metadata)
assert 'sell' in dataframe.columns assert 'sell' in dataframe.columns
@ -196,21 +193,21 @@ def test_call_deprecated_function(result, monkeypatch):
default_location = path.join(path.dirname(path.realpath(__file__))) default_location = path.join(path.dirname(path.realpath(__file__)))
resolver = StrategyResolver({'strategy': 'TestStrategyLegacy', resolver = StrategyResolver({'strategy': 'TestStrategyLegacy',
'strategy_path': default_location}) 'strategy_path': default_location})
pair = 'ETH/BTC' metadata = {'pair': 'ETH/BTC'}
# Make sure we are using a legacy function # Make sure we are using a legacy function
assert resolver.strategy._populate_fun_len == 2 assert resolver.strategy._populate_fun_len == 2
assert resolver.strategy._buy_fun_len == 2 assert resolver.strategy._buy_fun_len == 2
assert resolver.strategy._sell_fun_len == 2 assert resolver.strategy._sell_fun_len == 2
indicator_df = resolver.strategy.advise_indicators(result, pair=pair) indicator_df = resolver.strategy.advise_indicators(result, metadata=metadata)
assert type(indicator_df) is DataFrame assert type(indicator_df) is DataFrame
assert 'adx' in indicator_df.columns assert 'adx' in indicator_df.columns
buydf = resolver.strategy.advise_buy(result, pair=pair) buydf = resolver.strategy.advise_buy(result, metadata=metadata)
assert type(buydf) is DataFrame assert type(buydf) is DataFrame
assert 'buy' in buydf.columns assert 'buy' in buydf.columns
selldf = resolver.strategy.advise_sell(result, pair=pair) selldf = resolver.strategy.advise_sell(result, metadata=metadata)
assert type(selldf) is DataFrame assert type(selldf) is DataFrame
assert 'sell' in selldf assert 'sell' in selldf

View File

@ -45,7 +45,7 @@ class TestStrategy(IStrategy):
# Optimal ticker interval for the strategy # Optimal ticker interval for the strategy
ticker_interval = '5m' ticker_interval = '5m'
def populate_indicators(self, dataframe: DataFrame, pair: str) -> DataFrame: def populate_indicators(self, dataframe: DataFrame, metadata: dict) -> DataFrame:
""" """
Adds several different TA indicators to the given DataFrame Adds several different TA indicators to the given DataFrame
@ -53,7 +53,7 @@ class TestStrategy(IStrategy):
you are using. Let uncomment only the indicator you are using in your strategies you are using. Let uncomment only the indicator you are using in your strategies
or your hyperopt configuration, otherwise you will waste your memory and CPU usage. or your hyperopt configuration, otherwise you will waste your memory and CPU usage.
:param dataframe: Raw data from the exchange and parsed by parse_ticker_dataframe() :param dataframe: Raw data from the exchange and parsed by parse_ticker_dataframe()
:param pair: Pair currently analyzed :param metadata: Additional information, like the currently traded pair
:return: a Dataframe with all mandatory indicators for the strategies :return: a Dataframe with all mandatory indicators for the strategies
""" """
@ -215,11 +215,11 @@ class TestStrategy(IStrategy):
return dataframe return dataframe
def populate_buy_trend(self, dataframe: DataFrame, pair: str) -> DataFrame: def populate_buy_trend(self, dataframe: DataFrame, metadata: dict) -> DataFrame:
""" """
Based on TA indicators, populates the buy signal for the given dataframe Based on TA indicators, populates the buy signal for the given dataframe
:param dataframe: DataFrame populated with indicators :param dataframe: DataFrame populated with indicators
:param pair: Pair currently analyzed :param metadata: Additional information, like the currently traded pair
:return: DataFrame with buy column :return: DataFrame with buy column
""" """
dataframe.loc[ dataframe.loc[
@ -232,11 +232,11 @@ class TestStrategy(IStrategy):
return dataframe return dataframe
def populate_sell_trend(self, dataframe: DataFrame, pair: str) -> DataFrame: def populate_sell_trend(self, dataframe: DataFrame, metadata: dict) -> DataFrame:
""" """
Based on TA indicators, populates the sell signal for the given dataframe Based on TA indicators, populates the sell signal for the given dataframe
:param dataframe: DataFrame populated with indicators :param dataframe: DataFrame populated with indicators
:param pair: Pair currently analyzed :param metadata: Additional information, like the currently traded pair
:return: DataFrame with buy column :return: DataFrame with buy column
""" """
dataframe.loc[ dataframe.loc[