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):
for attr_name, attr in self.strategy.enumerate_parameters('buy'):
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 populate_buy_trend
@ -35,7 +36,8 @@ class HyperOptAuto(IHyperOpt):
def populate_buy_trend(dataframe: DataFrame, metadata: dict):
for attr_name, attr in self.strategy.enumerate_parameters('sell'):
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 populate_buy_trend

View File

@ -1,6 +1,7 @@
# flake8: noqa: F401
from freqtrade.exchange import (timeframe_to_minutes, timeframe_to_msecs, timeframe_to_next_date,
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.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
"""
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):
default: int
@ -65,7 +73,7 @@ class IntParameter(BaseParameter):
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):
"""
Initialize hyperopt-optimizable parameter.
Initialize hyperopt-optimizable integer parameter.
:param low: Lower end (inclusive) of optimization space or [low, high].
:param high: Upper end (inclusive) of optimization space.
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)
class FloatParameter(BaseParameter):
class RealParameter(BaseParameter):
default: float
value: float
opt_range: Sequence[float]
def __init__(self, low: Union[float, Sequence[float]], high: Optional[float] = None, *,
default: float, space: Optional[str] = None,
optimize: bool = True, load: bool = True, **kwargs):
default: float, space: Optional[str] = None, optimize: bool = True,
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 high: Upper end (inclusive) of optimization space.
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.
"""
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 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
else:
opt_range = [low, high]
@ -134,6 +142,50 @@ class FloatParameter(BaseParameter):
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):
default: Any
value: Any

View File

@ -4,7 +4,7 @@ import talib.abstract as ta
from pandas import DataFrame
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):
@ -60,9 +60,10 @@ class HyperoptableStrategy(IStrategy):
}
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_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):
"""

View File

@ -13,8 +13,8 @@ from freqtrade.data.history import load_data
from freqtrade.exceptions import OperationalException, StrategyError
from freqtrade.persistence import PairLocks, Trade
from freqtrade.resolvers import StrategyResolver
from freqtrade.strategy.hyper import (BaseParameter, CategoricalParameter, FloatParameter,
IntParameter)
from freqtrade.strategy.hyper import (BaseParameter, CategoricalParameter, DecimalParameter,
IntParameter, RealParameter)
from freqtrade.strategy.interface import SellCheckTuple, SellType
from freqtrade.strategy.strategy_wrapper import strategy_safe_wrapper
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.*"):
IntParameter(low=0, default=5, space='buy')
with pytest.raises(OperationalException, match=r"FloatParameter space must be.*"):
FloatParameter(low=0, default=5, space='buy')
with pytest.raises(OperationalException, match=r"RealParameter space must be.*"):
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\."):
IntParameter([0, 10], high=7, default=5, space='buy')
with pytest.raises(OperationalException, match=r"FloatParameter space invalid\."):
FloatParameter([0, 10], high=7, default=5, space='buy')
with pytest.raises(OperationalException, match=r"RealParameter space invalid\."):
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.*"):
CategoricalParameter(['aa'], default='aa', space='buy')
@ -583,10 +589,16 @@ def test_hyperopt_parameters():
assert intpar.value == 1
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 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'],
default='buy_macd', space='buy')
assert isinstance(catpar.get_space(''), Categorical)