Merge pull request #1475 from freqtrade/feat/hyperopt_sell
Feat/hyperopt sell
This commit is contained in:
commit
99e2d795c5
110
docs/hyperopt.md
110
docs/hyperopt.md
@ -11,30 +11,45 @@ and still take a long time.
|
||||
|
||||
## 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)
|
||||
|
||||
### 1. Install a Custom Hyperopt File
|
||||
This is very simple. Put your hyperopt file into the folder
|
||||
`user_data/hyperopts`.
|
||||
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.
|
||||
|
||||
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`
|
||||
|
||||
|
||||
### 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:
|
||||
- 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).
|
||||
|
||||
There are two places you need to change in your hyperopt file to add a new buy hyperopt for testing:
|
||||
|
||||
- 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`.
|
||||
|
||||
1. Guards are conditions like "never buy if ADX < 10", or never buy if
|
||||
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".
|
||||
1. Guards are conditions like "never buy if ADX < 10", or never buy if 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".
|
||||
|
||||
Hyperoptimization will, for each eval round, pick one trigger and possibly
|
||||
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
|
||||
`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
|
||||
|
||||
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:
|
||||
|
||||
```
|
||||
```python
|
||||
def indicator_space() -> List[Dimension]:
|
||||
"""
|
||||
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:
|
||||
|
||||
```
|
||||
``` python
|
||||
def populate_buy_trend(dataframe: DataFrame) -> DataFrame:
|
||||
conditions = []
|
||||
# GUARDS AND TRENDS
|
||||
@ -88,12 +114,13 @@ So let's write the buy strategy using these values:
|
||||
conditions.append(dataframe['rsi'] < params['rsi-value'])
|
||||
|
||||
# TRIGGERS
|
||||
if params['trigger'] == 'bb_lower':
|
||||
conditions.append(dataframe['close'] < dataframe['bb_lowerband'])
|
||||
if params['trigger'] == 'macd_cross_signal':
|
||||
conditions.append(qtpylib.crossed_above(
|
||||
dataframe['macd'], dataframe['macdsignal']
|
||||
))
|
||||
if 'trigger' in params:
|
||||
if params['trigger'] == 'bb_lower':
|
||||
conditions.append(dataframe['close'] < dataframe['bb_lowerband'])
|
||||
if params['trigger'] == 'macd_cross_signal':
|
||||
conditions.append(qtpylib.crossed_above(
|
||||
dataframe['macd'], dataframe['macdsignal']
|
||||
))
|
||||
|
||||
dataframe.loc[
|
||||
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.
|
||||
|
||||
```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
|
||||
(only required for generating sells) and the custom hyperopt used.
|
||||
Use `<hyperoptname>` as the name of the custom hyperopt used.
|
||||
|
||||
The `-e` flag will set how many evaluations hyperopt will do. We recommend
|
||||
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
|
||||
|
||||
If you would like to hyperopt parameters using an alternate ticker data that
|
||||
@ -162,6 +193,7 @@ Legal values are:
|
||||
|
||||
- `all`: optimize everything
|
||||
- `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
|
||||
- `stoploss`: search for the best stoploss value
|
||||
- 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:
|
||||
135 trades. Avg profit 0.57%. Total profit 0.03871918 BTC (0.7722Σ%). Avg duration 180.4 mins.
|
||||
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:
|
||||
@ -215,9 +251,24 @@ If you are optimizing ROI, you're result will look as follows and include a ROI
|
||||
Best result:
|
||||
135 trades. Avg profit 0.57%. Total profit 0.03871918 BTC (0.7722Σ%). Avg duration 180.4 mins.
|
||||
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:
|
||||
{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:
|
||||
@ -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`.
|
||||
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.
|
||||
|
||||
## Next Step
|
||||
|
@ -272,7 +272,7 @@ class Arguments(object):
|
||||
'-s', '--spaces',
|
||||
help='Specify which parameters to hyperopt. Space separate list. \
|
||||
Default: %(default)s',
|
||||
choices=['all', 'buy', 'roi', 'stoploss'],
|
||||
choices=['all', 'buy', 'sell', 'roi', 'stoploss'],
|
||||
default='all',
|
||||
nargs='+',
|
||||
dest='spaces',
|
||||
|
@ -33,6 +33,7 @@ class DefaultHyperOpts(IHyperOpt):
|
||||
# Bollinger bands
|
||||
bollinger = qtpylib.bollinger_bands(qtpylib.typical_price(dataframe), window=20, stds=2)
|
||||
dataframe['bb_lowerband'] = bollinger['lower']
|
||||
dataframe['bb_upperband'] = bollinger['upper']
|
||||
dataframe['sar'] = ta.SAR(dataframe)
|
||||
return dataframe
|
||||
|
||||
@ -57,16 +58,17 @@ class DefaultHyperOpts(IHyperOpt):
|
||||
conditions.append(dataframe['rsi'] < params['rsi-value'])
|
||||
|
||||
# TRIGGERS
|
||||
if params['trigger'] == 'bb_lower':
|
||||
conditions.append(dataframe['close'] < dataframe['bb_lowerband'])
|
||||
if params['trigger'] == 'macd_cross_signal':
|
||||
conditions.append(qtpylib.crossed_above(
|
||||
dataframe['macd'], dataframe['macdsignal']
|
||||
))
|
||||
if params['trigger'] == 'sar_reversal':
|
||||
conditions.append(qtpylib.crossed_above(
|
||||
dataframe['close'], dataframe['sar']
|
||||
))
|
||||
if 'trigger' in params:
|
||||
if params['trigger'] == 'bb_lower':
|
||||
conditions.append(dataframe['close'] < dataframe['bb_lowerband'])
|
||||
if params['trigger'] == 'macd_cross_signal':
|
||||
conditions.append(qtpylib.crossed_above(
|
||||
dataframe['macd'], dataframe['macdsignal']
|
||||
))
|
||||
if params['trigger'] == 'sar_reversal':
|
||||
conditions.append(qtpylib.crossed_above(
|
||||
dataframe['close'], dataframe['sar']
|
||||
))
|
||||
|
||||
dataframe.loc[
|
||||
reduce(lambda x, y: x & y, conditions),
|
||||
@ -93,6 +95,67 @@ class DefaultHyperOpts(IHyperOpt):
|
||||
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
|
||||
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.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
|
||||
|
@ -5,17 +5,18 @@ This module contains the hyperopt logic
|
||||
"""
|
||||
|
||||
import logging
|
||||
from argparse import Namespace
|
||||
import multiprocessing
|
||||
import os
|
||||
import sys
|
||||
from pathlib import Path
|
||||
from argparse import Namespace
|
||||
from math import exp
|
||||
import multiprocessing
|
||||
from operator import itemgetter
|
||||
from pathlib import Path
|
||||
from pprint import pprint
|
||||
from typing import Any, Dict, List
|
||||
|
||||
from pandas import DataFrame
|
||||
from joblib import Parallel, delayed, dump, load, wrap_non_picklable_objects
|
||||
from pandas import DataFrame
|
||||
from skopt import Optimizer
|
||||
from skopt.space import Dimension
|
||||
|
||||
@ -26,7 +27,6 @@ from freqtrade.optimize import get_timeframe
|
||||
from freqtrade.optimize.backtesting import Backtesting
|
||||
from freqtrade.resolvers import HyperOptResolver
|
||||
|
||||
|
||||
logger = logging.getLogger(__name__)
|
||||
|
||||
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'))
|
||||
best_result = results[0]
|
||||
logger.info(
|
||||
'Best result:\n%s\nwith values:\n%s',
|
||||
best_result['result'],
|
||||
best_result['params']
|
||||
'Best result:\n%s\nwith values:\n',
|
||||
best_result['result']
|
||||
)
|
||||
pprint(best_result['params'], indent=4)
|
||||
if 'roi_t1' in best_result['params']:
|
||||
logger.info('ROI table:\n%s',
|
||||
self.custom_hyperopt.generate_roi_table(best_result['params']))
|
||||
logger.info('ROI table:')
|
||||
pprint(self.custom_hyperopt.generate_roi_table(best_result['params']), indent=4)
|
||||
|
||||
def log_results(self, results) -> None:
|
||||
"""
|
||||
@ -151,6 +151,12 @@ class Hyperopt(Backtesting):
|
||||
spaces: List[Dimension] = []
|
||||
if self.has_space('buy'):
|
||||
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'):
|
||||
spaces += self.custom_hyperopt.roi_space()
|
||||
if self.has_space('stoploss'):
|
||||
@ -164,6 +170,13 @@ class Hyperopt(Backtesting):
|
||||
|
||||
if self.has_space('buy'):
|
||||
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'):
|
||||
self.strategy.stoploss = params['stoploss']
|
||||
@ -247,7 +260,7 @@ class Hyperopt(Backtesting):
|
||||
timerange=timerange
|
||||
)
|
||||
|
||||
if self.has_space('buy'):
|
||||
if self.has_space('buy') or self.has_space('sell'):
|
||||
self.strategy.advise_indicators = \
|
||||
self.custom_hyperopt.populate_indicators # type: ignore
|
||||
dump(self.strategy.tickerdata_to_dataframe(data), TICKERDATA_PICKLE)
|
||||
|
@ -37,6 +37,13 @@ class IHyperOpt(ABC):
|
||||
Create a buy strategy generator
|
||||
"""
|
||||
|
||||
@staticmethod
|
||||
@abstractmethod
|
||||
def sell_strategy_generator(params: Dict[str, Any]) -> Callable:
|
||||
"""
|
||||
Create a sell strategy generator
|
||||
"""
|
||||
|
||||
@staticmethod
|
||||
@abstractmethod
|
||||
def indicator_space() -> List[Dimension]:
|
||||
@ -44,6 +51,13 @@ class IHyperOpt(ABC):
|
||||
Create an indicator space
|
||||
"""
|
||||
|
||||
@staticmethod
|
||||
@abstractmethod
|
||||
def sell_indicator_space() -> List[Dimension]:
|
||||
"""
|
||||
Create a sell indicator space
|
||||
"""
|
||||
|
||||
@staticmethod
|
||||
@abstractmethod
|
||||
def generate_roi_table(params: Dict) -> Dict[int, float]:
|
||||
|
@ -32,6 +32,13 @@ class HyperOptResolver(IResolver):
|
||||
hyperopt_name = config.get('hyperopt') or DEFAULT_HYPEROPT
|
||||
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(
|
||||
self, hyperopt_name: str, extra_dir: Optional[str] = None) -> IHyperOpt:
|
||||
"""
|
||||
|
@ -9,7 +9,8 @@ import pytest
|
||||
from freqtrade.data.converter import parse_ticker_dataframe
|
||||
from freqtrade.data.history import load_tickerdata_file
|
||||
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.optimize.test_backtesting import get_args
|
||||
|
||||
@ -38,6 +39,28 @@ def create_trials(mocker, hyperopt) -> None:
|
||||
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:
|
||||
start_mock = MagicMock()
|
||||
mocker.patch(
|
||||
@ -201,7 +224,7 @@ def test_start_calls_optimizer(mocker, default_conf, caplog) -> None:
|
||||
hyperopt.start()
|
||||
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
|
||||
|
||||
|
||||
@ -312,6 +335,15 @@ def test_generate_optimizer(mocker, default_conf) -> None:
|
||||
'mfi-enabled': False,
|
||||
'rsi-enabled': False,
|
||||
'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_t2': 30.0,
|
||||
'roi_t3': 20.0,
|
||||
|
@ -42,6 +42,7 @@ class SampleHyperOpts(IHyperOpt):
|
||||
# Bollinger bands
|
||||
bollinger = qtpylib.bollinger_bands(qtpylib.typical_price(dataframe), window=20, stds=2)
|
||||
dataframe['bb_lowerband'] = bollinger['lower']
|
||||
dataframe['bb_upperband'] = bollinger['upper']
|
||||
dataframe['sar'] = ta.SAR(dataframe)
|
||||
return dataframe
|
||||
|
||||
@ -66,16 +67,17 @@ class SampleHyperOpts(IHyperOpt):
|
||||
conditions.append(dataframe['rsi'] < params['rsi-value'])
|
||||
|
||||
# TRIGGERS
|
||||
if params['trigger'] == 'bb_lower':
|
||||
conditions.append(dataframe['close'] < dataframe['bb_lowerband'])
|
||||
if params['trigger'] == 'macd_cross_signal':
|
||||
conditions.append(qtpylib.crossed_above(
|
||||
dataframe['macd'], dataframe['macdsignal']
|
||||
))
|
||||
if params['trigger'] == 'sar_reversal':
|
||||
conditions.append(qtpylib.crossed_above(
|
||||
dataframe['close'], dataframe['sar']
|
||||
))
|
||||
if 'trigger' in params:
|
||||
if params['trigger'] == 'bb_lower':
|
||||
conditions.append(dataframe['close'] < dataframe['bb_lowerband'])
|
||||
if params['trigger'] == 'macd_cross_signal':
|
||||
conditions.append(qtpylib.crossed_above(
|
||||
dataframe['macd'], dataframe['macdsignal']
|
||||
))
|
||||
if params['trigger'] == 'sar_reversal':
|
||||
conditions.append(qtpylib.crossed_above(
|
||||
dataframe['close'], dataframe['sar']
|
||||
))
|
||||
|
||||
dataframe.loc[
|
||||
reduce(lambda x, y: x & y, conditions),
|
||||
@ -102,6 +104,67 @@ class SampleHyperOpts(IHyperOpt):
|
||||
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
|
||||
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.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
|
||||
|
Loading…
Reference in New Issue
Block a user