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
|
## 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
|
||||||
|
@ -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',
|
||||||
|
@ -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
|
||||||
|
@ -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)
|
||||||
|
@ -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]:
|
||||||
|
@ -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:
|
||||||
"""
|
"""
|
||||||
|
@ -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,
|
||||||
|
@ -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
|
||||||
|
Loading…
Reference in New Issue
Block a user