From 3686efa08a168ded9f801972840255402616a83a Mon Sep 17 00:00:00 2001 From: Rokas Kupstys Date: Sat, 3 Jul 2021 10:08:52 +0300 Subject: [PATCH] Add range property to CategoricalParameter and DecimalParameter, add their tests. At the moment we can keep a single code path when using IntParameter, but we have to make a special hyperopt case for CategoricalParameter/DecimalParameter. Range property solves this. --- docs/hyperopt.md | 3 +++ freqtrade/strategy/hyper.py | 28 ++++++++++++++++++++++++++++ tests/strategy/test_interface.py | 25 ++++++++++++++++++++----- 3 files changed, 51 insertions(+), 5 deletions(-) diff --git a/docs/hyperopt.md b/docs/hyperopt.md index a117ac1ce..5ce0d3813 100644 --- a/docs/hyperopt.md +++ b/docs/hyperopt.md @@ -403,6 +403,9 @@ While this strategy is most likely too simple to provide consistent profit, it s !!! Note `self.buy_ema_short.range` will act differently between hyperopt and other modes. For hyperopt, the above example may generate 48 new columns, however for all other modes (backtesting, dry/live), it will only generate the column for the selected value. You should therefore avoid using the resulting column with explicit values (values other than `self.buy_ema_short.value`). +!!! Note + `range` property may also be used with `DecimalParameter` and `CategoricalParameter`. `RealParameter` does not provide this property due to infinite search space. + ??? Hint "Performance tip" By doing the calculation of all possible indicators in `populate_indicators()`, the calculation of the indicator happens only once for every parameter. While this may slow down the hyperopt startup speed, the overall performance will increase as the Hyperopt execution itself may pick the same value for multiple epochs (changing other values). diff --git a/freqtrade/strategy/hyper.py b/freqtrade/strategy/hyper.py index 21a806202..36a3b0600 100644 --- a/freqtrade/strategy/hyper.py +++ b/freqtrade/strategy/hyper.py @@ -205,6 +205,21 @@ class DecimalParameter(NumericParameter): return SKDecimal(low=self.low, high=self.high, decimals=self._decimals, name=name, **self._space_params) + @property + def range(self): + """ + Get each value in this space as list. + Returns a List from low to high (inclusive) in Hyperopt mode. + Returns a List with 1 item (`value`) in "non-hyperopt" mode, to avoid + calculating 100ds of indicators. + """ + if self.in_space and self.optimize: + low = int(self.low * pow(10, self._decimals)) + high = int(self.high * pow(10, self._decimals)) + 1 + return [round(n * pow(0.1, self._decimals), self._decimals) for n in range(low, high)] + else: + return [self.value] + class CategoricalParameter(BaseParameter): default: Any @@ -239,6 +254,19 @@ class CategoricalParameter(BaseParameter): """ return Categorical(self.opt_range, name=name, **self._space_params) + @property + def range(self): + """ + Get each value in this space as list. + Returns a List of categories in Hyperopt mode. + Returns a List with 1 item (`value`) in "non-hyperopt" mode, to avoid + calculating 100ds of indicators. + """ + if self.in_space and self.optimize: + return self.opt_range + else: + return [self.value] + class HyperStrategyMixin(object): """ diff --git a/tests/strategy/test_interface.py b/tests/strategy/test_interface.py index 04d12a51f..2beb4465d 100644 --- a/tests/strategy/test_interface.py +++ b/tests/strategy/test_interface.py @@ -12,6 +12,7 @@ from freqtrade.data.dataprovider import DataProvider from freqtrade.data.history import load_data from freqtrade.enums import SellType from freqtrade.exceptions import OperationalException, StrategyError +from freqtrade.optimize.space import SKDecimal from freqtrade.persistence import PairLocks, Trade from freqtrade.resolvers import StrategyResolver from freqtrade.strategy.hyper import (BaseParameter, CategoricalParameter, DecimalParameter, @@ -657,17 +658,31 @@ def test_hyperopt_parameters(): assert list(intpar.range) == [0, 1, 2, 3, 4, 5] fltpar = RealParameter(low=0.0, high=5.5, default=1.0, space='buy') + assert fltpar.value == 1 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 = DecimalParameter(low=0.0, high=0.5, default=0.14, decimals=1, space='buy') + assert fltpar.value == 0.1 + assert isinstance(fltpar.get_space(''), SKDecimal) + assert isinstance(fltpar.range, list) + assert len(list(fltpar.range)) == 1 + # Range contains ONLY the default / value. + assert list(fltpar.range) == [fltpar.value] + fltpar.in_space = True + assert len(list(fltpar.range)) == 6 + assert list(fltpar.range) == [0.0, 0.1, 0.2, 0.3, 0.4, 0.5] catpar = CategoricalParameter(['buy_rsi', 'buy_macd', 'buy_none'], default='buy_macd', space='buy') - assert isinstance(catpar.get_space(''), Categorical) assert catpar.value == 'buy_macd' + assert isinstance(catpar.get_space(''), Categorical) + assert isinstance(catpar.range, list) + assert len(list(catpar.range)) == 1 + # Range contains ONLY the default / value. + assert list(catpar.range) == [catpar.value] + catpar.in_space = True + assert len(list(catpar.range)) == 3 + assert list(catpar.range) == ['buy_rsi', 'buy_macd', 'buy_none'] def test_auto_hyperopt_interface(default_conf):