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
- Minimal ROI recommended
- Stoploss recommended
- Hyperopt parameter
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`
@ -61,17 +60,16 @@ file as reference.**
### Buy strategy
Edit the method `populate_buy_trend()` into your strategy file to
update your buy strategy.
Edit the method `populate_buy_trend()` into your strategy file to update your buy strategy.
Sample from `user_data/strategies/test_strategy.py`:
```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
: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
"""
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`:
```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
: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
"""
dataframe.loc[
@ -110,7 +108,7 @@ def populate_sell_trend(self, dataframe: DataFrame, pair: str) -> 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.
@ -119,9 +117,16 @@ You should only add the indicators used in either `populate_buy_trend()`, `popul
Sample:
```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
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['adx'] = ta.ADX(dataframe)
@ -152,6 +157,11 @@ def populate_indicators(self, dataframe: DataFrame, pair: str) -> 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
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
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
ticker_data.loc[:, 'buy'] = ticker_data['buy'].shift(1)

View File

@ -75,7 +75,7 @@ class Hyperopt(Backtesting):
return arg_dict
@staticmethod
def populate_indicators(dataframe: DataFrame, pair: str) -> DataFrame:
def populate_indicators(dataframe: DataFrame, metadata: dict) -> DataFrame:
dataframe['adx'] = ta.ADX(dataframe)
macd = ta.MACD(dataframe)
dataframe['macd'] = macd['macd']
@ -228,7 +228,7 @@ class Hyperopt(Backtesting):
"""
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
"""

View File

@ -28,7 +28,7 @@ class DefaultStrategy(IStrategy):
# Optimal ticker interval for the strategy
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
@ -36,7 +36,7 @@ class DefaultStrategy(IStrategy):
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 pair: Pair currently analyzed
:param metadata: Additional information, like the currently traded pair
:return: a Dataframe with all mandatory indicators for the strategies
"""
@ -199,11 +199,11 @@ class DefaultStrategy(IStrategy):
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
:param dataframe: DataFrame
:param pair: Pair currently analyzed
:param metadata: Additional information, like the currently traded pair
:return: DataFrame with buy column
"""
dataframe.loc[
@ -221,11 +221,11 @@ class DefaultStrategy(IStrategy):
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
:param dataframe: DataFrame
:param pair: Pair currently analyzed
:param metadata: Additional information, like the currently traded pair
:return: DataFrame with buy column
"""
dataframe.loc[

View File

@ -74,29 +74,29 @@ class IStrategy(ABC):
self.config = config
@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
: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
"""
@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
:param dataframe: DataFrame
:param pair: Pair currently analyzed
:param metadata: Additional information, like the currently traded pair
:return: DataFrame with buy column
"""
@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
:param dataframe: DataFrame
:param pair: Pair currently analyzed
:param metadata: Additional information, like the currently traded pair
:return: DataFrame with sell column
"""
@ -106,16 +106,16 @@ class IStrategy(ABC):
"""
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
add several TA indicators and buy signal to it
:return DataFrame with ticker data and indicator data
"""
dataframe = parse_ticker_dataframe(ticker_history)
dataframe = self.advise_indicators(dataframe, pair)
dataframe = self.advise_buy(dataframe, pair)
dataframe = self.advise_sell(dataframe, pair)
dataframe = self.advise_indicators(dataframe, metadata)
dataframe = self.advise_buy(dataframe, metadata)
dataframe = self.advise_sell(dataframe, metadata)
return dataframe
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
try:
dataframe = self.analyze_ticker(ticker_hist, pair)
dataframe = self.analyze_ticker(ticker_hist, {'pair': pair})
except ValueError as error:
logger.warning(
'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
"""
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()}
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
This method should not be overridden.
: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
"""
if self._populate_fun_len == 2:
@ -291,14 +291,14 @@ class IStrategy(ABC):
"the current function headers!", DeprecationWarning)
return self.populate_indicators(dataframe) # type: ignore
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
This method should not be overridden.
:param dataframe: DataFrame
:param pair: The currently traded pair
:param pair: Additional information, like the currently traded pair
:return: DataFrame with buy column
"""
if self._buy_fun_len == 2:
@ -306,14 +306,14 @@ class IStrategy(ABC):
"the current function headers!", DeprecationWarning)
return self.populate_buy_trend(dataframe) # type: ignore
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
This method should not be overridden.
:param dataframe: DataFrame
:param pair: The currently traded pair
:param pair: Additional information, like the currently traded pair
:return: DataFrame with sell column
"""
if self._sell_fun_len == 2:
@ -321,4 +321,4 @@ class IStrategy(ABC):
"the current function headers!", DeprecationWarning)
return self.populate_sell_trend(dataframe) # type: ignore
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):
resolver = StrategyResolver({'strategy': 'TestStrategy'})
pair = 'ETH/BTC'
assert len(resolver.strategy.populate_indicators.__annotations__) == 3
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)
assert 'adx' in resolver.strategy.advise_indicators(result, metadata=pair)
def test_load_strategy_invalid_directory(result, caplog):
@ -92,7 +89,7 @@ def test_strategy(result):
config = {'strategy': 'DefaultStrategy'}
resolver = StrategyResolver(config)
pair = 'ETH/BTC'
metadata = {'pair': 'ETH/BTC'}
assert resolver.strategy.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 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
dataframe = resolver.strategy.advise_buy(df_indicators, pair=pair)
dataframe = resolver.strategy.advise_buy(df_indicators, metadata=metadata)
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
@ -196,21 +193,21 @@ def test_call_deprecated_function(result, monkeypatch):
default_location = path.join(path.dirname(path.realpath(__file__)))
resolver = StrategyResolver({'strategy': 'TestStrategyLegacy',
'strategy_path': default_location})
pair = 'ETH/BTC'
metadata = {'pair': 'ETH/BTC'}
# Make sure we are using a legacy function
assert resolver.strategy._populate_fun_len == 2
assert resolver.strategy._buy_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 '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 '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 'sell' in selldf

View File

@ -45,7 +45,7 @@ class TestStrategy(IStrategy):
# Optimal ticker interval for the strategy
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
@ -53,7 +53,7 @@ class TestStrategy(IStrategy):
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 pair: Pair currently analyzed
:param metadata: Additional information, like the currently traded pair
:return: a Dataframe with all mandatory indicators for the strategies
"""
@ -215,11 +215,11 @@ class TestStrategy(IStrategy):
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
: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
"""
dataframe.loc[
@ -232,11 +232,11 @@ class TestStrategy(IStrategy):
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
: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
"""
dataframe.loc[