2018-03-22 08:27:13 +00:00
|
|
|
"""
|
|
|
|
IHyperOpt interface
|
2019-11-13 08:38:06 +00:00
|
|
|
This module defines the interface to apply for hyperopt
|
2018-03-22 08:27:13 +00:00
|
|
|
"""
|
2024-05-12 15:16:02 +00:00
|
|
|
|
2019-08-14 10:25:49 +00:00
|
|
|
import logging
|
|
|
|
import math
|
2019-11-02 10:10:33 +00:00
|
|
|
from abc import ABC
|
2024-10-04 04:53:50 +00:00
|
|
|
from typing import Union
|
2018-03-22 08:27:13 +00:00
|
|
|
|
2021-09-15 19:36:53 +00:00
|
|
|
from sklearn.base import RegressorMixin
|
2021-04-14 18:34:34 +00:00
|
|
|
from skopt.space import Categorical, Dimension, Integer
|
2018-03-22 08:27:13 +00:00
|
|
|
|
2022-09-18 11:20:36 +00:00
|
|
|
from freqtrade.constants import Config
|
2019-08-14 10:25:49 +00:00
|
|
|
from freqtrade.exchange import timeframe_to_minutes
|
2019-08-20 19:17:21 +00:00
|
|
|
from freqtrade.misc import round_dict
|
2021-04-14 18:32:34 +00:00
|
|
|
from freqtrade.optimize.space import SKDecimal
|
2021-03-24 14:03:38 +00:00
|
|
|
from freqtrade.strategy import IStrategy
|
2020-09-28 17:39:41 +00:00
|
|
|
|
2021-03-27 09:40:48 +00:00
|
|
|
|
2019-08-14 10:25:49 +00:00
|
|
|
logger = logging.getLogger(__name__)
|
2019-09-03 16:54:28 +00:00
|
|
|
|
2021-09-15 19:36:53 +00:00
|
|
|
EstimatorType = Union[RegressorMixin, str]
|
|
|
|
|
2019-09-03 16:54:28 +00:00
|
|
|
|
2018-03-22 08:27:13 +00:00
|
|
|
class IHyperOpt(ABC):
|
|
|
|
"""
|
2019-11-13 08:38:06 +00:00
|
|
|
Interface for freqtrade hyperopt
|
|
|
|
Defines the mandatory structure must follow any custom hyperopt
|
2018-03-22 08:27:13 +00:00
|
|
|
|
2019-08-14 10:25:49 +00:00
|
|
|
Class attributes you can use:
|
2021-04-03 14:54:47 +00:00
|
|
|
timeframe -> int: value of the timeframe to use for the strategy
|
2018-03-22 08:27:13 +00:00
|
|
|
"""
|
2024-05-12 15:16:02 +00:00
|
|
|
|
2020-06-02 08:19:27 +00:00
|
|
|
timeframe: str
|
2021-03-24 14:03:38 +00:00
|
|
|
strategy: IStrategy
|
2018-03-22 08:27:13 +00:00
|
|
|
|
2022-09-18 11:20:36 +00:00
|
|
|
def __init__(self, config: Config) -> None:
|
2019-09-26 08:59:21 +00:00
|
|
|
self.config = config
|
|
|
|
|
2022-03-20 08:00:53 +00:00
|
|
|
# Assign timeframe to be used in hyperopt
|
2024-05-12 15:16:02 +00:00
|
|
|
IHyperOpt.timeframe = str(config["timeframe"])
|
2019-09-26 08:59:21 +00:00
|
|
|
|
2024-10-04 04:53:50 +00:00
|
|
|
def generate_estimator(self, dimensions: list[Dimension], **kwargs) -> EstimatorType:
|
2021-09-15 19:36:53 +00:00
|
|
|
"""
|
|
|
|
Return base_estimator.
|
|
|
|
Can be any of "GP", "RF", "ET", "GBRT" or an instance of a class
|
|
|
|
inheriting from RegressorMixin (from sklearn).
|
|
|
|
"""
|
2024-05-12 15:16:02 +00:00
|
|
|
return "ET"
|
2021-09-15 19:36:53 +00:00
|
|
|
|
2024-10-04 04:53:50 +00:00
|
|
|
def generate_roi_table(self, params: dict) -> dict[int, float]:
|
2018-03-22 08:27:13 +00:00
|
|
|
"""
|
2019-08-05 14:54:53 +00:00
|
|
|
Create a ROI table.
|
|
|
|
|
|
|
|
Generates the ROI table that will be used by Hyperopt.
|
|
|
|
You may override it in your custom Hyperopt class.
|
|
|
|
"""
|
|
|
|
roi_table = {}
|
2024-05-12 15:16:02 +00:00
|
|
|
roi_table[0] = params["roi_p1"] + params["roi_p2"] + params["roi_p3"]
|
|
|
|
roi_table[params["roi_t3"]] = params["roi_p1"] + params["roi_p2"]
|
|
|
|
roi_table[params["roi_t3"] + params["roi_t2"]] = params["roi_p1"]
|
|
|
|
roi_table[params["roi_t3"] + params["roi_t2"] + params["roi_t1"]] = 0
|
2019-08-05 14:54:53 +00:00
|
|
|
|
|
|
|
return roi_table
|
2018-03-22 08:27:13 +00:00
|
|
|
|
2024-10-04 04:53:50 +00:00
|
|
|
def roi_space(self) -> list[Dimension]:
|
2018-03-22 08:27:13 +00:00
|
|
|
"""
|
2019-08-14 10:25:49 +00:00
|
|
|
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
|
2021-04-03 14:54:47 +00:00
|
|
|
timeframe used.
|
2019-08-14 10:25:49 +00:00
|
|
|
|
|
|
|
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
|
|
|
|
|
2021-04-03 15:10:39 +00:00
|
|
|
timeframe_min = timeframe_to_minutes(self.timeframe)
|
2019-08-14 10:25:49 +00:00
|
|
|
|
|
|
|
# We define here limits for the ROI space parameters automagically adapted to the
|
2019-11-03 09:01:05 +00:00
|
|
|
# timeframe used by the bot:
|
2019-08-14 10:25:49 +00:00
|
|
|
#
|
|
|
|
# * '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()
|
2021-04-03 14:54:47 +00:00
|
|
|
# method for the 5m timeframe.
|
2019-12-11 06:12:37 +00:00
|
|
|
roi_t_scale = timeframe_min / 5
|
|
|
|
roi_p_scale = math.log1p(timeframe_min) / math.log1p(5)
|
2019-08-14 10:25:49 +00:00
|
|
|
roi_limits = {
|
2024-05-12 15:16:02 +00:00
|
|
|
"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,
|
2019-08-14 10:25:49 +00:00
|
|
|
}
|
2019-08-20 20:42:44 +00:00
|
|
|
logger.debug(f"Using roi space limits: {roi_limits}")
|
2019-08-14 10:25:49 +00:00
|
|
|
p = {
|
2024-05-12 15:16:02 +00:00
|
|
|
"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"],
|
2019-08-14 10:25:49 +00:00
|
|
|
}
|
2021-04-15 19:38:20 +00:00
|
|
|
logger.info(f"Min roi table: {round_dict(self.generate_roi_table(p), 3)}")
|
2019-08-14 10:25:49 +00:00
|
|
|
p = {
|
2024-05-12 15:16:02 +00:00
|
|
|
"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"],
|
2019-08-14 10:25:49 +00:00
|
|
|
}
|
2021-04-15 19:38:20 +00:00
|
|
|
logger.info(f"Max roi table: {round_dict(self.generate_roi_table(p), 3)}")
|
2019-08-05 14:54:53 +00:00
|
|
|
|
|
|
|
return [
|
2024-05-12 15:16:02 +00:00
|
|
|
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"),
|
|
|
|
SKDecimal(
|
|
|
|
roi_limits["roi_p1_min"], roi_limits["roi_p1_max"], decimals=3, name="roi_p1"
|
|
|
|
),
|
|
|
|
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"
|
|
|
|
),
|
2019-08-05 14:54:53 +00:00
|
|
|
]
|
2018-03-22 08:27:13 +00:00
|
|
|
|
2024-10-04 04:53:50 +00:00
|
|
|
def stoploss_space(self) -> list[Dimension]:
|
2018-03-22 08:27:13 +00:00
|
|
|
"""
|
2019-08-14 10:25:49 +00:00
|
|
|
Create a stoploss space.
|
2019-08-05 14:54:53 +00:00
|
|
|
|
2019-08-14 10:25:49 +00:00
|
|
|
Defines range of stoploss values to search.
|
2019-08-05 14:54:53 +00:00
|
|
|
You may override it in your custom Hyperopt class.
|
|
|
|
"""
|
|
|
|
return [
|
2024-05-12 15:16:02 +00:00
|
|
|
SKDecimal(-0.35, -0.02, decimals=3, name="stoploss"),
|
2019-08-05 14:54:53 +00:00
|
|
|
]
|
2019-08-14 10:25:49 +00:00
|
|
|
|
2024-10-04 04:53:50 +00:00
|
|
|
def generate_trailing_params(self, params: dict) -> dict:
|
2019-12-10 00:13:45 +00:00
|
|
|
"""
|
|
|
|
Create dict with trailing stop parameters.
|
|
|
|
"""
|
|
|
|
return {
|
2024-05-12 15:16:02 +00:00
|
|
|
"trailing_stop": params["trailing_stop"],
|
|
|
|
"trailing_stop_positive": params["trailing_stop_positive"],
|
|
|
|
"trailing_stop_positive_offset": (
|
|
|
|
params["trailing_stop_positive"] + params["trailing_stop_positive_offset_p1"]
|
|
|
|
),
|
|
|
|
"trailing_only_offset_is_reached": params["trailing_only_offset_is_reached"],
|
2019-12-10 00:13:45 +00:00
|
|
|
}
|
|
|
|
|
2024-10-04 04:53:50 +00:00
|
|
|
def trailing_space(self) -> list[Dimension]:
|
2019-11-07 22:55:14 +00:00
|
|
|
"""
|
|
|
|
Create a trailing stoploss space.
|
|
|
|
|
|
|
|
You may override it in your custom Hyperopt class.
|
|
|
|
"""
|
|
|
|
return [
|
2019-11-08 09:47:28 +00:00
|
|
|
# It was decided to always set trailing_stop is to True if the 'trailing' hyperspace
|
|
|
|
# is used. Otherwise hyperopt will vary other parameters that won't have effect if
|
|
|
|
# trailing_stop is set False.
|
|
|
|
# This parameter is included into the hyperspace dimensions rather than assigning
|
|
|
|
# it explicitly in the code in order to have it printed in the results along with
|
|
|
|
# other 'trailing' hyperspace parameters.
|
2024-05-12 15:16:02 +00:00
|
|
|
Categorical([True], name="trailing_stop"),
|
|
|
|
SKDecimal(0.01, 0.35, decimals=3, name="trailing_stop_positive"),
|
2019-12-10 00:13:45 +00:00
|
|
|
# 'trailing_stop_positive_offset' should be greater than 'trailing_stop_positive',
|
|
|
|
# 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
|
|
|
|
# generate_trailing_params() method.
|
2020-02-07 23:49:06 +00:00
|
|
|
# This is similar to the hyperspace dimensions used for constructing the ROI tables.
|
2024-05-12 15:16:02 +00:00
|
|
|
SKDecimal(0.001, 0.1, decimals=3, name="trailing_stop_positive_offset_p1"),
|
|
|
|
Categorical([True, False], name="trailing_only_offset_is_reached"),
|
2019-11-07 22:55:14 +00:00
|
|
|
]
|
|
|
|
|
2024-10-04 04:53:50 +00:00
|
|
|
def max_open_trades_space(self) -> list[Dimension]:
|
2023-01-04 09:34:44 +00:00
|
|
|
"""
|
|
|
|
Create a max open trades space.
|
|
|
|
|
|
|
|
You may override it in your custom Hyperopt class.
|
|
|
|
"""
|
|
|
|
return [
|
2024-05-12 15:16:02 +00:00
|
|
|
Integer(-1, 10, name="max_open_trades"),
|
2023-01-04 09:34:44 +00:00
|
|
|
]
|
|
|
|
|
2022-03-20 08:00:53 +00:00
|
|
|
# This is needed for proper unpickling the class attribute timeframe
|
2019-08-14 10:25:49 +00:00
|
|
|
# which is set to the actual value by the resolver.
|
|
|
|
# Why do I still need such shamanic mantras in modern python?
|
|
|
|
def __getstate__(self):
|
|
|
|
state = self.__dict__.copy()
|
2024-05-12 15:16:02 +00:00
|
|
|
state["timeframe"] = self.timeframe
|
2019-08-14 10:25:49 +00:00
|
|
|
return state
|
|
|
|
|
|
|
|
def __setstate__(self, state):
|
|
|
|
self.__dict__.update(state)
|
2024-05-12 15:16:02 +00:00
|
|
|
IHyperOpt.timeframe = state["timeframe"]
|