Implement DecimalParameter and rename FloatParameter to RealParameter.

This commit is contained in:
Rokas Kupstys 2021-04-01 10:17:39 +03:00
parent d64295ba24
commit ea43d5ba85
5 changed files with 88 additions and 20 deletions

View File

@ -26,7 +26,8 @@ class HyperOptAuto(IHyperOpt):
def populate_buy_trend(dataframe: DataFrame, metadata: dict): def populate_buy_trend(dataframe: DataFrame, metadata: dict):
for attr_name, attr in self.strategy.enumerate_parameters('buy'): for attr_name, attr in self.strategy.enumerate_parameters('buy'):
if attr.optimize: if attr.optimize:
attr.value = params[attr_name] # noinspection PyProtectedMember
attr._set_value(params[attr_name])
return self.strategy.populate_buy_trend(dataframe, metadata) return self.strategy.populate_buy_trend(dataframe, metadata)
return populate_buy_trend return populate_buy_trend
@ -35,7 +36,8 @@ class HyperOptAuto(IHyperOpt):
def populate_buy_trend(dataframe: DataFrame, metadata: dict): def populate_buy_trend(dataframe: DataFrame, metadata: dict):
for attr_name, attr in self.strategy.enumerate_parameters('sell'): for attr_name, attr in self.strategy.enumerate_parameters('sell'):
if attr.optimize: if attr.optimize:
attr.value = params[attr_name] # noinspection PyProtectedMember
attr._set_value(params[attr_name])
return self.strategy.populate_sell_trend(dataframe, metadata) return self.strategy.populate_sell_trend(dataframe, metadata)
return populate_buy_trend return populate_buy_trend

View File

@ -1,6 +1,7 @@
# flake8: noqa: F401 # flake8: noqa: F401
from freqtrade.exchange import (timeframe_to_minutes, timeframe_to_msecs, timeframe_to_next_date, from freqtrade.exchange import (timeframe_to_minutes, timeframe_to_msecs, timeframe_to_next_date,
timeframe_to_prev_date, timeframe_to_seconds) timeframe_to_prev_date, timeframe_to_seconds)
from freqtrade.strategy.hyper import CategoricalParameter, FloatParameter, IntParameter from freqtrade.strategy.hyper import (CategoricalParameter, DecimalParameter, IntParameter,
RealParameter)
from freqtrade.strategy.interface import IStrategy from freqtrade.strategy.interface import IStrategy
from freqtrade.strategy.strategy_helper import merge_informative_pair, stoploss_from_open from freqtrade.strategy.strategy_helper import merge_informative_pair, stoploss_from_open

View File

@ -56,6 +56,14 @@ class BaseParameter(ABC):
Get-space - will be used by Hyperopt to get the hyperopt Space Get-space - will be used by Hyperopt to get the hyperopt Space
""" """
def _set_value(self, value: Any):
"""
Update current value. Used by hyperopt functions for the purpose where optimization and
value spaces differ.
:param value: A numerical value.
"""
self.value = value
class IntParameter(BaseParameter): class IntParameter(BaseParameter):
default: int default: int
@ -65,7 +73,7 @@ class IntParameter(BaseParameter):
def __init__(self, low: Union[int, Sequence[int]], high: Optional[int] = None, *, default: int, def __init__(self, low: Union[int, Sequence[int]], high: Optional[int] = None, *, default: int,
space: Optional[str] = None, optimize: bool = True, load: bool = True, **kwargs): space: Optional[str] = None, optimize: bool = True, load: bool = True, **kwargs):
""" """
Initialize hyperopt-optimizable parameter. Initialize hyperopt-optimizable integer parameter.
:param low: Lower end (inclusive) of optimization space or [low, high]. :param low: Lower end (inclusive) of optimization space or [low, high].
:param high: Upper end (inclusive) of optimization space. :param high: Upper end (inclusive) of optimization space.
Must be none of entire range is passed first parameter. Must be none of entire range is passed first parameter.
@ -95,16 +103,16 @@ class IntParameter(BaseParameter):
return Integer(*self.opt_range, name=name, **self._space_params) return Integer(*self.opt_range, name=name, **self._space_params)
class FloatParameter(BaseParameter): class RealParameter(BaseParameter):
default: float default: float
value: float value: float
opt_range: Sequence[float] opt_range: Sequence[float]
def __init__(self, low: Union[float, Sequence[float]], high: Optional[float] = None, *, def __init__(self, low: Union[float, Sequence[float]], high: Optional[float] = None, *,
default: float, space: Optional[str] = None, default: float, space: Optional[str] = None, optimize: bool = True,
optimize: bool = True, load: bool = True, **kwargs): load: bool = True, **kwargs):
""" """
Initialize hyperopt-optimizable parameter. Initialize hyperopt-optimizable floating point parameter with unlimited precision.
:param low: Lower end (inclusive) of optimization space or [low, high]. :param low: Lower end (inclusive) of optimization space or [low, high].
:param high: Upper end (inclusive) of optimization space. :param high: Upper end (inclusive) of optimization space.
Must be none if entire range is passed first parameter. Must be none if entire range is passed first parameter.
@ -116,10 +124,10 @@ class FloatParameter(BaseParameter):
:param kwargs: Extra parameters to skopt.space.Real. :param kwargs: Extra parameters to skopt.space.Real.
""" """
if high is not None and isinstance(low, Sequence): if high is not None and isinstance(low, Sequence):
raise OperationalException('FloatParameter space invalid.') raise OperationalException(f'{self.__class__.__name__} space invalid.')
if high is None or isinstance(low, Sequence): if high is None or isinstance(low, Sequence):
if not isinstance(low, Sequence) or len(low) != 2: if not isinstance(low, Sequence) or len(low) != 2:
raise OperationalException('FloatParameter space must be [low, high]') raise OperationalException(f'{self.__class__.__name__} space must be [low, high]')
opt_range = low opt_range = low
else: else:
opt_range = [low, high] opt_range = [low, high]
@ -134,6 +142,50 @@ class FloatParameter(BaseParameter):
return Real(*self.opt_range, name=name, **self._space_params) return Real(*self.opt_range, name=name, **self._space_params)
class DecimalParameter(RealParameter):
default: float
value: float
opt_range: Sequence[float]
def __init__(self, low: Union[float, Sequence[float]], high: Optional[float] = None, *,
default: float, decimals: int = 3, space: Optional[str] = None,
optimize: bool = True, load: bool = True, **kwargs):
"""
Initialize hyperopt-optimizable decimal parameter with a limited precision.
:param low: Lower end (inclusive) of optimization space or [low, high].
:param high: Upper end (inclusive) of optimization space.
Must be none if entire range is passed first parameter.
:param default: A default value.
:param decimals: A number of decimals after floating point to be included in testing.
:param space: A parameter category. Can be 'buy' or 'sell'. This parameter is optional if
parameter fieldname is prefixed with 'buy_' or 'sell_'.
:param optimize: Include parameter in hyperopt optimizations.
:param load: Load parameter value from {space}_params.
:param kwargs: Extra parameters to skopt.space.Real.
"""
self._decimals = decimals
default = round(default, self._decimals)
super().__init__(low=low, high=high, default=default, space=space, optimize=optimize,
load=load, **kwargs)
def get_space(self, name: str) -> 'Integer':
"""
Create skopt optimization space.
:param name: A name of parameter field.
"""
low = int(self.opt_range[0] * pow(10, self._decimals))
high = int(self.opt_range[1] * pow(10, self._decimals))
return Integer(low, high, name=name, **self._space_params)
def _set_value(self, value: int):
"""
Update current value. Used by hyperopt functions for the purpose where optimization and
value spaces differ.
:param value: An integer value.
"""
self.value = round(value * pow(0.1, self._decimals), self._decimals)
class CategoricalParameter(BaseParameter): class CategoricalParameter(BaseParameter):
default: Any default: Any
value: Any value: Any

View File

@ -4,7 +4,7 @@ import talib.abstract as ta
from pandas import DataFrame from pandas import DataFrame
import freqtrade.vendor.qtpylib.indicators as qtpylib import freqtrade.vendor.qtpylib.indicators as qtpylib
from freqtrade.strategy import FloatParameter, IntParameter, IStrategy from freqtrade.strategy import DecimalParameter, IntParameter, IStrategy, RealParameter
class HyperoptableStrategy(IStrategy): class HyperoptableStrategy(IStrategy):
@ -60,9 +60,10 @@ class HyperoptableStrategy(IStrategy):
} }
buy_rsi = IntParameter([0, 50], default=30, space='buy') buy_rsi = IntParameter([0, 50], default=30, space='buy')
buy_plusdi = FloatParameter(low=0, high=1, default=0.5, space='buy') buy_plusdi = RealParameter(low=0, high=1, default=0.5, space='buy')
sell_rsi = IntParameter(low=50, high=100, default=70, space='sell') sell_rsi = IntParameter(low=50, high=100, default=70, space='sell')
sell_minusdi = FloatParameter(low=0, high=1, default=0.5, space='sell', load=False) sell_minusdi = DecimalParameter(low=0, high=1, default=0.5001, decimals=3, space='sell',
load=False)
def informative_pairs(self): def informative_pairs(self):
""" """

View File

@ -13,8 +13,8 @@ from freqtrade.data.history import load_data
from freqtrade.exceptions import OperationalException, StrategyError from freqtrade.exceptions import OperationalException, StrategyError
from freqtrade.persistence import PairLocks, Trade from freqtrade.persistence import PairLocks, Trade
from freqtrade.resolvers import StrategyResolver from freqtrade.resolvers import StrategyResolver
from freqtrade.strategy.hyper import (BaseParameter, CategoricalParameter, FloatParameter, from freqtrade.strategy.hyper import (BaseParameter, CategoricalParameter, DecimalParameter,
IntParameter) IntParameter, RealParameter)
from freqtrade.strategy.interface import SellCheckTuple, SellType from freqtrade.strategy.interface import SellCheckTuple, SellType
from freqtrade.strategy.strategy_wrapper import strategy_safe_wrapper from freqtrade.strategy.strategy_wrapper import strategy_safe_wrapper
from tests.conftest import log_has, log_has_re from tests.conftest import log_has, log_has_re
@ -564,14 +564,20 @@ def test_hyperopt_parameters():
with pytest.raises(OperationalException, match=r"IntParameter space must be.*"): with pytest.raises(OperationalException, match=r"IntParameter space must be.*"):
IntParameter(low=0, default=5, space='buy') IntParameter(low=0, default=5, space='buy')
with pytest.raises(OperationalException, match=r"FloatParameter space must be.*"): with pytest.raises(OperationalException, match=r"RealParameter space must be.*"):
FloatParameter(low=0, default=5, space='buy') RealParameter(low=0, default=5, space='buy')
with pytest.raises(OperationalException, match=r"DecimalParameter space must be.*"):
DecimalParameter(low=0, default=5, space='buy')
with pytest.raises(OperationalException, match=r"IntParameter space invalid\."): with pytest.raises(OperationalException, match=r"IntParameter space invalid\."):
IntParameter([0, 10], high=7, default=5, space='buy') IntParameter([0, 10], high=7, default=5, space='buy')
with pytest.raises(OperationalException, match=r"FloatParameter space invalid\."): with pytest.raises(OperationalException, match=r"RealParameter space invalid\."):
FloatParameter([0, 10], high=7, default=5, space='buy') RealParameter([0, 10], high=7, default=5, space='buy')
with pytest.raises(OperationalException, match=r"DecimalParameter space invalid\."):
DecimalParameter([0, 10], high=7, default=5, space='buy')
with pytest.raises(OperationalException, match=r"CategoricalParameter space must.*"): with pytest.raises(OperationalException, match=r"CategoricalParameter space must.*"):
CategoricalParameter(['aa'], default='aa', space='buy') CategoricalParameter(['aa'], default='aa', space='buy')
@ -583,10 +589,16 @@ def test_hyperopt_parameters():
assert intpar.value == 1 assert intpar.value == 1
assert isinstance(intpar.get_space(''), Integer) assert isinstance(intpar.get_space(''), Integer)
fltpar = FloatParameter(low=0.0, high=5.5, default=1.0, space='buy') fltpar = RealParameter(low=0.0, high=5.5, default=1.0, space='buy')
assert isinstance(fltpar.get_space(''), Real) assert isinstance(fltpar.get_space(''), Real)
assert fltpar.value == 1 assert fltpar.value == 1
fltpar = DecimalParameter(low=0.0, high=5.5, default=1.0004, decimals=3, space='buy')
assert isinstance(fltpar.get_space(''), Integer)
assert fltpar.value == 1
fltpar._set_value(2222)
assert fltpar.value == 2.222
catpar = CategoricalParameter(['buy_rsi', 'buy_macd', 'buy_none'], catpar = CategoricalParameter(['buy_rsi', 'buy_macd', 'buy_none'],
default='buy_macd', space='buy') default='buy_macd', space='buy')
assert isinstance(catpar.get_space(''), Categorical) assert isinstance(catpar.get_space(''), Categorical)