Merge pull request #1044 from freqtrade/pair_to_strat
pair to strategy enhancement
This commit is contained in:
commit
81cf7229be
@ -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,22 +60,22 @@ 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) -> 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 populated with indicators
|
||||||
|
:param metadata: Additional information, like the currently traded pair
|
||||||
:return: DataFrame with buy column
|
:return: DataFrame with buy column
|
||||||
"""
|
"""
|
||||||
dataframe.loc[
|
dataframe.loc[
|
||||||
(
|
(
|
||||||
(dataframe['adx'] > 30) &
|
(dataframe['adx'] > 30) &
|
||||||
(dataframe['tema'] <= dataframe['blower']) &
|
(dataframe['tema'] <= dataframe['bb_middleband']) &
|
||||||
(dataframe['tema'] > dataframe['tema'].shift(1))
|
(dataframe['tema'] > dataframe['tema'].shift(1))
|
||||||
),
|
),
|
||||||
'buy'] = 1
|
'buy'] = 1
|
||||||
@ -87,38 +86,47 @@ def populate_buy_trend(self, dataframe: DataFrame) -> DataFrame:
|
|||||||
### Sell strategy
|
### Sell strategy
|
||||||
|
|
||||||
Edit the method `populate_sell_trend()` into your strategy file to update your sell strategy.
|
Edit the method `populate_sell_trend()` into your strategy file to update your sell strategy.
|
||||||
|
Please note that the sell-signal is only used if `use_sell_signal` is set to true in the configuration.
|
||||||
|
|
||||||
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) -> 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 populated with indicators
|
||||||
|
:param metadata: Additional information, like the currently traded pair
|
||||||
:return: DataFrame with buy column
|
:return: DataFrame with buy column
|
||||||
"""
|
"""
|
||||||
dataframe.loc[
|
dataframe.loc[
|
||||||
(
|
(
|
||||||
(dataframe['adx'] > 70) &
|
(dataframe['adx'] > 70) &
|
||||||
(dataframe['tema'] > dataframe['blower']) &
|
(dataframe['tema'] > dataframe['bb_middleband']) &
|
||||||
(dataframe['tema'] < dataframe['tema'].shift(1))
|
(dataframe['tema'] < dataframe['tema'].shift(1))
|
||||||
),
|
),
|
||||||
'sell'] = 1
|
'sell'] = 1
|
||||||
return dataframe
|
return dataframe
|
||||||
```
|
```
|
||||||
|
|
||||||
## Add more Indicator
|
## Add more Indicators
|
||||||
|
|
||||||
As you have seen, buy and sell strategies need indicators. You can add
|
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.
|
||||||
more indicators by extending the list contained in
|
|
||||||
the method `populate_indicators()` from your strategy file.
|
You should only add the indicators used in either `populate_buy_trend()`, `populate_sell_trend()`, or to populate another indicator, otherwise performance may suffer.
|
||||||
|
|
||||||
Sample:
|
Sample:
|
||||||
|
|
||||||
```python
|
```python
|
||||||
def populate_indicators(dataframe: DataFrame) -> 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)
|
||||||
@ -149,6 +157,11 @@ def populate_indicators(dataframe: DataFrame) -> 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).
|
||||||
|
@ -57,8 +57,8 @@ class Backtesting(object):
|
|||||||
self.strategy: IStrategy = StrategyResolver(self.config).strategy
|
self.strategy: IStrategy = StrategyResolver(self.config).strategy
|
||||||
self.ticker_interval = self.strategy.ticker_interval
|
self.ticker_interval = self.strategy.ticker_interval
|
||||||
self.tickerdata_to_dataframe = self.strategy.tickerdata_to_dataframe
|
self.tickerdata_to_dataframe = self.strategy.tickerdata_to_dataframe
|
||||||
self.populate_buy_trend = self.strategy.populate_buy_trend
|
self.advise_buy = self.strategy.advise_buy
|
||||||
self.populate_sell_trend = self.strategy.populate_sell_trend
|
self.advise_sell = self.strategy.advise_sell
|
||||||
|
|
||||||
# Reset keys for backtesting
|
# Reset keys for backtesting
|
||||||
self.config['exchange']['key'] = ''
|
self.config['exchange']['key'] = ''
|
||||||
@ -229,8 +229,8 @@ class Backtesting(object):
|
|||||||
for pair, pair_data in processed.items():
|
for pair, pair_data in processed.items():
|
||||||
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.populate_sell_trend(
|
ticker_data = self.advise_sell(
|
||||||
self.populate_buy_trend(pair_data))[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)
|
||||||
|
@ -75,7 +75,7 @@ class Hyperopt(Backtesting):
|
|||||||
return arg_dict
|
return arg_dict
|
||||||
|
|
||||||
@staticmethod
|
@staticmethod
|
||||||
def populate_indicators(dataframe: DataFrame) -> 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) -> DataFrame:
|
def populate_buy_trend(dataframe: DataFrame, metadata: dict) -> DataFrame:
|
||||||
"""
|
"""
|
||||||
Buy strategy Hyperopt will build and use
|
Buy strategy Hyperopt will build and use
|
||||||
"""
|
"""
|
||||||
@ -270,7 +270,7 @@ class Hyperopt(Backtesting):
|
|||||||
self.strategy.minimal_roi = self.generate_roi_table(params)
|
self.strategy.minimal_roi = self.generate_roi_table(params)
|
||||||
|
|
||||||
if self.has_space('buy'):
|
if self.has_space('buy'):
|
||||||
self.populate_buy_trend = self.buy_strategy_generator(params)
|
self.advise_buy = self.buy_strategy_generator(params)
|
||||||
|
|
||||||
if self.has_space('stoploss'):
|
if self.has_space('stoploss'):
|
||||||
self.strategy.stoploss = params['stoploss']
|
self.strategy.stoploss = params['stoploss']
|
||||||
@ -351,7 +351,7 @@ class Hyperopt(Backtesting):
|
|||||||
)
|
)
|
||||||
|
|
||||||
if self.has_space('buy'):
|
if self.has_space('buy'):
|
||||||
self.strategy.populate_indicators = Hyperopt.populate_indicators # type: ignore
|
self.strategy.advise_indicators = Hyperopt.populate_indicators # type: ignore
|
||||||
dump(self.tickerdata_to_dataframe(data), TICKERDATA_PICKLE)
|
dump(self.tickerdata_to_dataframe(data), TICKERDATA_PICKLE)
|
||||||
self.exchange = None # type: ignore
|
self.exchange = None # type: ignore
|
||||||
self.load_previous_results()
|
self.load_previous_results()
|
||||||
|
@ -28,13 +28,16 @@ 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) -> 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
|
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
|
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 metadata: Additional information, like the currently traded pair
|
||||||
|
:return: a Dataframe with all mandatory indicators for the strategies
|
||||||
"""
|
"""
|
||||||
|
|
||||||
# Momentum Indicator
|
# Momentum Indicator
|
||||||
@ -196,10 +199,11 @@ class DefaultStrategy(IStrategy):
|
|||||||
|
|
||||||
return dataframe
|
return dataframe
|
||||||
|
|
||||||
def populate_buy_trend(self, dataframe: DataFrame) -> 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 metadata: Additional information, like the currently traded pair
|
||||||
:return: DataFrame with buy column
|
:return: DataFrame with buy column
|
||||||
"""
|
"""
|
||||||
dataframe.loc[
|
dataframe.loc[
|
||||||
@ -217,10 +221,11 @@ class DefaultStrategy(IStrategy):
|
|||||||
|
|
||||||
return dataframe
|
return dataframe
|
||||||
|
|
||||||
def populate_sell_trend(self, dataframe: DataFrame) -> 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 metadata: Additional information, like the currently traded pair
|
||||||
:return: DataFrame with buy column
|
:return: DataFrame with buy column
|
||||||
"""
|
"""
|
||||||
dataframe.loc[
|
dataframe.loc[
|
||||||
|
@ -7,6 +7,7 @@ from abc import ABC, abstractmethod
|
|||||||
from datetime import datetime
|
from datetime import datetime
|
||||||
from enum import Enum
|
from enum import Enum
|
||||||
from typing import Dict, List, NamedTuple, Tuple
|
from typing import Dict, List, NamedTuple, Tuple
|
||||||
|
import warnings
|
||||||
|
|
||||||
import arrow
|
import arrow
|
||||||
from pandas import DataFrame
|
from pandas import DataFrame
|
||||||
@ -57,34 +58,45 @@ class IStrategy(ABC):
|
|||||||
ticker_interval -> str: value of the ticker interval to use for the strategy
|
ticker_interval -> str: value of the ticker interval to use for the strategy
|
||||||
"""
|
"""
|
||||||
|
|
||||||
|
_populate_fun_len: int = 0
|
||||||
|
_buy_fun_len: int = 0
|
||||||
|
_sell_fun_len: int = 0
|
||||||
|
# associated minimal roi
|
||||||
minimal_roi: Dict
|
minimal_roi: Dict
|
||||||
|
|
||||||
|
# associated stoploss
|
||||||
stoploss: float
|
stoploss: float
|
||||||
|
|
||||||
|
# associated ticker interval
|
||||||
ticker_interval: str
|
ticker_interval: str
|
||||||
|
|
||||||
def __init__(self, config: dict) -> None:
|
def __init__(self, config: dict) -> None:
|
||||||
self.config = config
|
self.config = config
|
||||||
|
|
||||||
@abstractmethod
|
@abstractmethod
|
||||||
def populate_indicators(self, dataframe: DataFrame) -> 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 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) -> 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 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) -> 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 metadata: Additional information, like the currently traded pair
|
||||||
:return: DataFrame with sell column
|
:return: DataFrame with sell column
|
||||||
"""
|
"""
|
||||||
|
|
||||||
@ -94,16 +106,16 @@ class IStrategy(ABC):
|
|||||||
"""
|
"""
|
||||||
return self.__class__.__name__
|
return self.__class__.__name__
|
||||||
|
|
||||||
def analyze_ticker(self, ticker_history: List[Dict]) -> 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.populate_indicators(dataframe)
|
dataframe = self.advise_indicators(dataframe, metadata)
|
||||||
dataframe = self.populate_buy_trend(dataframe)
|
dataframe = self.advise_buy(dataframe, metadata)
|
||||||
dataframe = self.populate_sell_trend(dataframe)
|
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]:
|
||||||
@ -118,7 +130,7 @@ class IStrategy(ABC):
|
|||||||
return False, False
|
return False, False
|
||||||
|
|
||||||
try:
|
try:
|
||||||
dataframe = self.analyze_ticker(ticker_hist)
|
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',
|
||||||
@ -263,5 +275,50 @@ 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.populate_indicators(parse_ticker_dataframe(pair_data))
|
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, 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 metadata: Additional information, like the currently traded pair
|
||||||
|
:return: a Dataframe with all mandatory indicators for the strategies
|
||||||
|
"""
|
||||||
|
if self._populate_fun_len == 2:
|
||||||
|
warnings.warn("deprecated - check out the Sample strategy to see "
|
||||||
|
"the current function headers!", DeprecationWarning)
|
||||||
|
return self.populate_indicators(dataframe) # type: ignore
|
||||||
|
else:
|
||||||
|
return self.populate_indicators(dataframe, metadata)
|
||||||
|
|
||||||
|
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: Additional information, like the currently traded pair
|
||||||
|
:return: DataFrame with buy column
|
||||||
|
"""
|
||||||
|
if self._buy_fun_len == 2:
|
||||||
|
warnings.warn("deprecated - check out the Sample strategy to see "
|
||||||
|
"the current function headers!", DeprecationWarning)
|
||||||
|
return self.populate_buy_trend(dataframe) # type: ignore
|
||||||
|
else:
|
||||||
|
return self.populate_buy_trend(dataframe, metadata)
|
||||||
|
|
||||||
|
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: Additional information, like the currently traded pair
|
||||||
|
:return: DataFrame with sell column
|
||||||
|
"""
|
||||||
|
if self._sell_fun_len == 2:
|
||||||
|
warnings.warn("deprecated - check out the Sample strategy to see "
|
||||||
|
"the current function headers!", DeprecationWarning)
|
||||||
|
return self.populate_sell_trend(dataframe) # type: ignore
|
||||||
|
else:
|
||||||
|
return self.populate_sell_trend(dataframe, metadata)
|
||||||
|
@ -92,6 +92,13 @@ class StrategyResolver(object):
|
|||||||
strategy = self._search_strategy(path, strategy_name=strategy_name, config=config)
|
strategy = self._search_strategy(path, strategy_name=strategy_name, config=config)
|
||||||
if strategy:
|
if strategy:
|
||||||
logger.info('Using resolved strategy %s from \'%s\'', strategy_name, path)
|
logger.info('Using resolved strategy %s from \'%s\'', strategy_name, path)
|
||||||
|
strategy._populate_fun_len = len(
|
||||||
|
inspect.getfullargspec(strategy.populate_indicators).args)
|
||||||
|
strategy._buy_fun_len = len(
|
||||||
|
inspect.getfullargspec(strategy.populate_buy_trend).args)
|
||||||
|
strategy._sell_fun_len = len(
|
||||||
|
inspect.getfullargspec(strategy.populate_sell_trend).args)
|
||||||
|
|
||||||
return import_strategy(strategy, config=config)
|
return import_strategy(strategy, config=config)
|
||||||
except FileNotFoundError:
|
except FileNotFoundError:
|
||||||
logger.warning('Path "%s" does not exist', path)
|
logger.warning('Path "%s" does not exist', path)
|
||||||
|
@ -146,7 +146,7 @@ def _trend(signals, buy_value, sell_value):
|
|||||||
return signals
|
return signals
|
||||||
|
|
||||||
|
|
||||||
def _trend_alternate(dataframe=None):
|
def _trend_alternate(dataframe=None, metadata=None):
|
||||||
signals = dataframe
|
signals = dataframe
|
||||||
low = signals['low']
|
low = signals['low']
|
||||||
n = len(low)
|
n = len(low)
|
||||||
@ -332,8 +332,8 @@ def test_backtesting_init(mocker, default_conf) -> None:
|
|||||||
assert backtesting.config == default_conf
|
assert backtesting.config == default_conf
|
||||||
assert backtesting.ticker_interval == '5m'
|
assert backtesting.ticker_interval == '5m'
|
||||||
assert callable(backtesting.tickerdata_to_dataframe)
|
assert callable(backtesting.tickerdata_to_dataframe)
|
||||||
assert callable(backtesting.populate_buy_trend)
|
assert callable(backtesting.advise_buy)
|
||||||
assert callable(backtesting.populate_sell_trend)
|
assert callable(backtesting.advise_sell)
|
||||||
get_fee.assert_called()
|
get_fee.assert_called()
|
||||||
assert backtesting.fee == 0.5
|
assert backtesting.fee == 0.5
|
||||||
|
|
||||||
@ -611,42 +611,42 @@ def test_backtest_ticks(default_conf, fee, mocker):
|
|||||||
mocker.patch('freqtrade.exchange.Exchange.get_fee', fee)
|
mocker.patch('freqtrade.exchange.Exchange.get_fee', fee)
|
||||||
patch_exchange(mocker)
|
patch_exchange(mocker)
|
||||||
ticks = [1, 5]
|
ticks = [1, 5]
|
||||||
fun = Backtesting(default_conf).populate_buy_trend
|
fun = Backtesting(default_conf).advise_buy
|
||||||
for _ in ticks:
|
for _ in ticks:
|
||||||
backtest_conf = _make_backtest_conf(mocker, conf=default_conf)
|
backtest_conf = _make_backtest_conf(mocker, conf=default_conf)
|
||||||
backtesting = Backtesting(default_conf)
|
backtesting = Backtesting(default_conf)
|
||||||
backtesting.populate_buy_trend = fun # Override
|
backtesting.advise_buy = fun # Override
|
||||||
backtesting.populate_sell_trend = fun # Override
|
backtesting.advise_sell = fun # Override
|
||||||
results = backtesting.backtest(backtest_conf)
|
results = backtesting.backtest(backtest_conf)
|
||||||
assert not results.empty
|
assert not results.empty
|
||||||
|
|
||||||
|
|
||||||
def test_backtest_clash_buy_sell(mocker, default_conf):
|
def test_backtest_clash_buy_sell(mocker, default_conf):
|
||||||
# Override the default buy trend function in our default_strategy
|
# Override the default buy trend function in our default_strategy
|
||||||
def fun(dataframe=None):
|
def fun(dataframe=None, pair=None):
|
||||||
buy_value = 1
|
buy_value = 1
|
||||||
sell_value = 1
|
sell_value = 1
|
||||||
return _trend(dataframe, buy_value, sell_value)
|
return _trend(dataframe, buy_value, sell_value)
|
||||||
|
|
||||||
backtest_conf = _make_backtest_conf(mocker, conf=default_conf)
|
backtest_conf = _make_backtest_conf(mocker, conf=default_conf)
|
||||||
backtesting = Backtesting(default_conf)
|
backtesting = Backtesting(default_conf)
|
||||||
backtesting.populate_buy_trend = fun # Override
|
backtesting.advise_buy = fun # Override
|
||||||
backtesting.populate_sell_trend = fun # Override
|
backtesting.advise_sell = fun # Override
|
||||||
results = backtesting.backtest(backtest_conf)
|
results = backtesting.backtest(backtest_conf)
|
||||||
assert results.empty
|
assert results.empty
|
||||||
|
|
||||||
|
|
||||||
def test_backtest_only_sell(mocker, default_conf):
|
def test_backtest_only_sell(mocker, default_conf):
|
||||||
# Override the default buy trend function in our default_strategy
|
# Override the default buy trend function in our default_strategy
|
||||||
def fun(dataframe=None):
|
def fun(dataframe=None, pair=None):
|
||||||
buy_value = 0
|
buy_value = 0
|
||||||
sell_value = 1
|
sell_value = 1
|
||||||
return _trend(dataframe, buy_value, sell_value)
|
return _trend(dataframe, buy_value, sell_value)
|
||||||
|
|
||||||
backtest_conf = _make_backtest_conf(mocker, conf=default_conf)
|
backtest_conf = _make_backtest_conf(mocker, conf=default_conf)
|
||||||
backtesting = Backtesting(default_conf)
|
backtesting = Backtesting(default_conf)
|
||||||
backtesting.populate_buy_trend = fun # Override
|
backtesting.advise_buy = fun # Override
|
||||||
backtesting.populate_sell_trend = fun # Override
|
backtesting.advise_sell = fun # Override
|
||||||
results = backtesting.backtest(backtest_conf)
|
results = backtesting.backtest(backtest_conf)
|
||||||
assert results.empty
|
assert results.empty
|
||||||
|
|
||||||
@ -655,8 +655,8 @@ def test_backtest_alternate_buy_sell(default_conf, fee, mocker):
|
|||||||
mocker.patch('freqtrade.exchange.Exchange.get_fee', fee)
|
mocker.patch('freqtrade.exchange.Exchange.get_fee', fee)
|
||||||
backtest_conf = _make_backtest_conf(mocker, conf=default_conf, pair='UNITTEST/BTC')
|
backtest_conf = _make_backtest_conf(mocker, conf=default_conf, pair='UNITTEST/BTC')
|
||||||
backtesting = Backtesting(default_conf)
|
backtesting = Backtesting(default_conf)
|
||||||
backtesting.populate_buy_trend = _trend_alternate # Override
|
backtesting.advise_buy = _trend_alternate # Override
|
||||||
backtesting.populate_sell_trend = _trend_alternate # Override
|
backtesting.advise_sell = _trend_alternate # Override
|
||||||
results = backtesting.backtest(backtest_conf)
|
results = backtesting.backtest(backtest_conf)
|
||||||
backtesting._store_backtest_result("test_.json", results)
|
backtesting._store_backtest_result("test_.json", results)
|
||||||
assert len(results) == 4
|
assert len(results) == 4
|
||||||
|
@ -247,7 +247,7 @@ def test_populate_indicators(init_hyperopt) -> None:
|
|||||||
tick = load_tickerdata_file(None, 'UNITTEST/BTC', '1m')
|
tick = load_tickerdata_file(None, 'UNITTEST/BTC', '1m')
|
||||||
tickerlist = {'UNITTEST/BTC': tick}
|
tickerlist = {'UNITTEST/BTC': tick}
|
||||||
dataframes = _HYPEROPT.tickerdata_to_dataframe(tickerlist)
|
dataframes = _HYPEROPT.tickerdata_to_dataframe(tickerlist)
|
||||||
dataframe = _HYPEROPT.populate_indicators(dataframes['UNITTEST/BTC'])
|
dataframe = _HYPEROPT.populate_indicators(dataframes['UNITTEST/BTC'], {'pair': 'UNITTEST/BTC'})
|
||||||
|
|
||||||
# Check if some indicators are generated. We will not test all of them
|
# Check if some indicators are generated. We will not test all of them
|
||||||
assert 'adx' in dataframe
|
assert 'adx' in dataframe
|
||||||
@ -259,7 +259,7 @@ def test_buy_strategy_generator(init_hyperopt) -> None:
|
|||||||
tick = load_tickerdata_file(None, 'UNITTEST/BTC', '1m')
|
tick = load_tickerdata_file(None, 'UNITTEST/BTC', '1m')
|
||||||
tickerlist = {'UNITTEST/BTC': tick}
|
tickerlist = {'UNITTEST/BTC': tick}
|
||||||
dataframes = _HYPEROPT.tickerdata_to_dataframe(tickerlist)
|
dataframes = _HYPEROPT.tickerdata_to_dataframe(tickerlist)
|
||||||
dataframe = _HYPEROPT.populate_indicators(dataframes['UNITTEST/BTC'])
|
dataframe = _HYPEROPT.populate_indicators(dataframes['UNITTEST/BTC'], {'pair': 'UNITTEST/BTC'})
|
||||||
|
|
||||||
populate_buy_trend = _HYPEROPT.buy_strategy_generator(
|
populate_buy_trend = _HYPEROPT.buy_strategy_generator(
|
||||||
{
|
{
|
||||||
@ -274,7 +274,7 @@ def test_buy_strategy_generator(init_hyperopt) -> None:
|
|||||||
'trigger': 'bb_lower'
|
'trigger': 'bb_lower'
|
||||||
}
|
}
|
||||||
)
|
)
|
||||||
result = populate_buy_trend(dataframe)
|
result = populate_buy_trend(dataframe, {'pair': 'UNITTEST/BTC'})
|
||||||
# Check if some indicators are generated. We will not test all of them
|
# Check if some indicators are generated. We will not test all of them
|
||||||
assert 'buy' in result
|
assert 'buy' in result
|
||||||
assert 1 in result['buy']
|
assert 1 in result['buy']
|
||||||
|
235
freqtrade/tests/strategy/legacy_strategy.py
Normal file
235
freqtrade/tests/strategy/legacy_strategy.py
Normal file
@ -0,0 +1,235 @@
|
|||||||
|
|
||||||
|
# --- Do not remove these libs ---
|
||||||
|
from freqtrade.strategy.interface import IStrategy
|
||||||
|
from pandas import DataFrame
|
||||||
|
# --------------------------------
|
||||||
|
|
||||||
|
# Add your lib to import here
|
||||||
|
import talib.abstract as ta
|
||||||
|
import freqtrade.vendor.qtpylib.indicators as qtpylib
|
||||||
|
import numpy # noqa
|
||||||
|
|
||||||
|
|
||||||
|
# This class is a sample. Feel free to customize it.
|
||||||
|
class TestStrategyLegacy(IStrategy):
|
||||||
|
"""
|
||||||
|
This is a test strategy using the legacy function headers, which will be
|
||||||
|
removed in a future update.
|
||||||
|
Please do not use this as a template, but refer to user_data/strategy/TestStrategy.py
|
||||||
|
for a uptodate version of this template.
|
||||||
|
|
||||||
|
"""
|
||||||
|
|
||||||
|
# Minimal ROI designed for the strategy.
|
||||||
|
# This attribute will be overridden if the config file contains "minimal_roi"
|
||||||
|
minimal_roi = {
|
||||||
|
"40": 0.0,
|
||||||
|
"30": 0.01,
|
||||||
|
"20": 0.02,
|
||||||
|
"0": 0.04
|
||||||
|
}
|
||||||
|
|
||||||
|
# Optimal stoploss designed for the strategy
|
||||||
|
# This attribute will be overridden if the config file contains "stoploss"
|
||||||
|
stoploss = -0.10
|
||||||
|
|
||||||
|
# Optimal ticker interval for the strategy
|
||||||
|
ticker_interval = '5m'
|
||||||
|
|
||||||
|
def populate_indicators(self, dataframe: DataFrame) -> 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.
|
||||||
|
"""
|
||||||
|
|
||||||
|
# Momentum Indicator
|
||||||
|
# ------------------------------------
|
||||||
|
|
||||||
|
# ADX
|
||||||
|
dataframe['adx'] = ta.ADX(dataframe)
|
||||||
|
|
||||||
|
"""
|
||||||
|
# Awesome oscillator
|
||||||
|
dataframe['ao'] = qtpylib.awesome_oscillator(dataframe)
|
||||||
|
|
||||||
|
# Commodity Channel Index: values Oversold:<-100, Overbought:>100
|
||||||
|
dataframe['cci'] = ta.CCI(dataframe)
|
||||||
|
|
||||||
|
# MACD
|
||||||
|
macd = ta.MACD(dataframe)
|
||||||
|
dataframe['macd'] = macd['macd']
|
||||||
|
dataframe['macdsignal'] = macd['macdsignal']
|
||||||
|
dataframe['macdhist'] = macd['macdhist']
|
||||||
|
|
||||||
|
# MFI
|
||||||
|
dataframe['mfi'] = ta.MFI(dataframe)
|
||||||
|
|
||||||
|
# Minus Directional Indicator / Movement
|
||||||
|
dataframe['minus_dm'] = ta.MINUS_DM(dataframe)
|
||||||
|
dataframe['minus_di'] = ta.MINUS_DI(dataframe)
|
||||||
|
|
||||||
|
# Plus Directional Indicator / Movement
|
||||||
|
dataframe['plus_dm'] = ta.PLUS_DM(dataframe)
|
||||||
|
dataframe['plus_di'] = ta.PLUS_DI(dataframe)
|
||||||
|
dataframe['minus_di'] = ta.MINUS_DI(dataframe)
|
||||||
|
|
||||||
|
# ROC
|
||||||
|
dataframe['roc'] = ta.ROC(dataframe)
|
||||||
|
|
||||||
|
# RSI
|
||||||
|
dataframe['rsi'] = ta.RSI(dataframe)
|
||||||
|
|
||||||
|
# Inverse Fisher transform on RSI, values [-1.0, 1.0] (https://goo.gl/2JGGoy)
|
||||||
|
rsi = 0.1 * (dataframe['rsi'] - 50)
|
||||||
|
dataframe['fisher_rsi'] = (numpy.exp(2 * rsi) - 1) / (numpy.exp(2 * rsi) + 1)
|
||||||
|
|
||||||
|
# Inverse Fisher transform on RSI normalized, value [0.0, 100.0] (https://goo.gl/2JGGoy)
|
||||||
|
dataframe['fisher_rsi_norma'] = 50 * (dataframe['fisher_rsi'] + 1)
|
||||||
|
|
||||||
|
# Stoch
|
||||||
|
stoch = ta.STOCH(dataframe)
|
||||||
|
dataframe['slowd'] = stoch['slowd']
|
||||||
|
dataframe['slowk'] = stoch['slowk']
|
||||||
|
|
||||||
|
# Stoch fast
|
||||||
|
stoch_fast = ta.STOCHF(dataframe)
|
||||||
|
dataframe['fastd'] = stoch_fast['fastd']
|
||||||
|
dataframe['fastk'] = stoch_fast['fastk']
|
||||||
|
|
||||||
|
# Stoch RSI
|
||||||
|
stoch_rsi = ta.STOCHRSI(dataframe)
|
||||||
|
dataframe['fastd_rsi'] = stoch_rsi['fastd']
|
||||||
|
dataframe['fastk_rsi'] = stoch_rsi['fastk']
|
||||||
|
"""
|
||||||
|
|
||||||
|
# Overlap Studies
|
||||||
|
# ------------------------------------
|
||||||
|
|
||||||
|
# Bollinger bands
|
||||||
|
bollinger = qtpylib.bollinger_bands(qtpylib.typical_price(dataframe), window=20, stds=2)
|
||||||
|
dataframe['bb_lowerband'] = bollinger['lower']
|
||||||
|
dataframe['bb_middleband'] = bollinger['mid']
|
||||||
|
dataframe['bb_upperband'] = bollinger['upper']
|
||||||
|
|
||||||
|
"""
|
||||||
|
# EMA - Exponential Moving Average
|
||||||
|
dataframe['ema3'] = ta.EMA(dataframe, timeperiod=3)
|
||||||
|
dataframe['ema5'] = ta.EMA(dataframe, timeperiod=5)
|
||||||
|
dataframe['ema10'] = ta.EMA(dataframe, timeperiod=10)
|
||||||
|
dataframe['ema50'] = ta.EMA(dataframe, timeperiod=50)
|
||||||
|
dataframe['ema100'] = ta.EMA(dataframe, timeperiod=100)
|
||||||
|
|
||||||
|
# SAR Parabol
|
||||||
|
dataframe['sar'] = ta.SAR(dataframe)
|
||||||
|
|
||||||
|
# SMA - Simple Moving Average
|
||||||
|
dataframe['sma'] = ta.SMA(dataframe, timeperiod=40)
|
||||||
|
"""
|
||||||
|
|
||||||
|
# TEMA - Triple Exponential Moving Average
|
||||||
|
dataframe['tema'] = ta.TEMA(dataframe, timeperiod=9)
|
||||||
|
|
||||||
|
# Cycle Indicator
|
||||||
|
# ------------------------------------
|
||||||
|
# Hilbert Transform Indicator - SineWave
|
||||||
|
hilbert = ta.HT_SINE(dataframe)
|
||||||
|
dataframe['htsine'] = hilbert['sine']
|
||||||
|
dataframe['htleadsine'] = hilbert['leadsine']
|
||||||
|
|
||||||
|
# Pattern Recognition - Bullish candlestick patterns
|
||||||
|
# ------------------------------------
|
||||||
|
"""
|
||||||
|
# Hammer: values [0, 100]
|
||||||
|
dataframe['CDLHAMMER'] = ta.CDLHAMMER(dataframe)
|
||||||
|
# Inverted Hammer: values [0, 100]
|
||||||
|
dataframe['CDLINVERTEDHAMMER'] = ta.CDLINVERTEDHAMMER(dataframe)
|
||||||
|
# Dragonfly Doji: values [0, 100]
|
||||||
|
dataframe['CDLDRAGONFLYDOJI'] = ta.CDLDRAGONFLYDOJI(dataframe)
|
||||||
|
# Piercing Line: values [0, 100]
|
||||||
|
dataframe['CDLPIERCING'] = ta.CDLPIERCING(dataframe) # values [0, 100]
|
||||||
|
# Morningstar: values [0, 100]
|
||||||
|
dataframe['CDLMORNINGSTAR'] = ta.CDLMORNINGSTAR(dataframe) # values [0, 100]
|
||||||
|
# Three White Soldiers: values [0, 100]
|
||||||
|
dataframe['CDL3WHITESOLDIERS'] = ta.CDL3WHITESOLDIERS(dataframe) # values [0, 100]
|
||||||
|
"""
|
||||||
|
|
||||||
|
# Pattern Recognition - Bearish candlestick patterns
|
||||||
|
# ------------------------------------
|
||||||
|
"""
|
||||||
|
# Hanging Man: values [0, 100]
|
||||||
|
dataframe['CDLHANGINGMAN'] = ta.CDLHANGINGMAN(dataframe)
|
||||||
|
# Shooting Star: values [0, 100]
|
||||||
|
dataframe['CDLSHOOTINGSTAR'] = ta.CDLSHOOTINGSTAR(dataframe)
|
||||||
|
# Gravestone Doji: values [0, 100]
|
||||||
|
dataframe['CDLGRAVESTONEDOJI'] = ta.CDLGRAVESTONEDOJI(dataframe)
|
||||||
|
# Dark Cloud Cover: values [0, 100]
|
||||||
|
dataframe['CDLDARKCLOUDCOVER'] = ta.CDLDARKCLOUDCOVER(dataframe)
|
||||||
|
# Evening Doji Star: values [0, 100]
|
||||||
|
dataframe['CDLEVENINGDOJISTAR'] = ta.CDLEVENINGDOJISTAR(dataframe)
|
||||||
|
# Evening Star: values [0, 100]
|
||||||
|
dataframe['CDLEVENINGSTAR'] = ta.CDLEVENINGSTAR(dataframe)
|
||||||
|
"""
|
||||||
|
|
||||||
|
# Pattern Recognition - Bullish/Bearish candlestick patterns
|
||||||
|
# ------------------------------------
|
||||||
|
"""
|
||||||
|
# Three Line Strike: values [0, -100, 100]
|
||||||
|
dataframe['CDL3LINESTRIKE'] = ta.CDL3LINESTRIKE(dataframe)
|
||||||
|
# Spinning Top: values [0, -100, 100]
|
||||||
|
dataframe['CDLSPINNINGTOP'] = ta.CDLSPINNINGTOP(dataframe) # values [0, -100, 100]
|
||||||
|
# Engulfing: values [0, -100, 100]
|
||||||
|
dataframe['CDLENGULFING'] = ta.CDLENGULFING(dataframe) # values [0, -100, 100]
|
||||||
|
# Harami: values [0, -100, 100]
|
||||||
|
dataframe['CDLHARAMI'] = ta.CDLHARAMI(dataframe) # values [0, -100, 100]
|
||||||
|
# Three Outside Up/Down: values [0, -100, 100]
|
||||||
|
dataframe['CDL3OUTSIDE'] = ta.CDL3OUTSIDE(dataframe) # values [0, -100, 100]
|
||||||
|
# Three Inside Up/Down: values [0, -100, 100]
|
||||||
|
dataframe['CDL3INSIDE'] = ta.CDL3INSIDE(dataframe) # values [0, -100, 100]
|
||||||
|
"""
|
||||||
|
|
||||||
|
# Chart type
|
||||||
|
# ------------------------------------
|
||||||
|
"""
|
||||||
|
# Heikinashi stategy
|
||||||
|
heikinashi = qtpylib.heikinashi(dataframe)
|
||||||
|
dataframe['ha_open'] = heikinashi['open']
|
||||||
|
dataframe['ha_close'] = heikinashi['close']
|
||||||
|
dataframe['ha_high'] = heikinashi['high']
|
||||||
|
dataframe['ha_low'] = heikinashi['low']
|
||||||
|
"""
|
||||||
|
|
||||||
|
return dataframe
|
||||||
|
|
||||||
|
def populate_buy_trend(self, dataframe: DataFrame) -> DataFrame:
|
||||||
|
"""
|
||||||
|
Based on TA indicators, populates the buy signal for the given dataframe
|
||||||
|
:param dataframe: DataFrame
|
||||||
|
:return: DataFrame with buy column
|
||||||
|
"""
|
||||||
|
dataframe.loc[
|
||||||
|
(
|
||||||
|
(dataframe['adx'] > 30) &
|
||||||
|
(dataframe['tema'] <= dataframe['bb_middleband']) &
|
||||||
|
(dataframe['tema'] > dataframe['tema'].shift(1))
|
||||||
|
),
|
||||||
|
'buy'] = 1
|
||||||
|
|
||||||
|
return dataframe
|
||||||
|
|
||||||
|
def populate_sell_trend(self, dataframe: DataFrame) -> DataFrame:
|
||||||
|
"""
|
||||||
|
Based on TA indicators, populates the sell signal for the given dataframe
|
||||||
|
:param dataframe: DataFrame
|
||||||
|
:return: DataFrame with buy column
|
||||||
|
"""
|
||||||
|
dataframe.loc[
|
||||||
|
(
|
||||||
|
(dataframe['adx'] > 70) &
|
||||||
|
(dataframe['tema'] > dataframe['bb_middleband']) &
|
||||||
|
(dataframe['tema'] < dataframe['tema'].shift(1))
|
||||||
|
),
|
||||||
|
'sell'] = 1
|
||||||
|
return dataframe
|
@ -25,10 +25,11 @@ def test_default_strategy_structure():
|
|||||||
def test_default_strategy(result):
|
def test_default_strategy(result):
|
||||||
strategy = DefaultStrategy({})
|
strategy = DefaultStrategy({})
|
||||||
|
|
||||||
|
metadata = {'pair': 'ETH/BTC'}
|
||||||
assert type(strategy.minimal_roi) is dict
|
assert type(strategy.minimal_roi) is dict
|
||||||
assert type(strategy.stoploss) is float
|
assert type(strategy.stoploss) is float
|
||||||
assert type(strategy.ticker_interval) is str
|
assert type(strategy.ticker_interval) is str
|
||||||
indicators = strategy.populate_indicators(result)
|
indicators = strategy.populate_indicators(result, metadata)
|
||||||
assert type(indicators) is DataFrame
|
assert type(indicators) is DataFrame
|
||||||
assert type(strategy.populate_buy_trend(indicators)) is DataFrame
|
assert type(strategy.populate_buy_trend(indicators, metadata)) is DataFrame
|
||||||
assert type(strategy.populate_sell_trend(indicators)) is DataFrame
|
assert type(strategy.populate_sell_trend(indicators, metadata)) is DataFrame
|
||||||
|
@ -1,8 +1,10 @@
|
|||||||
# pragma pylint: disable=missing-docstring, protected-access, C0103
|
# pragma pylint: disable=missing-docstring, protected-access, C0103
|
||||||
import logging
|
import logging
|
||||||
import os
|
from os import path
|
||||||
|
import warnings
|
||||||
|
|
||||||
import pytest
|
import pytest
|
||||||
|
from pandas import DataFrame
|
||||||
|
|
||||||
from freqtrade.strategy import import_strategy
|
from freqtrade.strategy import import_strategy
|
||||||
from freqtrade.strategy.default_strategy import DefaultStrategy
|
from freqtrade.strategy.default_strategy import DefaultStrategy
|
||||||
@ -37,8 +39,8 @@ def test_import_strategy(caplog):
|
|||||||
|
|
||||||
def test_search_strategy():
|
def test_search_strategy():
|
||||||
default_config = {}
|
default_config = {}
|
||||||
default_location = os.path.join(os.path.dirname(
|
default_location = path.join(path.dirname(
|
||||||
os.path.realpath(__file__)), '..', '..', 'strategy'
|
path.realpath(__file__)), '..', '..', 'strategy'
|
||||||
)
|
)
|
||||||
assert isinstance(
|
assert isinstance(
|
||||||
StrategyResolver._search_strategy(
|
StrategyResolver._search_strategy(
|
||||||
@ -57,12 +59,13 @@ def test_search_strategy():
|
|||||||
|
|
||||||
def test_load_strategy(result):
|
def test_load_strategy(result):
|
||||||
resolver = StrategyResolver({'strategy': 'TestStrategy'})
|
resolver = StrategyResolver({'strategy': 'TestStrategy'})
|
||||||
assert 'adx' in resolver.strategy.populate_indicators(result)
|
metadata = {'pair': 'ETH/BTC'}
|
||||||
|
assert 'adx' in resolver.strategy.advise_indicators(result, metadata=metadata)
|
||||||
|
|
||||||
|
|
||||||
def test_load_strategy_invalid_directory(result, caplog):
|
def test_load_strategy_invalid_directory(result, caplog):
|
||||||
resolver = StrategyResolver()
|
resolver = StrategyResolver()
|
||||||
extra_dir = os.path.join('some', 'path')
|
extra_dir = path.join('some', 'path')
|
||||||
resolver._load_strategy('TestStrategy', config={}, extra_dir=extra_dir)
|
resolver._load_strategy('TestStrategy', config={}, extra_dir=extra_dir)
|
||||||
|
|
||||||
assert (
|
assert (
|
||||||
@ -70,7 +73,8 @@ def test_load_strategy_invalid_directory(result, caplog):
|
|||||||
logging.WARNING,
|
logging.WARNING,
|
||||||
'Path "{}" does not exist'.format(extra_dir),
|
'Path "{}" does not exist'.format(extra_dir),
|
||||||
) in caplog.record_tuples
|
) in caplog.record_tuples
|
||||||
assert 'adx' in resolver.strategy.populate_indicators(result)
|
|
||||||
|
assert 'adx' in resolver.strategy.advise_indicators(result, {'pair': 'ETH/BTC'})
|
||||||
|
|
||||||
|
|
||||||
def test_load_not_found_strategy():
|
def test_load_not_found_strategy():
|
||||||
@ -85,7 +89,7 @@ def test_strategy(result):
|
|||||||
config = {'strategy': 'DefaultStrategy'}
|
config = {'strategy': 'DefaultStrategy'}
|
||||||
|
|
||||||
resolver = StrategyResolver(config)
|
resolver = StrategyResolver(config)
|
||||||
|
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
|
||||||
|
|
||||||
@ -95,12 +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'
|
||||||
|
|
||||||
assert 'adx' in resolver.strategy.populate_indicators(result)
|
df_indicators = resolver.strategy.advise_indicators(result, metadata=metadata)
|
||||||
|
assert 'adx' in df_indicators
|
||||||
|
|
||||||
dataframe = resolver.strategy.populate_buy_trend(resolver.strategy.populate_indicators(result))
|
dataframe = resolver.strategy.advise_buy(df_indicators, metadata=metadata)
|
||||||
assert 'buy' in dataframe.columns
|
assert 'buy' in dataframe.columns
|
||||||
|
|
||||||
dataframe = resolver.strategy.populate_sell_trend(resolver.strategy.populate_indicators(result))
|
dataframe = resolver.strategy.advise_sell(df_indicators, metadata=metadata)
|
||||||
assert 'sell' in dataframe.columns
|
assert 'sell' in dataframe.columns
|
||||||
|
|
||||||
|
|
||||||
@ -150,3 +155,59 @@ def test_strategy_override_ticker_interval(caplog):
|
|||||||
logging.INFO,
|
logging.INFO,
|
||||||
'Override strategy \'ticker_interval\' with value in config file: 60.'
|
'Override strategy \'ticker_interval\' with value in config file: 60.'
|
||||||
) in caplog.record_tuples
|
) in caplog.record_tuples
|
||||||
|
|
||||||
|
|
||||||
|
def test_deprecate_populate_indicators(result):
|
||||||
|
default_location = path.join(path.dirname(path.realpath(__file__)))
|
||||||
|
resolver = StrategyResolver({'strategy': 'TestStrategyLegacy',
|
||||||
|
'strategy_path': default_location})
|
||||||
|
with warnings.catch_warnings(record=True) as w:
|
||||||
|
# Cause all warnings to always be triggered.
|
||||||
|
warnings.simplefilter("always")
|
||||||
|
indicators = resolver.strategy.advise_indicators(result, 'ETH/BTC')
|
||||||
|
assert len(w) == 1
|
||||||
|
assert issubclass(w[-1].category, DeprecationWarning)
|
||||||
|
assert "deprecated - check out the Sample strategy to see the current function headers!" \
|
||||||
|
in str(w[-1].message)
|
||||||
|
|
||||||
|
with warnings.catch_warnings(record=True) as w:
|
||||||
|
# Cause all warnings to always be triggered.
|
||||||
|
warnings.simplefilter("always")
|
||||||
|
resolver.strategy.advise_buy(indicators, 'ETH/BTC')
|
||||||
|
assert len(w) == 1
|
||||||
|
assert issubclass(w[-1].category, DeprecationWarning)
|
||||||
|
assert "deprecated - check out the Sample strategy to see the current function headers!" \
|
||||||
|
in str(w[-1].message)
|
||||||
|
|
||||||
|
with warnings.catch_warnings(record=True) as w:
|
||||||
|
# Cause all warnings to always be triggered.
|
||||||
|
warnings.simplefilter("always")
|
||||||
|
resolver.strategy.advise_sell(indicators, 'ETH_BTC')
|
||||||
|
assert len(w) == 1
|
||||||
|
assert issubclass(w[-1].category, DeprecationWarning)
|
||||||
|
assert "deprecated - check out the Sample strategy to see the current function headers!" \
|
||||||
|
in str(w[-1].message)
|
||||||
|
|
||||||
|
|
||||||
|
def test_call_deprecated_function(result, monkeypatch):
|
||||||
|
default_location = path.join(path.dirname(path.realpath(__file__)))
|
||||||
|
resolver = StrategyResolver({'strategy': 'TestStrategyLegacy',
|
||||||
|
'strategy_path': default_location})
|
||||||
|
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, metadata=metadata)
|
||||||
|
assert type(indicator_df) is DataFrame
|
||||||
|
assert 'adx' in indicator_df.columns
|
||||||
|
|
||||||
|
buydf = resolver.strategy.advise_buy(result, metadata=metadata)
|
||||||
|
assert type(buydf) is DataFrame
|
||||||
|
assert 'buy' in buydf.columns
|
||||||
|
|
||||||
|
selldf = resolver.strategy.advise_sell(result, metadata=metadata)
|
||||||
|
assert type(selldf) is DataFrame
|
||||||
|
assert 'sell' in selldf
|
||||||
|
@ -14,7 +14,7 @@ def load_dataframe_pair(pairs, strategy):
|
|||||||
assert isinstance(pairs[0], str)
|
assert isinstance(pairs[0], str)
|
||||||
dataframe = ld[pairs[0]]
|
dataframe = ld[pairs[0]]
|
||||||
|
|
||||||
dataframe = strategy.analyze_ticker(dataframe)
|
dataframe = strategy.analyze_ticker(dataframe, pairs[0])
|
||||||
return dataframe
|
return dataframe
|
||||||
|
|
||||||
|
|
||||||
|
@ -159,8 +159,8 @@ def plot_analyzed_dataframe(args: Namespace) -> None:
|
|||||||
dataframes = strategy.tickerdata_to_dataframe(tickers)
|
dataframes = strategy.tickerdata_to_dataframe(tickers)
|
||||||
|
|
||||||
dataframe = dataframes[pair]
|
dataframe = dataframes[pair]
|
||||||
dataframe = strategy.populate_buy_trend(dataframe)
|
dataframe = strategy.advise_buy(dataframe, {'pair': pair})
|
||||||
dataframe = strategy.populate_sell_trend(dataframe)
|
dataframe = strategy.advise_sell(dataframe, {'pair': pair})
|
||||||
|
|
||||||
if len(dataframe.index) > args.plot_limit:
|
if len(dataframe.index) > args.plot_limit:
|
||||||
logger.warning('Ticker contained more than %s candles as defined '
|
logger.warning('Ticker contained more than %s candles as defined '
|
||||||
|
@ -18,6 +18,7 @@ class TestStrategy(IStrategy):
|
|||||||
More information in https://github.com/freqtrade/freqtrade/blob/develop/docs/bot-optimization.md
|
More information in https://github.com/freqtrade/freqtrade/blob/develop/docs/bot-optimization.md
|
||||||
|
|
||||||
You can:
|
You can:
|
||||||
|
:return: a Dataframe with all mandatory indicators for the strategies
|
||||||
- Rename the class name (Do not forget to update class_name)
|
- Rename the class name (Do not forget to update class_name)
|
||||||
- Add any methods you want to build your strategy
|
- Add any methods you want to build your strategy
|
||||||
- Add any lib you need to build your strategy
|
- Add any lib you need to build your strategy
|
||||||
@ -44,13 +45,16 @@ 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) -> 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
|
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
|
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 metadata: Additional information, like the currently traded pair
|
||||||
|
:return: a Dataframe with all mandatory indicators for the strategies
|
||||||
"""
|
"""
|
||||||
|
|
||||||
# Momentum Indicator
|
# Momentum Indicator
|
||||||
@ -211,10 +215,11 @@ class TestStrategy(IStrategy):
|
|||||||
|
|
||||||
return dataframe
|
return dataframe
|
||||||
|
|
||||||
def populate_buy_trend(self, dataframe: DataFrame) -> 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 populated with indicators
|
||||||
|
:param metadata: Additional information, like the currently traded pair
|
||||||
:return: DataFrame with buy column
|
:return: DataFrame with buy column
|
||||||
"""
|
"""
|
||||||
dataframe.loc[
|
dataframe.loc[
|
||||||
@ -227,10 +232,11 @@ class TestStrategy(IStrategy):
|
|||||||
|
|
||||||
return dataframe
|
return dataframe
|
||||||
|
|
||||||
def populate_sell_trend(self, dataframe: DataFrame) -> 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 populated with indicators
|
||||||
|
:param metadata: Additional information, like the currently traded pair
|
||||||
:return: DataFrame with buy column
|
:return: DataFrame with buy column
|
||||||
"""
|
"""
|
||||||
dataframe.loc[
|
dataframe.loc[
|
||||||
|
Loading…
Reference in New Issue
Block a user