Merge pull request #2137 from hroff-1902/hyperopt-adaptive-roi-space
Hyperopt: adaptive roi_space
This commit is contained in:
commit
7af445adf3
@ -389,18 +389,20 @@ minimal_roi = {
|
|||||||
}
|
}
|
||||||
```
|
```
|
||||||
|
|
||||||
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) with the values that can vary in the following ranges:
|
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 ticker_interval used. By default the values can vary in the following ranges (for some of the most used ticker intervals, values are rounded to 5 digits after the decimal point):
|
||||||
|
|
||||||
| # | minutes | ROI percentage |
|
| # step <td colspan=2> 1m <td colspan=2> 5m <td colspan=2> 1h <td colspan=2> 1d |
|
||||||
|---|---|---|
|
|---|---|---|---|---|---|---|---|---|
|
||||||
| 1 | always 0 | 0.03...0.31 |
|
| 1 | 0 | 0.01161...0.11992 | 0 | 0.03...0.31 | 0 | 0.06883...0.71124 | 0 | 0.12178...1.25835 |
|
||||||
| 2 | 10...40 | 0.02...0.11 |
|
| 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 |
|
||||||
| 3 | 20...100 | 0.01...0.04 |
|
| 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 |
|
||||||
| 4 | 30...220 | always 0 |
|
| 4 | 6...44 | 0.0 | 30...220 | 0.0 | 360...2640 | 0.0 | 8640...63360 | 0.0 |
|
||||||
|
|
||||||
This structure of the ROI table is sufficient in most cases. Override the `roi_space()` method defining the ranges desired if you need components of the ROI tables to vary in other ranges.
|
These ranges should be sufficient in most cases. The minutes in the steps (ROI dict keys) are scaled linearly depending on the ticker interval used. The ROI values in the steps (ROI dict values) are scaled logarithmically depending on the ticker interval used.
|
||||||
|
|
||||||
Override the `generate_roi_table()` and `roi_space()` methods and implement your own custom approach for generation of the ROI tables during hyperoptimization in these methods if you need a different structure of the ROI table or other amount of rows (steps) in the ROI tables.
|
If you have the `generate_roi_table()` and `roi_space()` methods in your custom hyperopt file, remove them in order to utilize these adaptive ROI tables and the ROI hyperoptimization space generated by Freqtrade by default.
|
||||||
|
|
||||||
|
Override the `roi_space()` method if you need components of the ROI tables to vary in other ranges. Override the `generate_roi_table()` and `roi_space()` methods and implement your own custom approach for generation of the ROI tables during hyperoptimization if you need a different structure of the ROI tables or other amount of rows (steps). A sample for these methods can be found in [user_data/hyperopts/sample_hyperopt_advanced.py](https://github.com/freqtrade/freqtrade/blob/develop/user_data/hyperopts/sample_hyperopt_advanced.py).
|
||||||
|
|
||||||
### Understand Hyperopt Stoploss results
|
### Understand Hyperopt Stoploss results
|
||||||
|
|
||||||
@ -422,7 +424,9 @@ Stoploss: -0.37996664668703606
|
|||||||
|
|
||||||
If you are optimizing stoploss values, Freqtrade creates the 'stoploss' optimization hyperspace for you. By default, the stoploss values in that hyperspace can vary in the range -0.5...-0.02, which is sufficient in most cases.
|
If you are optimizing stoploss values, Freqtrade creates the 'stoploss' optimization hyperspace for you. By default, the stoploss values in that hyperspace can vary in the range -0.5...-0.02, which is sufficient in most cases.
|
||||||
|
|
||||||
Override the `stoploss_space()` method and define the desired range in it if you need stoploss values to vary in other range during hyperoptimization.
|
If you have the `stoploss_space()` method in your custom hyperopt file, remove it in order to utilize Stoploss hyperoptimization space generated by Freqtrade by default.
|
||||||
|
|
||||||
|
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/user_data/hyperopts/sample_hyperopt_advanced.py).
|
||||||
|
|
||||||
### Validate backtesting results
|
### Validate backtesting results
|
||||||
|
|
||||||
|
@ -114,3 +114,10 @@ def deep_merge_dicts(source, destination):
|
|||||||
destination[key] = value
|
destination[key] = value
|
||||||
|
|
||||||
return destination
|
return destination
|
||||||
|
|
||||||
|
|
||||||
|
def round_dict(d, n):
|
||||||
|
"""
|
||||||
|
Rounds float values in the dict to n digits after the decimal point.
|
||||||
|
"""
|
||||||
|
return {k: (round(v, n) if isinstance(v, float) else v) for k, v in d.items()}
|
||||||
|
@ -24,8 +24,10 @@ from skopt.space import Dimension
|
|||||||
|
|
||||||
from freqtrade.configuration import TimeRange
|
from freqtrade.configuration import TimeRange
|
||||||
from freqtrade.data.history import load_data, get_timeframe
|
from freqtrade.data.history import load_data, get_timeframe
|
||||||
|
from freqtrade.misc import round_dict
|
||||||
from freqtrade.optimize.backtesting import Backtesting
|
from freqtrade.optimize.backtesting import Backtesting
|
||||||
# Import IHyperOptLoss to allow users import from this file
|
# Import IHyperOpt and IHyperOptLoss to allow unpickling classes from these modules
|
||||||
|
from freqtrade.optimize.hyperopt_interface import IHyperOpt # noqa: F4
|
||||||
from freqtrade.optimize.hyperopt_loss_interface import IHyperOptLoss # noqa: F4
|
from freqtrade.optimize.hyperopt_loss_interface import IHyperOptLoss # noqa: F4
|
||||||
from freqtrade.resolvers.hyperopt_resolver import HyperOptResolver, HyperOptLossResolver
|
from freqtrade.resolvers.hyperopt_resolver import HyperOptResolver, HyperOptLossResolver
|
||||||
|
|
||||||
@ -178,9 +180,11 @@ class Hyperopt:
|
|||||||
indent=4)
|
indent=4)
|
||||||
if self.has_space('roi'):
|
if self.has_space('roi'):
|
||||||
print("ROI table:")
|
print("ROI table:")
|
||||||
pprint(self.custom_hyperopt.generate_roi_table(params), indent=4)
|
# Round printed values to 5 digits after the decimal point
|
||||||
|
pprint(round_dict(self.custom_hyperopt.generate_roi_table(params), 5), indent=4)
|
||||||
if self.has_space('stoploss'):
|
if self.has_space('stoploss'):
|
||||||
print(f"Stoploss: {params.get('stoploss')}")
|
# Also round to 5 digits after the decimal point
|
||||||
|
print(f"Stoploss: {round(params.get('stoploss'), 5)}")
|
||||||
|
|
||||||
def log_results(self, results) -> None:
|
def log_results(self, results) -> None:
|
||||||
"""
|
"""
|
||||||
|
@ -2,6 +2,8 @@
|
|||||||
IHyperOpt interface
|
IHyperOpt interface
|
||||||
This module defines the interface to apply for hyperopts
|
This module defines the interface to apply for hyperopts
|
||||||
"""
|
"""
|
||||||
|
import logging
|
||||||
|
import math
|
||||||
|
|
||||||
from abc import ABC, abstractmethod
|
from abc import ABC, abstractmethod
|
||||||
from typing import Dict, Any, Callable, List
|
from typing import Dict, Any, Callable, List
|
||||||
@ -9,15 +11,19 @@ from typing import Dict, Any, Callable, List
|
|||||||
from pandas import DataFrame
|
from pandas import DataFrame
|
||||||
from skopt.space import Dimension, Integer, Real
|
from skopt.space import Dimension, Integer, Real
|
||||||
|
|
||||||
|
from freqtrade.exchange import timeframe_to_minutes
|
||||||
|
from freqtrade.misc import round_dict
|
||||||
|
|
||||||
|
|
||||||
|
logger = logging.getLogger(__name__)
|
||||||
|
|
||||||
|
|
||||||
class IHyperOpt(ABC):
|
class IHyperOpt(ABC):
|
||||||
"""
|
"""
|
||||||
Interface for freqtrade hyperopts
|
Interface for freqtrade hyperopts
|
||||||
Defines the mandatory structure must follow any custom strategies
|
Defines the mandatory structure must follow any custom hyperopts
|
||||||
|
|
||||||
Attributes you can use:
|
Class attributes you can use:
|
||||||
minimal_roi -> Dict: Minimal ROI designed for the strategy
|
|
||||||
stoploss -> float: optimal stoploss designed for the strategy
|
|
||||||
ticker_interval -> int: value of the ticker interval to use for the strategy
|
ticker_interval -> int: value of the ticker interval to use for the strategy
|
||||||
"""
|
"""
|
||||||
ticker_interval: str
|
ticker_interval: str
|
||||||
@ -75,6 +81,83 @@ class IHyperOpt(ABC):
|
|||||||
|
|
||||||
return roi_table
|
return roi_table
|
||||||
|
|
||||||
|
@staticmethod
|
||||||
|
def roi_space() -> List[Dimension]:
|
||||||
|
"""
|
||||||
|
Create a ROI space.
|
||||||
|
|
||||||
|
Defines values to search for each ROI steps.
|
||||||
|
|
||||||
|
This method implements adaptive roi hyperspace with varied
|
||||||
|
ranges for parameters which automatically adapts to the
|
||||||
|
ticker interval used.
|
||||||
|
|
||||||
|
It's used by Freqtrade by default, if no custom roi_space method is defined.
|
||||||
|
"""
|
||||||
|
|
||||||
|
# Default scaling coefficients for the roi hyperspace. Can be changed
|
||||||
|
# to adjust resulting ranges of the ROI tables.
|
||||||
|
# Increase if you need wider ranges in the roi hyperspace, decrease if shorter
|
||||||
|
# ranges are needed.
|
||||||
|
roi_t_alpha = 1.0
|
||||||
|
roi_p_alpha = 1.0
|
||||||
|
|
||||||
|
ticker_interval_mins = timeframe_to_minutes(IHyperOpt.ticker_interval)
|
||||||
|
|
||||||
|
# We define here limits for the ROI space parameters automagically adapted to the
|
||||||
|
# ticker_interval used by the bot:
|
||||||
|
#
|
||||||
|
# * 'roi_t' (limits for the time intervals in the ROI tables) components
|
||||||
|
# are scaled linearly.
|
||||||
|
# * 'roi_p' (limits for the ROI value steps) components are scaled logarithmically.
|
||||||
|
#
|
||||||
|
# The scaling is designed so that it maps exactly to the legacy Freqtrade roi_space()
|
||||||
|
# method for the 5m ticker interval.
|
||||||
|
roi_t_scale = ticker_interval_mins / 5
|
||||||
|
roi_p_scale = math.log1p(ticker_interval_mins) / math.log1p(5)
|
||||||
|
roi_limits = {
|
||||||
|
'roi_t1_min': int(10 * roi_t_scale * roi_t_alpha),
|
||||||
|
'roi_t1_max': int(120 * roi_t_scale * roi_t_alpha),
|
||||||
|
'roi_t2_min': int(10 * roi_t_scale * roi_t_alpha),
|
||||||
|
'roi_t2_max': int(60 * roi_t_scale * roi_t_alpha),
|
||||||
|
'roi_t3_min': int(10 * roi_t_scale * roi_t_alpha),
|
||||||
|
'roi_t3_max': int(40 * roi_t_scale * roi_t_alpha),
|
||||||
|
'roi_p1_min': 0.01 * roi_p_scale * roi_p_alpha,
|
||||||
|
'roi_p1_max': 0.04 * roi_p_scale * roi_p_alpha,
|
||||||
|
'roi_p2_min': 0.01 * roi_p_scale * roi_p_alpha,
|
||||||
|
'roi_p2_max': 0.07 * roi_p_scale * roi_p_alpha,
|
||||||
|
'roi_p3_min': 0.01 * roi_p_scale * roi_p_alpha,
|
||||||
|
'roi_p3_max': 0.20 * roi_p_scale * roi_p_alpha,
|
||||||
|
}
|
||||||
|
logger.debug(f"Using roi space limits: {roi_limits}")
|
||||||
|
p = {
|
||||||
|
'roi_t1': roi_limits['roi_t1_min'],
|
||||||
|
'roi_t2': roi_limits['roi_t2_min'],
|
||||||
|
'roi_t3': roi_limits['roi_t3_min'],
|
||||||
|
'roi_p1': roi_limits['roi_p1_min'],
|
||||||
|
'roi_p2': roi_limits['roi_p2_min'],
|
||||||
|
'roi_p3': roi_limits['roi_p3_min'],
|
||||||
|
}
|
||||||
|
logger.info(f"Min roi table: {round_dict(IHyperOpt.generate_roi_table(p), 5)}")
|
||||||
|
p = {
|
||||||
|
'roi_t1': roi_limits['roi_t1_max'],
|
||||||
|
'roi_t2': roi_limits['roi_t2_max'],
|
||||||
|
'roi_t3': roi_limits['roi_t3_max'],
|
||||||
|
'roi_p1': roi_limits['roi_p1_max'],
|
||||||
|
'roi_p2': roi_limits['roi_p2_max'],
|
||||||
|
'roi_p3': roi_limits['roi_p3_max'],
|
||||||
|
}
|
||||||
|
logger.info(f"Max roi table: {round_dict(IHyperOpt.generate_roi_table(p), 5)}")
|
||||||
|
|
||||||
|
return [
|
||||||
|
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_t3_min'], roi_limits['roi_t3_max'], name='roi_t3'),
|
||||||
|
Real(roi_limits['roi_p1_min'], roi_limits['roi_p1_max'], name='roi_p1'),
|
||||||
|
Real(roi_limits['roi_p2_min'], roi_limits['roi_p2_max'], name='roi_p2'),
|
||||||
|
Real(roi_limits['roi_p3_min'], roi_limits['roi_p3_max'], name='roi_p3'),
|
||||||
|
]
|
||||||
|
|
||||||
@staticmethod
|
@staticmethod
|
||||||
def stoploss_space() -> List[Dimension]:
|
def stoploss_space() -> List[Dimension]:
|
||||||
"""
|
"""
|
||||||
@ -87,19 +170,14 @@ class IHyperOpt(ABC):
|
|||||||
Real(-0.5, -0.02, name='stoploss'),
|
Real(-0.5, -0.02, name='stoploss'),
|
||||||
]
|
]
|
||||||
|
|
||||||
@staticmethod
|
# This is needed for proper unpickling the class attribute ticker_interval
|
||||||
def roi_space() -> List[Dimension]:
|
# which is set to the actual value by the resolver.
|
||||||
"""
|
# Why do I still need such shamanic mantras in modern python?
|
||||||
Create a ROI space.
|
def __getstate__(self):
|
||||||
|
state = self.__dict__.copy()
|
||||||
|
state['ticker_interval'] = self.ticker_interval
|
||||||
|
return state
|
||||||
|
|
||||||
Defines values to search for each ROI steps.
|
def __setstate__(self, state):
|
||||||
You may override it in your custom Hyperopt class.
|
self.__dict__.update(state)
|
||||||
"""
|
IHyperOpt.ticker_interval = state['ticker_interval']
|
||||||
return [
|
|
||||||
Integer(10, 120, name='roi_t1'),
|
|
||||||
Integer(10, 60, name='roi_t2'),
|
|
||||||
Integer(10, 40, name='roi_t3'),
|
|
||||||
Real(0.01, 0.04, name='roi_p1'),
|
|
||||||
Real(0.01, 0.07, name='roi_p2'),
|
|
||||||
Real(0.01, 0.20, name='roi_p3'),
|
|
||||||
]
|
|
||||||
|
@ -35,7 +35,7 @@ class HyperOptResolver(IResolver):
|
|||||||
extra_dir=config.get('hyperopt_path'))
|
extra_dir=config.get('hyperopt_path'))
|
||||||
|
|
||||||
# Assign ticker_interval to be used in hyperopt
|
# Assign ticker_interval to be used in hyperopt
|
||||||
self.hyperopt.__class__.ticker_interval = str(config['ticker_interval'])
|
IHyperOpt.ticker_interval = str(config['ticker_interval'])
|
||||||
|
|
||||||
if not hasattr(self.hyperopt, 'populate_buy_trend'):
|
if not hasattr(self.hyperopt, 'populate_buy_trend'):
|
||||||
logger.warning("Custom Hyperopt does not provide populate_buy_trend. "
|
logger.warning("Custom Hyperopt does not provide populate_buy_trend. "
|
||||||
|
@ -418,7 +418,8 @@ def test_start_calls_optimizer(mocker, default_conf, caplog, capsys) -> None:
|
|||||||
|
|
||||||
parallel = mocker.patch(
|
parallel = mocker.patch(
|
||||||
'freqtrade.optimize.hyperopt.Hyperopt.run_optimizer_parallel',
|
'freqtrade.optimize.hyperopt.Hyperopt.run_optimizer_parallel',
|
||||||
MagicMock(return_value=[{'loss': 1, 'results_explanation': 'foo result', 'params': {}}])
|
MagicMock(return_value=[{'loss': 1, 'results_explanation': 'foo result',
|
||||||
|
'params': {'buy': {}, 'sell': {}, 'roi': {}, 'stoploss': 0.0}}])
|
||||||
)
|
)
|
||||||
patch_exchange(mocker)
|
patch_exchange(mocker)
|
||||||
|
|
||||||
|
Loading…
Reference in New Issue
Block a user