Merge pull request #1475 from freqtrade/feat/hyperopt_sell

Feat/hyperopt sell
This commit is contained in:
Matthias 2019-01-08 21:19:53 +01:00 committed by GitHub
commit 99e2d795c5
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
8 changed files with 373 additions and 63 deletions

View File

@ -11,30 +11,45 @@ and still take a long time.
## Prepare Hyperopting ## Prepare Hyperopting
Before we start digging in Hyperopt, we recommend you to take a look at Before we start digging into Hyperopt, we recommend you to take a look at
an example hyperopt file located into [user_data/hyperopts/](https://github.com/freqtrade/freqtrade/blob/develop/user_data/hyperopts/test_hyperopt.py) an example hyperopt file located into [user_data/hyperopts/](https://github.com/freqtrade/freqtrade/blob/develop/user_data/hyperopts/test_hyperopt.py)
### 1. Install a Custom Hyperopt File Configuring hyperopt is similar to writing your own strategy, and many tasks will be similar and a lot of code can be copied across from the strategy.
This is very simple. Put your hyperopt file into the folder
`user_data/hyperopts`.
Let assume you want a hyperopt file `awesome_hyperopt.py`:<br/> ### Checklist on all tasks / possibilities in hyperopt
Depending on the space you want to optimize, only some of the below are required.
* fill `populate_indicators` - probably a copy from your strategy
* fill `buy_strategy_generator` - for buy signal optimization
* fill `indicator_space` - for buy signal optimzation
* fill `sell_strategy_generator` - for sell signal optimization
* fill `sell_indicator_space` - for sell signal optimzation
* fill `roi_space` - for ROI optimization
* fill `generate_roi_table` - for ROI optimization (if you need more than 3 entries)
* fill `stoploss_space` - stoploss optimization
* Optional but recommended
* copy `populate_buy_trend` from your strategy - otherwise default-strategy will be used
* copy `populate_sell_trend` from your strategy - otherwise default-strategy will be used
### 1. Install a Custom Hyperopt File
Put your hyperopt file into the folder`user_data/hyperopts`.
Let assume you want a hyperopt file `awesome_hyperopt.py`:
Copy the file `user_data/hyperopts/sample_hyperopt.py` into `user_data/hyperopts/awesome_hyperopt.py` Copy the file `user_data/hyperopts/sample_hyperopt.py` into `user_data/hyperopts/awesome_hyperopt.py`
### 2. Configure your Guards and Triggers ### 2. Configure your Guards and Triggers
There are two places you need to change in your hyperopt file to add a
new buy hyperopt for testing: There are two places you need to change in your hyperopt file to add a new buy hyperopt for testing:
- Inside [populate_buy_trend()](https://github.com/freqtrade/freqtrade/blob/develop/user_data/hyperopts/test_hyperopt.py#L230-L251).
- Inside [indicator_space()](https://github.com/freqtrade/freqtrade/blob/develop/user_data/hyperopts/test_hyperopt.py#L207-L223). - Inside `indicator_space()` - the parameters hyperopt shall be optimizing.
- Inside `populate_buy_trend()` - applying the parameters.
There you have two different types of indicators: 1. `guards` and 2. `triggers`. There you have two different types of indicators: 1. `guards` and 2. `triggers`.
1. Guards are conditions like "never buy if ADX < 10", or never buy if 1. Guards are conditions like "never buy if ADX < 10", or never buy if current price is over EMA10.
current price is over EMA10. 2. Triggers are ones that actually trigger buy in specific moment, like "buy when EMA5 crosses over EMA10" or "buy when close price touches lower bollinger band".
2. Triggers are ones that actually trigger buy in specific moment, like
"buy when EMA5 crosses over EMA10" or "buy when close price touches lower
bollinger band".
Hyperoptimization will, for each eval round, pick one trigger and possibly Hyperoptimization will, for each eval round, pick one trigger and possibly
multiple guards. The constructed strategy will be something like multiple guards. The constructed strategy will be something like
@ -45,6 +60,17 @@ If you have updated the buy strategy, ie. changed the contents of
`populate_buy_trend()` method you have to update the `guards` and `populate_buy_trend()` method you have to update the `guards` and
`triggers` hyperopts must use. `triggers` hyperopts must use.
#### Sell optimization
Similar to the buy-signal above, sell-signals can also be optimized.
Place the corresponding settings into the following methods
* Inside `sell_indicator_space()` - the parameters hyperopt shall be optimizing.
* Inside `populate_sell_trend()` - applying the parameters.
The configuration and rules are the same than for buy signals.
To avoid naming collisions in the search-space, please prefix all sell-spaces with `sell-`.
## Solving a Mystery ## Solving a Mystery
Let's say you are curious: should you use MACD crossings or lower Bollinger Let's say you are curious: should you use MACD crossings or lower Bollinger
@ -55,7 +81,7 @@ mystery.
We will start by defining a search space: We will start by defining a search space:
``` ```python
def indicator_space() -> List[Dimension]: def indicator_space() -> List[Dimension]:
""" """
Define your Hyperopt space for searching strategy parameters Define your Hyperopt space for searching strategy parameters
@ -78,7 +104,7 @@ one we call `trigger` and use it to decide which buy trigger we want to use.
So let's write the buy strategy using these values: So let's write the buy strategy using these values:
``` ``` python
def populate_buy_trend(dataframe: DataFrame) -> DataFrame: def populate_buy_trend(dataframe: DataFrame) -> DataFrame:
conditions = [] conditions = []
# GUARDS AND TRENDS # GUARDS AND TRENDS
@ -88,12 +114,13 @@ So let's write the buy strategy using these values:
conditions.append(dataframe['rsi'] < params['rsi-value']) conditions.append(dataframe['rsi'] < params['rsi-value'])
# TRIGGERS # TRIGGERS
if params['trigger'] == 'bb_lower': if 'trigger' in params:
conditions.append(dataframe['close'] < dataframe['bb_lowerband']) if params['trigger'] == 'bb_lower':
if params['trigger'] == 'macd_cross_signal': conditions.append(dataframe['close'] < dataframe['bb_lowerband'])
conditions.append(qtpylib.crossed_above( if params['trigger'] == 'macd_cross_signal':
dataframe['macd'], dataframe['macdsignal'] conditions.append(qtpylib.crossed_above(
)) dataframe['macd'], dataframe['macdsignal']
))
dataframe.loc[ dataframe.loc[
reduce(lambda x, y: x & y, conditions), reduce(lambda x, y: x & y, conditions),
@ -125,15 +152,19 @@ Because hyperopt tries a lot of combinations to find the best parameters it will
We strongly recommend to use `screen` or `tmux` to prevent any connection loss. We strongly recommend to use `screen` or `tmux` to prevent any connection loss.
```bash ```bash
python3 ./freqtrade/main.py -s <strategyname> --hyperopt <hyperoptname> -c config.json hyperopt -e 5000 python3 ./freqtrade/main.py --hyperopt <hyperoptname> -c config.json hyperopt -e 5000 --spaces all
``` ```
Use `<strategyname>` and `<hyperoptname>` as the names of the custom strategy Use `<hyperoptname>` as the name of the custom hyperopt used.
(only required for generating sells) and the custom hyperopt used.
The `-e` flag will set how many evaluations hyperopt will do. We recommend The `-e` flag will set how many evaluations hyperopt will do. We recommend
running at least several thousand evaluations. running at least several thousand evaluations.
The `--spaces all` flag determines that all possible parameters should be optimized. Possibilities are listed below.
!!! Warning
When switching parameters or changing configuration options, the file `user_data/hyperopt_results.pickle` should be removed. It's used to be able to continue interrupted calculations, but does not detect changes to settings or the hyperopt file.
### Execute Hyperopt with Different Ticker-Data Source ### Execute Hyperopt with Different Ticker-Data Source
If you would like to hyperopt parameters using an alternate ticker data that If you would like to hyperopt parameters using an alternate ticker data that
@ -162,6 +193,7 @@ Legal values are:
- `all`: optimize everything - `all`: optimize everything
- `buy`: just search for a new buy strategy - `buy`: just search for a new buy strategy
- `sell`: just search for a new sell strategy
- `roi`: just optimize the minimal profit table for your strategy - `roi`: just optimize the minimal profit table for your strategy
- `stoploss`: search for the best stoploss value - `stoploss`: search for the best stoploss value
- space-separated list of any of the above values for example `--spaces roi stoploss` - space-separated list of any of the above values for example `--spaces roi stoploss`
@ -175,7 +207,11 @@ Given the following result from hyperopt:
Best result: Best result:
135 trades. Avg profit 0.57%. Total profit 0.03871918 BTC (0.7722Σ%). Avg duration 180.4 mins. 135 trades. Avg profit 0.57%. Total profit 0.03871918 BTC (0.7722Σ%). Avg duration 180.4 mins.
with values: with values:
{'adx-value': 44, 'rsi-value': 29, 'adx-enabled': False, 'rsi-enabled': True, 'trigger': 'bb_lower'} { 'adx-value': 44,
'rsi-value': 29,
'adx-enabled': False,
'rsi-enabled': True,
'trigger': 'bb_lower'}
``` ```
You should understand this result like: You should understand this result like:
@ -215,9 +251,24 @@ If you are optimizing ROI, you're result will look as follows and include a ROI
Best result: Best result:
135 trades. Avg profit 0.57%. Total profit 0.03871918 BTC (0.7722Σ%). Avg duration 180.4 mins. 135 trades. Avg profit 0.57%. Total profit 0.03871918 BTC (0.7722Σ%). Avg duration 180.4 mins.
with values: with values:
{'adx-value': 44, 'rsi-value': 29, 'adx-enabled': False, 'rsi-enabled': True, 'trigger': 'bb_lower', 'roi_t1': 40, 'roi_t2': 57, 'roi_t3': 21, 'roi_p1': 0.03634636907306948, 'roi_p2': 0.055237357937802885, 'roi_p3': 0.015163796015548354, 'stoploss': -0.37996664668703606} { 'adx-value': 44,
'rsi-value': 29,
'adx-enabled': false,
'rsi-enabled': True,
'trigger': 'bb_lower',
'roi_t1': 40,
'roi_t2': 57,
'roi_t3': 21,
'roi_p1': 0.03634636907306948,
'roi_p2': 0.055237357937802885,
'roi_p3': 0.015163796015548354,
'stoploss': -0.37996664668703606
}
ROI table: ROI table:
{0: 0.10674752302642071, 21: 0.09158372701087236, 78: 0.03634636907306948, 118: 0} { 0: 0.10674752302642071,
21: 0.09158372701087236,
78: 0.03634636907306948,
118: 0}
``` ```
This would translate to the following ROI table: This would translate to the following ROI table:
@ -237,6 +288,7 @@ Once the optimized strategy has been implemented into your strategy, you should
To archive the same results (number of trades, ...) than during hyperopt, please use the command line flag `--disable-max-market-positions`. To archive the same results (number of trades, ...) than during hyperopt, please use the command line flag `--disable-max-market-positions`.
This setting is the default for hyperopt for speed reasons. You can overwrite this in the configuration by setting `"position_stacking"=false` or by changing the relevant line in your hyperopt file [here](https://github.com/freqtrade/freqtrade/blob/develop/freqtrade/optimize/hyperopt.py#L283). This setting is the default for hyperopt for speed reasons. You can overwrite this in the configuration by setting `"position_stacking"=false` or by changing the relevant line in your hyperopt file [here](https://github.com/freqtrade/freqtrade/blob/develop/freqtrade/optimize/hyperopt.py#L283).
!!! Note:
Dry/live runs will **NOT** use position stacking - therefore it does make sense to also validate the strategy without this as it's closer to reality. Dry/live runs will **NOT** use position stacking - therefore it does make sense to also validate the strategy without this as it's closer to reality.
## Next Step ## Next Step

View File

@ -272,7 +272,7 @@ class Arguments(object):
'-s', '--spaces', '-s', '--spaces',
help='Specify which parameters to hyperopt. Space separate list. \ help='Specify which parameters to hyperopt. Space separate list. \
Default: %(default)s', Default: %(default)s',
choices=['all', 'buy', 'roi', 'stoploss'], choices=['all', 'buy', 'sell', 'roi', 'stoploss'],
default='all', default='all',
nargs='+', nargs='+',
dest='spaces', dest='spaces',

View File

@ -33,6 +33,7 @@ class DefaultHyperOpts(IHyperOpt):
# Bollinger bands # Bollinger bands
bollinger = qtpylib.bollinger_bands(qtpylib.typical_price(dataframe), window=20, stds=2) bollinger = qtpylib.bollinger_bands(qtpylib.typical_price(dataframe), window=20, stds=2)
dataframe['bb_lowerband'] = bollinger['lower'] dataframe['bb_lowerband'] = bollinger['lower']
dataframe['bb_upperband'] = bollinger['upper']
dataframe['sar'] = ta.SAR(dataframe) dataframe['sar'] = ta.SAR(dataframe)
return dataframe return dataframe
@ -57,16 +58,17 @@ class DefaultHyperOpts(IHyperOpt):
conditions.append(dataframe['rsi'] < params['rsi-value']) conditions.append(dataframe['rsi'] < params['rsi-value'])
# TRIGGERS # TRIGGERS
if params['trigger'] == 'bb_lower': if 'trigger' in params:
conditions.append(dataframe['close'] < dataframe['bb_lowerband']) if params['trigger'] == 'bb_lower':
if params['trigger'] == 'macd_cross_signal': conditions.append(dataframe['close'] < dataframe['bb_lowerband'])
conditions.append(qtpylib.crossed_above( if params['trigger'] == 'macd_cross_signal':
dataframe['macd'], dataframe['macdsignal'] conditions.append(qtpylib.crossed_above(
)) dataframe['macd'], dataframe['macdsignal']
if params['trigger'] == 'sar_reversal': ))
conditions.append(qtpylib.crossed_above( if params['trigger'] == 'sar_reversal':
dataframe['close'], dataframe['sar'] conditions.append(qtpylib.crossed_above(
)) dataframe['close'], dataframe['sar']
))
dataframe.loc[ dataframe.loc[
reduce(lambda x, y: x & y, conditions), reduce(lambda x, y: x & y, conditions),
@ -93,6 +95,67 @@ class DefaultHyperOpts(IHyperOpt):
Categorical(['bb_lower', 'macd_cross_signal', 'sar_reversal'], name='trigger') Categorical(['bb_lower', 'macd_cross_signal', 'sar_reversal'], name='trigger')
] ]
@staticmethod
def sell_strategy_generator(params: Dict[str, Any]) -> Callable:
"""
Define the sell strategy parameters to be used by hyperopt
"""
def populate_sell_trend(dataframe: DataFrame, metadata: dict) -> DataFrame:
"""
Sell strategy Hyperopt will build and use
"""
# print(params)
conditions = []
# GUARDS AND TRENDS
if 'sell-mfi-enabled' in params and params['sell-mfi-enabled']:
conditions.append(dataframe['mfi'] > params['sell-mfi-value'])
if 'sell-fastd-enabled' in params and params['sell-fastd-enabled']:
conditions.append(dataframe['fastd'] > params['sell-fastd-value'])
if 'sell-adx-enabled' in params and params['sell-adx-enabled']:
conditions.append(dataframe['adx'] < params['sell-adx-value'])
if 'sell-rsi-enabled' in params and params['sell-rsi-enabled']:
conditions.append(dataframe['rsi'] > params['sell-rsi-value'])
# TRIGGERS
if 'sell-trigger' in params:
if params['sell-trigger'] == 'sell-bb_upper':
conditions.append(dataframe['close'] > dataframe['bb_upperband'])
if params['sell-trigger'] == 'sell-macd_cross_signal':
conditions.append(qtpylib.crossed_above(
dataframe['macdsignal'], dataframe['macd']
))
if params['sell-trigger'] == 'sell-sar_reversal':
conditions.append(qtpylib.crossed_above(
dataframe['sar'], dataframe['close']
))
dataframe.loc[
reduce(lambda x, y: x & y, conditions),
'sell'] = 1
return dataframe
return populate_sell_trend
@staticmethod
def sell_indicator_space() -> List[Dimension]:
"""
Define your Hyperopt space for searching sell strategy parameters
"""
return [
Integer(75, 100, name='sell-mfi-value'),
Integer(50, 100, name='sell-fastd-value'),
Integer(50, 100, name='sell-adx-value'),
Integer(60, 100, name='sell-rsi-value'),
Categorical([True, False], name='sell-mfi-enabled'),
Categorical([True, False], name='sell-fastd-enabled'),
Categorical([True, False], name='sell-adx-enabled'),
Categorical([True, False], name='sell-rsi-enabled'),
Categorical(['sell-bb_upper',
'sell-macd_cross_signal',
'sell-sar_reversal'], name='sell-trigger')
]
@staticmethod @staticmethod
def generate_roi_table(params: Dict) -> Dict[int, float]: def generate_roi_table(params: Dict) -> Dict[int, float]:
""" """
@ -128,3 +191,36 @@ class DefaultHyperOpts(IHyperOpt):
Real(0.01, 0.07, name='roi_p2'), Real(0.01, 0.07, name='roi_p2'),
Real(0.01, 0.20, name='roi_p3'), Real(0.01, 0.20, name='roi_p3'),
] ]
def populate_buy_trend(self, dataframe: DataFrame, metadata: dict) -> DataFrame:
"""
Based on TA indicators. Should be a copy of from strategy
must align to populate_indicators in this file
Only used when --spaces does not include buy
"""
dataframe.loc[
(
(dataframe['close'] < dataframe['bb_lowerband']) &
(dataframe['mfi'] < 16) &
(dataframe['adx'] > 25) &
(dataframe['rsi'] < 21)
),
'buy'] = 1
return dataframe
def populate_sell_trend(self, dataframe: DataFrame, metadata: dict) -> DataFrame:
"""
Based on TA indicators. Should be a copy of from strategy
must align to populate_indicators in this file
Only used when --spaces does not include sell
"""
dataframe.loc[
(
(qtpylib.crossed_above(
dataframe['macdsignal'], dataframe['macd']
)) &
(dataframe['fastd'] > 54)
),
'sell'] = 1
return dataframe

View File

@ -5,17 +5,18 @@ This module contains the hyperopt logic
""" """
import logging import logging
from argparse import Namespace import multiprocessing
import os import os
import sys import sys
from pathlib import Path from argparse import Namespace
from math import exp from math import exp
import multiprocessing
from operator import itemgetter from operator import itemgetter
from pathlib import Path
from pprint import pprint
from typing import Any, Dict, List from typing import Any, Dict, List
from pandas import DataFrame
from joblib import Parallel, delayed, dump, load, wrap_non_picklable_objects from joblib import Parallel, delayed, dump, load, wrap_non_picklable_objects
from pandas import DataFrame
from skopt import Optimizer from skopt import Optimizer
from skopt.space import Dimension from skopt.space import Dimension
@ -26,7 +27,6 @@ from freqtrade.optimize import get_timeframe
from freqtrade.optimize.backtesting import Backtesting from freqtrade.optimize.backtesting import Backtesting
from freqtrade.resolvers import HyperOptResolver from freqtrade.resolvers import HyperOptResolver
logger = logging.getLogger(__name__) logger = logging.getLogger(__name__)
MAX_LOSS = 100000 # just a big enough number to be bad result in loss optimization MAX_LOSS = 100000 # just a big enough number to be bad result in loss optimization
@ -102,13 +102,13 @@ class Hyperopt(Backtesting):
results = sorted(self.trials, key=itemgetter('loss')) results = sorted(self.trials, key=itemgetter('loss'))
best_result = results[0] best_result = results[0]
logger.info( logger.info(
'Best result:\n%s\nwith values:\n%s', 'Best result:\n%s\nwith values:\n',
best_result['result'], best_result['result']
best_result['params']
) )
pprint(best_result['params'], indent=4)
if 'roi_t1' in best_result['params']: if 'roi_t1' in best_result['params']:
logger.info('ROI table:\n%s', logger.info('ROI table:')
self.custom_hyperopt.generate_roi_table(best_result['params'])) pprint(self.custom_hyperopt.generate_roi_table(best_result['params']), indent=4)
def log_results(self, results) -> None: def log_results(self, results) -> None:
""" """
@ -151,6 +151,12 @@ class Hyperopt(Backtesting):
spaces: List[Dimension] = [] spaces: List[Dimension] = []
if self.has_space('buy'): if self.has_space('buy'):
spaces += self.custom_hyperopt.indicator_space() spaces += self.custom_hyperopt.indicator_space()
if self.has_space('sell'):
spaces += self.custom_hyperopt.sell_indicator_space()
# Make sure experimental is enabled
if 'experimental' not in self.config:
self.config['experimental'] = {}
self.config['experimental']['use_sell_signal'] = True
if self.has_space('roi'): if self.has_space('roi'):
spaces += self.custom_hyperopt.roi_space() spaces += self.custom_hyperopt.roi_space()
if self.has_space('stoploss'): if self.has_space('stoploss'):
@ -164,6 +170,13 @@ class Hyperopt(Backtesting):
if self.has_space('buy'): if self.has_space('buy'):
self.advise_buy = self.custom_hyperopt.buy_strategy_generator(params) self.advise_buy = self.custom_hyperopt.buy_strategy_generator(params)
elif hasattr(self.custom_hyperopt, 'populate_buy_trend'):
self.advise_buy = self.custom_hyperopt.populate_buy_trend # type: ignore
if self.has_space('sell'):
self.advise_sell = self.custom_hyperopt.sell_strategy_generator(params)
elif hasattr(self.custom_hyperopt, 'populate_sell_trend'):
self.advise_sell = self.custom_hyperopt.populate_sell_trend # type: ignore
if self.has_space('stoploss'): if self.has_space('stoploss'):
self.strategy.stoploss = params['stoploss'] self.strategy.stoploss = params['stoploss']
@ -247,7 +260,7 @@ class Hyperopt(Backtesting):
timerange=timerange timerange=timerange
) )
if self.has_space('buy'): if self.has_space('buy') or self.has_space('sell'):
self.strategy.advise_indicators = \ self.strategy.advise_indicators = \
self.custom_hyperopt.populate_indicators # type: ignore self.custom_hyperopt.populate_indicators # type: ignore
dump(self.strategy.tickerdata_to_dataframe(data), TICKERDATA_PICKLE) dump(self.strategy.tickerdata_to_dataframe(data), TICKERDATA_PICKLE)

View File

@ -37,6 +37,13 @@ class IHyperOpt(ABC):
Create a buy strategy generator Create a buy strategy generator
""" """
@staticmethod
@abstractmethod
def sell_strategy_generator(params: Dict[str, Any]) -> Callable:
"""
Create a sell strategy generator
"""
@staticmethod @staticmethod
@abstractmethod @abstractmethod
def indicator_space() -> List[Dimension]: def indicator_space() -> List[Dimension]:
@ -44,6 +51,13 @@ class IHyperOpt(ABC):
Create an indicator space Create an indicator space
""" """
@staticmethod
@abstractmethod
def sell_indicator_space() -> List[Dimension]:
"""
Create a sell indicator space
"""
@staticmethod @staticmethod
@abstractmethod @abstractmethod
def generate_roi_table(params: Dict) -> Dict[int, float]: def generate_roi_table(params: Dict) -> Dict[int, float]:

View File

@ -32,6 +32,13 @@ class HyperOptResolver(IResolver):
hyperopt_name = config.get('hyperopt') or DEFAULT_HYPEROPT hyperopt_name = config.get('hyperopt') or DEFAULT_HYPEROPT
self.hyperopt = self._load_hyperopt(hyperopt_name, extra_dir=config.get('hyperopt_path')) self.hyperopt = self._load_hyperopt(hyperopt_name, extra_dir=config.get('hyperopt_path'))
if not hasattr(self.hyperopt, 'populate_buy_trend'):
logger.warning("Custom Hyperopt does not provide populate_buy_trend. "
"Using populate_buy_trend from DefaultStrategy.")
if not hasattr(self.hyperopt, 'populate_sell_trend'):
logger.warning("Custom Hyperopt does not provide populate_sell_trend. "
"Using populate_sell_trend from DefaultStrategy.")
def _load_hyperopt( def _load_hyperopt(
self, hyperopt_name: str, extra_dir: Optional[str] = None) -> IHyperOpt: self, hyperopt_name: str, extra_dir: Optional[str] = None) -> IHyperOpt:
""" """

View File

@ -9,7 +9,8 @@ import pytest
from freqtrade.data.converter import parse_ticker_dataframe from freqtrade.data.converter import parse_ticker_dataframe
from freqtrade.data.history import load_tickerdata_file from freqtrade.data.history import load_tickerdata_file
from freqtrade.optimize.hyperopt import Hyperopt, start from freqtrade.optimize.hyperopt import Hyperopt, start
from freqtrade.resolvers import StrategyResolver from freqtrade.optimize.default_hyperopt import DefaultHyperOpts
from freqtrade.resolvers import StrategyResolver, HyperOptResolver
from freqtrade.tests.conftest import log_has, patch_exchange from freqtrade.tests.conftest import log_has, patch_exchange
from freqtrade.tests.optimize.test_backtesting import get_args from freqtrade.tests.optimize.test_backtesting import get_args
@ -38,6 +39,28 @@ def create_trials(mocker, hyperopt) -> None:
return [{'loss': 1, 'result': 'foo', 'params': {}}] return [{'loss': 1, 'result': 'foo', 'params': {}}]
def test_hyperoptresolver(mocker, default_conf, caplog) -> None:
mocker.patch(
'freqtrade.configuration.Configuration._load_config_file',
lambda *args, **kwargs: default_conf
)
hyperopts = DefaultHyperOpts
delattr(hyperopts, 'populate_buy_trend')
delattr(hyperopts, 'populate_sell_trend')
mocker.patch(
'freqtrade.resolvers.hyperopt_resolver.HyperOptResolver._load_hyperopt',
MagicMock(return_value=hyperopts)
)
x = HyperOptResolver(default_conf, ).hyperopt
assert not hasattr(x, 'populate_buy_trend')
assert not hasattr(x, 'populate_sell_trend')
assert log_has("Custom Hyperopt does not provide populate_sell_trend. "
"Using populate_sell_trend from DefaultStrategy.", caplog.record_tuples)
assert log_has("Custom Hyperopt does not provide populate_buy_trend. "
"Using populate_buy_trend from DefaultStrategy.", caplog.record_tuples)
def test_start(mocker, default_conf, caplog) -> None: def test_start(mocker, default_conf, caplog) -> None:
start_mock = MagicMock() start_mock = MagicMock()
mocker.patch( mocker.patch(
@ -201,7 +224,7 @@ def test_start_calls_optimizer(mocker, default_conf, caplog) -> None:
hyperopt.start() hyperopt.start()
parallel.assert_called_once() parallel.assert_called_once()
assert 'Best result:\nfoo result\nwith values:\n{}' in caplog.text assert 'Best result:\nfoo result\nwith values:\n\n' in caplog.text
assert dumper.called assert dumper.called
@ -312,6 +335,15 @@ def test_generate_optimizer(mocker, default_conf) -> None:
'mfi-enabled': False, 'mfi-enabled': False,
'rsi-enabled': False, 'rsi-enabled': False,
'trigger': 'macd_cross_signal', 'trigger': 'macd_cross_signal',
'sell-adx-value': 0,
'sell-fastd-value': 75,
'sell-mfi-value': 0,
'sell-rsi-value': 0,
'sell-adx-enabled': False,
'sell-fastd-enabled': True,
'sell-mfi-enabled': False,
'sell-rsi-enabled': False,
'sell-trigger': 'macd_cross_signal',
'roi_t1': 60.0, 'roi_t1': 60.0,
'roi_t2': 30.0, 'roi_t2': 30.0,
'roi_t3': 20.0, 'roi_t3': 20.0,

View File

@ -42,6 +42,7 @@ class SampleHyperOpts(IHyperOpt):
# Bollinger bands # Bollinger bands
bollinger = qtpylib.bollinger_bands(qtpylib.typical_price(dataframe), window=20, stds=2) bollinger = qtpylib.bollinger_bands(qtpylib.typical_price(dataframe), window=20, stds=2)
dataframe['bb_lowerband'] = bollinger['lower'] dataframe['bb_lowerband'] = bollinger['lower']
dataframe['bb_upperband'] = bollinger['upper']
dataframe['sar'] = ta.SAR(dataframe) dataframe['sar'] = ta.SAR(dataframe)
return dataframe return dataframe
@ -66,16 +67,17 @@ class SampleHyperOpts(IHyperOpt):
conditions.append(dataframe['rsi'] < params['rsi-value']) conditions.append(dataframe['rsi'] < params['rsi-value'])
# TRIGGERS # TRIGGERS
if params['trigger'] == 'bb_lower': if 'trigger' in params:
conditions.append(dataframe['close'] < dataframe['bb_lowerband']) if params['trigger'] == 'bb_lower':
if params['trigger'] == 'macd_cross_signal': conditions.append(dataframe['close'] < dataframe['bb_lowerband'])
conditions.append(qtpylib.crossed_above( if params['trigger'] == 'macd_cross_signal':
dataframe['macd'], dataframe['macdsignal'] conditions.append(qtpylib.crossed_above(
)) dataframe['macd'], dataframe['macdsignal']
if params['trigger'] == 'sar_reversal': ))
conditions.append(qtpylib.crossed_above( if params['trigger'] == 'sar_reversal':
dataframe['close'], dataframe['sar'] conditions.append(qtpylib.crossed_above(
)) dataframe['close'], dataframe['sar']
))
dataframe.loc[ dataframe.loc[
reduce(lambda x, y: x & y, conditions), reduce(lambda x, y: x & y, conditions),
@ -102,6 +104,67 @@ class SampleHyperOpts(IHyperOpt):
Categorical(['bb_lower', 'macd_cross_signal', 'sar_reversal'], name='trigger') Categorical(['bb_lower', 'macd_cross_signal', 'sar_reversal'], name='trigger')
] ]
@staticmethod
def sell_strategy_generator(params: Dict[str, Any]) -> Callable:
"""
Define the sell strategy parameters to be used by hyperopt
"""
def populate_sell_trend(dataframe: DataFrame, metadata: dict) -> DataFrame:
"""
Sell strategy Hyperopt will build and use
"""
# print(params)
conditions = []
# GUARDS AND TRENDS
if 'sell-mfi-enabled' in params and params['sell-mfi-enabled']:
conditions.append(dataframe['mfi'] > params['sell-mfi-value'])
if 'sell-fastd-enabled' in params and params['sell-fastd-enabled']:
conditions.append(dataframe['fastd'] > params['sell-fastd-value'])
if 'sell-adx-enabled' in params and params['sell-adx-enabled']:
conditions.append(dataframe['adx'] < params['sell-adx-value'])
if 'sell-rsi-enabled' in params and params['sell-rsi-enabled']:
conditions.append(dataframe['rsi'] > params['sell-rsi-value'])
# TRIGGERS
if 'sell-trigger' in params:
if params['sell-trigger'] == 'sell-bb_upper':
conditions.append(dataframe['close'] > dataframe['bb_upperband'])
if params['sell-trigger'] == 'sell-macd_cross_signal':
conditions.append(qtpylib.crossed_above(
dataframe['macdsignal'], dataframe['macd']
))
if params['sell-trigger'] == 'sell-sar_reversal':
conditions.append(qtpylib.crossed_above(
dataframe['sar'], dataframe['close']
))
dataframe.loc[
reduce(lambda x, y: x & y, conditions),
'sell'] = 1
return dataframe
return populate_sell_trend
@staticmethod
def sell_indicator_space() -> List[Dimension]:
"""
Define your Hyperopt space for searching sell strategy parameters
"""
return [
Integer(75, 100, name='sell-mfi-value'),
Integer(50, 100, name='sell-fastd-value'),
Integer(50, 100, name='sell-adx-value'),
Integer(60, 100, name='sell-rsi-value'),
Categorical([True, False], name='sell-mfi-enabled'),
Categorical([True, False], name='sell-fastd-enabled'),
Categorical([True, False], name='sell-adx-enabled'),
Categorical([True, False], name='sell-rsi-enabled'),
Categorical(['sell-bb_upper',
'sell-macd_cross_signal',
'sell-sar_reversal'], name='sell-trigger')
]
@staticmethod @staticmethod
def generate_roi_table(params: Dict) -> Dict[int, float]: def generate_roi_table(params: Dict) -> Dict[int, float]:
""" """
@ -137,3 +200,36 @@ class SampleHyperOpts(IHyperOpt):
Real(0.01, 0.07, name='roi_p2'), Real(0.01, 0.07, name='roi_p2'),
Real(0.01, 0.20, name='roi_p3'), Real(0.01, 0.20, name='roi_p3'),
] ]
def populate_buy_trend(self, dataframe: DataFrame, metadata: dict) -> DataFrame:
"""
Based on TA indicators. Should be a copy of from strategy
must align to populate_indicators in this file
Only used when --spaces does not include buy
"""
dataframe.loc[
(
(dataframe['close'] < dataframe['bb_lowerband']) &
(dataframe['mfi'] < 16) &
(dataframe['adx'] > 25) &
(dataframe['rsi'] < 21)
),
'buy'] = 1
return dataframe
def populate_sell_trend(self, dataframe: DataFrame, metadata: dict) -> DataFrame:
"""
Based on TA indicators. Should be a copy of from strategy
must align to populate_indicators in this file
Only used when --spaces does not include sell
"""
dataframe.loc[
(
(qtpylib.crossed_above(
dataframe['macdsignal'], dataframe['macd']
)) &
(dataframe['fastd'] > 54)
),
'sell'] = 1
return dataframe