Merge pull request #4740 from freqtrade/decimal_stoploss_Hyperopt

stoploss and roi skdecimal spaces hyperopt
This commit is contained in:
Matthias 2021-04-16 19:46:07 +02:00 committed by GitHub
commit 9f2b6ee9a5
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
8 changed files with 70 additions and 29 deletions

View File

@ -79,9 +79,31 @@ class MyAwesomeStrategy(IStrategy):
class HyperOpt: class HyperOpt:
# Define a custom stoploss space. # Define a custom stoploss space.
def stoploss_space(self): def stoploss_space(self):
return [Real(-0.05, -0.01, name='stoploss')] return [SKDecimal(-0.05, -0.01, decimals=3, name='stoploss')]
``` ```
## Space options
For the additional spaces, scikit-optimize (in combination with Freqtrade) provides the following space types:
* `Categorical` - Pick from a list of categories (e.g. `Categorical(['a', 'b', 'c'], name="cat")`)
* `Integer` - Pick from a range of whole numbers (e.g. `Integer(1, 10, name='rsi')`)
* `SKDecimal` - Pick from a range of decimal numbers with limited precision (e.g. `SKDecimal(0.1, 0.5, decimals=3, name='adx')`). *Available only with freqtrade*.
* `Real` - Pick from a range of decimal numbers with full precision (e.g. `Real(0.1, 0.5, name='adx')`
You can import all of these from `freqtrade.optimize.space`, although `Categorical`, `Integer` and `Real` are only aliases for their corresponding scikit-optimize Spaces. `SKDecimal` is provided by freqtrade for faster optimizations.
``` python
from freqtrade.optimize.space import Categorical, Dimension, Integer, SKDecimal, Real # noqa
```
!!! Hint "SKDecimal vs. Real"
We recommend to use `SKDecimal` instead of the `Real` space in almost all cases. While the Real space provides full accuracy (up to ~16 decimal places) - this precision is rarely needed, and leads to unnecessary long hyperopt times.
Assuming the definition of a rather small space (`SKDecimal(0.10, 0.15, decimals=2, name='xxx')`) - SKDecimal will have 5 possibilities (`[0.10, 0.11, 0.12, 0.13, 0.14, 0.15]`).
A corresponding real space `Real(0.10, 0.15 name='xxx')` on the other hand has an almost unlimited number of possibilities (`[0.10, 0.010000000001, 0.010000000002, ... 0.014999999999, 0.01500000000]`).
--- ---
## Legacy Hyperopt ## Legacy Hyperopt

View File

@ -294,6 +294,7 @@ Based on the results, hyperopt will tell you which parameter combination produce
## Parameter types ## Parameter types
There are four parameter types each suited for different purposes. There are four parameter types each suited for different purposes.
* `IntParameter` - defines an integral parameter with upper and lower boundaries of search space. * `IntParameter` - defines an integral parameter with upper and lower boundaries of search space.
* `DecimalParameter` - defines a floating point parameter with a limited number of decimals (default 3). Should be preferred instead of `RealParameter` in most cases. * `DecimalParameter` - defines a floating point parameter with a limited number of decimals (default 3). Should be preferred instead of `RealParameter` in most cases.
* `RealParameter` - defines a floating point parameter with upper and lower boundaries and no precision limit. Rarely used as it creates a space with a near infinite number of possibilities. * `RealParameter` - defines a floating point parameter with upper and lower boundaries and no precision limit. Rarely used as it creates a space with a near infinite number of possibilities.
@ -460,13 +461,13 @@ As stated in the comment, you can also use it as the value of the `minimal_roi`
#### Default ROI Search Space #### Default ROI Search Space
If you are optimizing ROI, Freqtrade creates the 'roi' optimization hyperspace for you -- it's the hyperspace of components for the ROI tables. By default, each ROI table generated by the Freqtrade consists of 4 rows (steps). Hyperopt implements adaptive ranges for ROI tables with ranges for values in the ROI steps that depend on the timeframe used. By default the values vary in the following ranges (for some of the most used timeframes, values are rounded to 5 digits after the decimal point): If you are optimizing ROI, Freqtrade creates the 'roi' optimization hyperspace for you -- it's the hyperspace of components for the ROI tables. By default, each ROI table generated by the Freqtrade consists of 4 rows (steps). Hyperopt implements adaptive ranges for ROI tables with ranges for values in the ROI steps that depend on the timeframe used. By default the values vary in the following ranges (for some of the most used timeframes, values are rounded to 3 digits after the decimal point):
| # step | 1m | | 5m | | 1h | | 1d | | | # step | 1m | | 5m | | 1h | | 1d | |
| ------ | ------ | ----------------- | -------- | ----------- | ---------- | ----------------- | ------------ | ----------------- | | ------ | ------ | ------------- | -------- | ----------- | ---------- | ------------- | ------------ | ------------- |
| 1 | 0 | 0.01161...0.11992 | 0 | 0.03...0.31 | 0 | 0.06883...0.71124 | 0 | 0.12178...1.25835 | | 1 | 0 | 0.011...0.119 | 0 | 0.03...0.31 | 0 | 0.068...0.711 | 0 | 0.121...1.258 |
| 2 | 2...8 | 0.00774...0.04255 | 10...40 | 0.02...0.11 | 120...480 | 0.04589...0.25238 | 2880...11520 | 0.08118...0.44651 | | 2 | 2...8 | 0.007...0.042 | 10...40 | 0.02...0.11 | 120...480 | 0.045...0.252 | 2880...11520 | 0.081...0.446 |
| 3 | 4...20 | 0.00387...0.01547 | 20...100 | 0.01...0.04 | 240...1200 | 0.02294...0.09177 | 5760...28800 | 0.04059...0.16237 | | 3 | 4...20 | 0.003...0.015 | 20...100 | 0.01...0.04 | 240...1200 | 0.022...0.091 | 5760...28800 | 0.040...0.162 |
| 4 | 6...44 | 0.0 | 30...220 | 0.0 | 360...2640 | 0.0 | 8640...63360 | 0.0 | | 4 | 6...44 | 0.0 | 30...220 | 0.0 | 360...2640 | 0.0 | 8640...63360 | 0.0 |
These ranges should be sufficient in most cases. The minutes in the steps (ROI dict keys) are scaled linearly depending on the timeframe used. The ROI values in the steps (ROI dict values) are scaled logarithmically depending on the timeframe used. These ranges should be sufficient in most cases. The minutes in the steps (ROI dict keys) are scaled linearly depending on the timeframe used. The ROI values in the steps (ROI dict values) are scaled logarithmically depending on the timeframe used.
@ -477,6 +478,9 @@ Override the `roi_space()` method if you need components of the ROI tables to va
A sample for these methods can be found in [sample_hyperopt_advanced.py](https://github.com/freqtrade/freqtrade/blob/develop/freqtrade/templates/sample_hyperopt_advanced.py). A sample for these methods can be found in [sample_hyperopt_advanced.py](https://github.com/freqtrade/freqtrade/blob/develop/freqtrade/templates/sample_hyperopt_advanced.py).
!!! Note "Reduced search space"
To limit the search space further, Decimals are limited to 3 decimal places (a precision of 0.001). This is usually sufficient, every value more precise than this will usually result in overfitted results. You can however [overriding pre-defined spaces](advanced-hyperopt.md#pverriding-pre-defined-spaces) to change this to your needs.
### Understand Hyperopt Stoploss results ### Understand Hyperopt Stoploss results
If you are optimizing stoploss values (i.e. if optimization search-space contains 'all', 'default' or 'stoploss'), your result will look as follows and include stoploss: If you are optimizing stoploss values (i.e. if optimization search-space contains 'all', 'default' or 'stoploss'), your result will look as follows and include stoploss:
@ -516,6 +520,9 @@ If you have the `stoploss_space()` method in your custom hyperopt file, remove i
Override the `stoploss_space()` method and define the desired range in it if you need stoploss values to vary in other range during hyperoptimization. A sample for this method can be found in [user_data/hyperopts/sample_hyperopt_advanced.py](https://github.com/freqtrade/freqtrade/blob/develop/freqtrade/templates/sample_hyperopt_advanced.py). Override the `stoploss_space()` method and define the desired range in it if you need stoploss values to vary in other range during hyperoptimization. A sample for this method can be found in [user_data/hyperopts/sample_hyperopt_advanced.py](https://github.com/freqtrade/freqtrade/blob/develop/freqtrade/templates/sample_hyperopt_advanced.py).
!!! Note "Reduced search space"
To limit the search space further, Decimals are limited to 3 decimal places (a precision of 0.001). This is usually sufficient, every value more precise than this will usually result in overfitted results. You can however [overriding pre-defined spaces](advanced-hyperopt.md#pverriding-pre-defined-spaces) to change this to your needs.
### Understand Hyperopt Trailing Stop results ### Understand Hyperopt Trailing Stop results
If you are optimizing trailing stop values (i.e. if optimization search-space contains 'all' or 'trailing'), your result will look as follows and include trailing stop parameters: If you are optimizing trailing stop values (i.e. if optimization search-space contains 'all' or 'trailing'), your result will look as follows and include trailing stop parameters:
@ -551,6 +558,9 @@ If you are optimizing trailing stop values, Freqtrade creates the 'trailing' opt
Override the `trailing_space()` method and define the desired range in it if you need values of the trailing stop parameters to vary in other ranges during hyperoptimization. A sample for this method can be found in [user_data/hyperopts/sample_hyperopt_advanced.py](https://github.com/freqtrade/freqtrade/blob/develop/freqtrade/templates/sample_hyperopt_advanced.py). Override the `trailing_space()` method and define the desired range in it if you need values of the trailing stop parameters to vary in other ranges during hyperoptimization. A sample for this method can be found in [user_data/hyperopts/sample_hyperopt_advanced.py](https://github.com/freqtrade/freqtrade/blob/develop/freqtrade/templates/sample_hyperopt_advanced.py).
!!! Note "Reduced search space"
To limit the search space further, Decimals are limited to 3 decimal places (a precision of 0.001). This is usually sufficient, every value more precise than this will usually result in overfitted results. You can however [overriding pre-defined spaces](advanced-hyperopt.md#pverriding-pre-defined-spaces) to change this to your needs.
### Reproducible results ### Reproducible results
The search for optimal parameters starts with a few (currently 30) random combinations in the hyperspace of parameters, random Hyperopt epochs. These random epochs are marked with an asterisk character (`*`) in the first column in the Hyperopt output. The search for optimal parameters starts with a few (currently 30) random combinations in the hyperspace of parameters, random Hyperopt epochs. These random epochs are marked with an asterisk character (`*`) in the first column in the Hyperopt output.

View File

@ -7,11 +7,12 @@ import math
from abc import ABC from abc import ABC
from typing import Any, Callable, Dict, List from typing import Any, Callable, Dict, List
from skopt.space import Categorical, Dimension, Integer, Real from skopt.space import Categorical, Dimension, Integer
from freqtrade.exceptions import OperationalException from freqtrade.exceptions import OperationalException
from freqtrade.exchange import timeframe_to_minutes from freqtrade.exchange import timeframe_to_minutes
from freqtrade.misc import round_dict from freqtrade.misc import round_dict
from freqtrade.optimize.space import SKDecimal
from freqtrade.strategy import IStrategy from freqtrade.strategy import IStrategy
@ -139,7 +140,7 @@ class IHyperOpt(ABC):
'roi_p2': roi_limits['roi_p2_min'], 'roi_p2': roi_limits['roi_p2_min'],
'roi_p3': roi_limits['roi_p3_min'], 'roi_p3': roi_limits['roi_p3_min'],
} }
logger.info(f"Min roi table: {round_dict(self.generate_roi_table(p), 5)}") logger.info(f"Min roi table: {round_dict(self.generate_roi_table(p), 3)}")
p = { p = {
'roi_t1': roi_limits['roi_t1_max'], 'roi_t1': roi_limits['roi_t1_max'],
'roi_t2': roi_limits['roi_t2_max'], 'roi_t2': roi_limits['roi_t2_max'],
@ -148,15 +149,18 @@ class IHyperOpt(ABC):
'roi_p2': roi_limits['roi_p2_max'], 'roi_p2': roi_limits['roi_p2_max'],
'roi_p3': roi_limits['roi_p3_max'], 'roi_p3': roi_limits['roi_p3_max'],
} }
logger.info(f"Max roi table: {round_dict(self.generate_roi_table(p), 5)}") logger.info(f"Max roi table: {round_dict(self.generate_roi_table(p), 3)}")
return [ return [
Integer(roi_limits['roi_t1_min'], roi_limits['roi_t1_max'], name='roi_t1'), Integer(roi_limits['roi_t1_min'], roi_limits['roi_t1_max'], name='roi_t1'),
Integer(roi_limits['roi_t2_min'], roi_limits['roi_t2_max'], name='roi_t2'), Integer(roi_limits['roi_t2_min'], roi_limits['roi_t2_max'], name='roi_t2'),
Integer(roi_limits['roi_t3_min'], roi_limits['roi_t3_max'], name='roi_t3'), Integer(roi_limits['roi_t3_min'], roi_limits['roi_t3_max'], name='roi_t3'),
Real(roi_limits['roi_p1_min'], roi_limits['roi_p1_max'], name='roi_p1'), SKDecimal(roi_limits['roi_p1_min'], roi_limits['roi_p1_max'], decimals=3,
Real(roi_limits['roi_p2_min'], roi_limits['roi_p2_max'], name='roi_p2'), name='roi_p1'),
Real(roi_limits['roi_p3_min'], roi_limits['roi_p3_max'], name='roi_p3'), SKDecimal(roi_limits['roi_p2_min'], roi_limits['roi_p2_max'], decimals=3,
name='roi_p2'),
SKDecimal(roi_limits['roi_p3_min'], roi_limits['roi_p3_max'], decimals=3,
name='roi_p3'),
] ]
def stoploss_space(self) -> List[Dimension]: def stoploss_space(self) -> List[Dimension]:
@ -167,7 +171,7 @@ class IHyperOpt(ABC):
You may override it in your custom Hyperopt class. You may override it in your custom Hyperopt class.
""" """
return [ return [
Real(-0.35, -0.02, name='stoploss'), SKDecimal(-0.35, -0.02, decimals=3, name='stoploss'),
] ]
def generate_trailing_params(self, params: Dict) -> Dict: def generate_trailing_params(self, params: Dict) -> Dict:
@ -197,14 +201,14 @@ class IHyperOpt(ABC):
# other 'trailing' hyperspace parameters. # other 'trailing' hyperspace parameters.
Categorical([True], name='trailing_stop'), Categorical([True], name='trailing_stop'),
Real(0.01, 0.35, name='trailing_stop_positive'), SKDecimal(0.01, 0.35, decimals=3, name='trailing_stop_positive'),
# 'trailing_stop_positive_offset' should be greater than 'trailing_stop_positive', # 'trailing_stop_positive_offset' should be greater than 'trailing_stop_positive',
# so this intermediate parameter is used as the value of the difference between # so this intermediate parameter is used as the value of the difference between
# them. The value of the 'trailing_stop_positive_offset' is constructed in the # them. The value of the 'trailing_stop_positive_offset' is constructed in the
# generate_trailing_params() method. # generate_trailing_params() method.
# This is similar to the hyperspace dimensions used for constructing the ROI tables. # This is similar to the hyperspace dimensions used for constructing the ROI tables.
Real(0.001, 0.1, name='trailing_stop_positive_offset_p1'), SKDecimal(0.001, 0.1, decimals=3, name='trailing_stop_positive_offset_p1'),
Categorical([True, False], name='trailing_only_offset_is_reached'), Categorical([True, False], name='trailing_only_offset_is_reached'),
] ]

View File

@ -0,0 +1,4 @@
# flake8: noqa: F401
from skopt.space import Categorical, Dimension, Integer, Real
from .decimalspace import SKDecimal

View File

@ -9,8 +9,9 @@ class SKDecimal(Integer):
self.decimals = decimals self.decimals = decimals
_low = int(low * pow(10, self.decimals)) _low = int(low * pow(10, self.decimals))
_high = int(high * pow(10, self.decimals)) _high = int(high * pow(10, self.decimals))
self.low_orig = low # trunc to precision to avoid points out of space
self.high_orig = high self.low_orig = round(_low * pow(0.1, self.decimals), self.decimals)
self.high_orig = round(_high * pow(0.1, self.decimals), self.decimals)
super().__init__(_low, _high, prior, base, transform, name, dtype) super().__init__(_low, _high, prior, base, transform, name, dtype)

View File

@ -10,7 +10,7 @@ from typing import Any, Iterator, Optional, Sequence, Tuple, Union
with suppress(ImportError): with suppress(ImportError):
from skopt.space import Integer, Real, Categorical from skopt.space import Integer, Real, Categorical
from freqtrade.optimize.decimalspace import SKDecimal from freqtrade.optimize.space import SKDecimal
from freqtrade.exceptions import OperationalException from freqtrade.exceptions import OperationalException

View File

@ -7,7 +7,7 @@ from typing import Any, Callable, Dict, List
import numpy as np # noqa import numpy as np # noqa
import pandas as pd # noqa import pandas as pd # noqa
from pandas import DataFrame from pandas import DataFrame
from skopt.space import Categorical, Dimension, Integer, Real # noqa from freqtrade.optimize.space import Categorical, Dimension, Integer, SKDecimal, Real # noqa
from freqtrade.optimize.hyperopt_interface import IHyperOpt from freqtrade.optimize.hyperopt_interface import IHyperOpt
@ -223,9 +223,9 @@ class AdvancedSampleHyperOpt(IHyperOpt):
Integer(10, 120, name='roi_t1'), Integer(10, 120, name='roi_t1'),
Integer(10, 60, name='roi_t2'), Integer(10, 60, name='roi_t2'),
Integer(10, 40, name='roi_t3'), Integer(10, 40, name='roi_t3'),
Real(0.01, 0.04, name='roi_p1'), SKDecimal(0.01, 0.04, decimals=3, name='roi_p1'),
Real(0.01, 0.07, name='roi_p2'), SKDecimal(0.01, 0.07, decimals=3, name='roi_p2'),
Real(0.01, 0.20, name='roi_p3'), SKDecimal(0.01, 0.20, decimals=3, name='roi_p3'),
] ]
@staticmethod @staticmethod
@ -237,7 +237,7 @@ class AdvancedSampleHyperOpt(IHyperOpt):
'stoploss' optimization hyperspace. 'stoploss' optimization hyperspace.
""" """
return [ return [
Real(-0.35, -0.02, name='stoploss'), SKDecimal(-0.35, -0.02, decimals=3, name='stoploss'),
] ]
@staticmethod @staticmethod
@ -256,14 +256,14 @@ class AdvancedSampleHyperOpt(IHyperOpt):
# other 'trailing' hyperspace parameters. # other 'trailing' hyperspace parameters.
Categorical([True], name='trailing_stop'), Categorical([True], name='trailing_stop'),
Real(0.01, 0.35, name='trailing_stop_positive'), SKDecimal(0.01, 0.35, decimals=3, name='trailing_stop_positive'),
# 'trailing_stop_positive_offset' should be greater than 'trailing_stop_positive', # 'trailing_stop_positive_offset' should be greater than 'trailing_stop_positive',
# so this intermediate parameter is used as the value of the difference between # so this intermediate parameter is used as the value of the difference between
# them. The value of the 'trailing_stop_positive_offset' is constructed in the # them. The value of the 'trailing_stop_positive_offset' is constructed in the
# generate_trailing_params() method. # generate_trailing_params() method.
# This is similar to the hyperspace dimensions used for constructing the ROI tables. # This is similar to the hyperspace dimensions used for constructing the ROI tables.
Real(0.001, 0.1, name='trailing_stop_positive_offset_p1'), SKDecimal(0.001, 0.1, decimals=3, name='trailing_stop_positive_offset_p1'),
Categorical([True, False], name='trailing_only_offset_is_reached'), Categorical([True, False], name='trailing_only_offset_is_reached'),
] ]

View File

@ -15,10 +15,10 @@ from filelock import Timeout
from freqtrade.commands.optimize_commands import setup_optimize_configuration, start_hyperopt from freqtrade.commands.optimize_commands import setup_optimize_configuration, start_hyperopt
from freqtrade.data.history import load_data from freqtrade.data.history import load_data
from freqtrade.exceptions import OperationalException from freqtrade.exceptions import OperationalException
from freqtrade.optimize.decimalspace import SKDecimal
from freqtrade.optimize.hyperopt import Hyperopt from freqtrade.optimize.hyperopt import Hyperopt
from freqtrade.optimize.hyperopt_auto import HyperOptAuto from freqtrade.optimize.hyperopt_auto import HyperOptAuto
from freqtrade.optimize.hyperopt_tools import HyperoptTools from freqtrade.optimize.hyperopt_tools import HyperoptTools
from freqtrade.optimize.space import SKDecimal
from freqtrade.resolvers.hyperopt_resolver import HyperOptResolver from freqtrade.resolvers.hyperopt_resolver import HyperOptResolver
from freqtrade.state import RunMode from freqtrade.state import RunMode
from tests.conftest import (get_args, log_has, log_has_re, patch_exchange, from tests.conftest import (get_args, log_has, log_has_re, patch_exchange,