Add trailing stoploss hyperspace
This commit is contained in:
parent
ad2289c34c
commit
f90676cfc5
@ -174,12 +174,11 @@ AVAILABLE_CLI_OPTIONS = {
|
||||
default=constants.HYPEROPT_EPOCH,
|
||||
),
|
||||
"spaces": Arg(
|
||||
'-s', '--spaces',
|
||||
help='Specify which parameters to hyperopt. Space-separated list. '
|
||||
'Default: `%(default)s`.',
|
||||
choices=['all', 'buy', 'sell', 'roi', 'stoploss'],
|
||||
'--spaces',
|
||||
help='Specify which parameters to hyperopt. Space-separated list.',
|
||||
choices=['all', 'buy', 'sell', 'roi', 'stoploss', 'trailing', 'default'],
|
||||
nargs='+',
|
||||
default='all',
|
||||
default='default',
|
||||
),
|
||||
"print_all": Arg(
|
||||
'--print-all',
|
||||
|
@ -149,7 +149,7 @@ class Hyperopt:
|
||||
self.trials_file.unlink()
|
||||
return trials
|
||||
|
||||
def log_trials_result(self) -> None:
|
||||
def log_trials_result(self) -> None: # noqa: C901
|
||||
"""
|
||||
Display Best hyperopt result
|
||||
"""
|
||||
@ -161,14 +161,16 @@ class Hyperopt:
|
||||
|
||||
if self.config.get('print_json'):
|
||||
result_dict: Dict = {}
|
||||
|
||||
if self.has_space('buy') or self.has_space('sell'):
|
||||
result_dict['params'] = {}
|
||||
|
||||
if self.has_space('buy'):
|
||||
result_dict['params'].update({p.name: params.get(p.name)
|
||||
for p in self.hyperopt_space('buy')})
|
||||
result_dict['params'].update(self.space_params(params, 'buy'))
|
||||
|
||||
if self.has_space('sell'):
|
||||
result_dict['params'].update({p.name: params.get(p.name)
|
||||
for p in self.hyperopt_space('sell')})
|
||||
result_dict['params'].update(self.space_params(params, 'sell'))
|
||||
|
||||
if self.has_space('roi'):
|
||||
# Convert keys in min_roi dict to strings because
|
||||
# rapidjson cannot dump dicts with integer keys...
|
||||
@ -177,25 +179,35 @@ class Hyperopt:
|
||||
result_dict['minimal_roi'] = OrderedDict(
|
||||
(str(k), v) for k, v in self.custom_hyperopt.generate_roi_table(params).items()
|
||||
)
|
||||
|
||||
if self.has_space('stoploss'):
|
||||
result_dict['stoploss'] = params.get('stoploss')
|
||||
result_dict.update(self.space_params(params, 'stoploss'))
|
||||
|
||||
if self.has_space('trailing'):
|
||||
result_dict.update(self.space_params(params, 'trailing'))
|
||||
|
||||
print(rapidjson.dumps(result_dict, default=str, number_mode=rapidjson.NM_NATIVE))
|
||||
else:
|
||||
if self.has_space('buy'):
|
||||
print('Buy hyperspace params:')
|
||||
pprint({p.name: params.get(p.name) for p in self.hyperopt_space('buy')},
|
||||
indent=4)
|
||||
pprint(self.space_params(params, 'buy', 5), indent=4)
|
||||
|
||||
if self.has_space('sell'):
|
||||
print('Sell hyperspace params:')
|
||||
pprint({p.name: params.get(p.name) for p in self.hyperopt_space('sell')},
|
||||
indent=4)
|
||||
pprint(self.space_params(params, 'sell', 5), indent=4)
|
||||
|
||||
if self.has_space('roi'):
|
||||
print("ROI table:")
|
||||
# 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'):
|
||||
# Also round to 5 digits after the decimal point
|
||||
print(f"Stoploss: {round(params.get('stoploss'), 5)}")
|
||||
print(f"Stoploss:")
|
||||
pprint(self.space_params(params, 'stoploss', 5), indent=4)
|
||||
|
||||
if self.has_space('trailing'):
|
||||
print('Trailing stop:')
|
||||
pprint(self.space_params(params, 'trailing', 5), indent=4)
|
||||
|
||||
def log_results(self, results) -> None:
|
||||
"""
|
||||
@ -233,9 +245,13 @@ class Hyperopt:
|
||||
|
||||
def has_space(self, space: str) -> bool:
|
||||
"""
|
||||
Tell if a space value is contained in the configuration
|
||||
Tell if the space value is contained in the configuration
|
||||
"""
|
||||
return any(s in self.config['spaces'] for s in [space, 'all'])
|
||||
# The 'trailing' space is not included in the 'default' set of spaces
|
||||
if space == 'trailing':
|
||||
return any(s in self.config['spaces'] for s in [space, 'all'])
|
||||
else:
|
||||
return any(s in self.config['spaces'] for s in [space, 'all', 'default'])
|
||||
|
||||
def hyperopt_space(self, space: Optional[str] = None) -> List[Dimension]:
|
||||
"""
|
||||
@ -245,20 +261,34 @@ class Hyperopt:
|
||||
for all hyperspaces used.
|
||||
"""
|
||||
spaces: List[Dimension] = []
|
||||
|
||||
if space == 'buy' or (space is None and self.has_space('buy')):
|
||||
logger.debug("Hyperopt has 'buy' space")
|
||||
spaces += self.custom_hyperopt.indicator_space()
|
||||
|
||||
if space == 'sell' or (space is None and self.has_space('sell')):
|
||||
logger.debug("Hyperopt has 'sell' space")
|
||||
spaces += self.custom_hyperopt.sell_indicator_space()
|
||||
|
||||
if space == 'roi' or (space is None and self.has_space('roi')):
|
||||
logger.debug("Hyperopt has 'roi' space")
|
||||
spaces += self.custom_hyperopt.roi_space()
|
||||
|
||||
if space == 'stoploss' or (space is None and self.has_space('stoploss')):
|
||||
logger.debug("Hyperopt has 'stoploss' space")
|
||||
spaces += self.custom_hyperopt.stoploss_space()
|
||||
|
||||
if space == 'trailing' or (space is None and self.has_space('trailing')):
|
||||
logger.debug("Hyperopt has 'trailing' space")
|
||||
spaces += self.custom_hyperopt.trailing_space()
|
||||
|
||||
return spaces
|
||||
|
||||
def space_params(self, params, space: str, r: int = None) -> Dict:
|
||||
d = {p.name: params.get(p.name) for p in self.hyperopt_space(space)}
|
||||
# Round floats to `r` digits after the decimal point if requested
|
||||
return round_dict(d, r) if r else d
|
||||
|
||||
def generate_optimizer(self, _params: Dict, iteration=None) -> Dict:
|
||||
"""
|
||||
Used Optimize function. Called once per epoch to optimize whatever is configured.
|
||||
@ -281,6 +311,15 @@ class Hyperopt:
|
||||
if self.has_space('stoploss'):
|
||||
self.backtesting.strategy.stoploss = params['stoploss']
|
||||
|
||||
if self.has_space('trailing'):
|
||||
self.backtesting.strategy.trailing_stop = params['trailing_stop']
|
||||
self.backtesting.strategy.trailing_stop_positive = \
|
||||
params['trailing_stop_positive']
|
||||
self.backtesting.strategy.trailing_stop_positive_offset = \
|
||||
params['trailing_stop_positive_offset']
|
||||
self.backtesting.strategy.trailing_only_offset_is_reached = \
|
||||
params['trailing_only_offset_is_reached']
|
||||
|
||||
processed = load(self.tickerdata_pickle)
|
||||
|
||||
min_date, max_date = get_timeframe(processed)
|
||||
|
@ -8,7 +8,7 @@ import math
|
||||
from abc import ABC
|
||||
from typing import Dict, Any, Callable, List
|
||||
|
||||
from skopt.space import Dimension, Integer, Real
|
||||
from skopt.space import Categorical, Dimension, Integer, Real
|
||||
|
||||
from freqtrade import OperationalException
|
||||
from freqtrade.exchange import timeframe_to_minutes
|
||||
@ -174,6 +174,20 @@ class IHyperOpt(ABC):
|
||||
Real(-0.35, -0.02, name='stoploss'),
|
||||
]
|
||||
|
||||
@staticmethod
|
||||
def trailing_space() -> List[Dimension]:
|
||||
"""
|
||||
Create a trailing stoploss space.
|
||||
|
||||
You may override it in your custom Hyperopt class.
|
||||
"""
|
||||
return [
|
||||
Categorical([True, False], name='trailing_stop'),
|
||||
Real(-0.35, -0.02, name='trailing_stop_positive'),
|
||||
Real(0.01, 0.1, name='trailing_stop_positive_offset'),
|
||||
Categorical([True, False], name='trailing_only_offset_is_reached'),
|
||||
]
|
||||
|
||||
# This is needed for proper unpickling the class attribute ticker_interval
|
||||
# which is set to the actual value by the resolver.
|
||||
# Why do I still need such shamanic mantras in modern python?
|
||||
|
@ -26,7 +26,7 @@ from tests.conftest import (get_args, log_has, log_has_re, patch_exchange,
|
||||
|
||||
@pytest.fixture(scope='function')
|
||||
def hyperopt(default_conf, mocker):
|
||||
default_conf.update({'spaces': ['all']})
|
||||
default_conf.update({'spaces': ['default']})
|
||||
patch_exchange(mocker)
|
||||
return Hyperopt(default_conf)
|
||||
|
||||
@ -108,7 +108,7 @@ def test_setup_hyperopt_configuration_with_arguments(mocker, default_conf, caplo
|
||||
'--enable-position-stacking',
|
||||
'--disable-max-market-positions',
|
||||
'--epochs', '1000',
|
||||
'--spaces', 'all',
|
||||
'--spaces', 'default',
|
||||
'--print-all'
|
||||
]
|
||||
|
||||
@ -414,7 +414,7 @@ def test_start_calls_optimizer(mocker, default_conf, caplog, capsys) -> None:
|
||||
default_conf.update({'config': 'config.json.example',
|
||||
'epochs': 1,
|
||||
'timerange': None,
|
||||
'spaces': 'all',
|
||||
'spaces': 'default',
|
||||
'hyperopt_jobs': 1, })
|
||||
|
||||
hyperopt = Hyperopt(default_conf)
|
||||
@ -463,14 +463,38 @@ def test_format_results(hyperopt):
|
||||
assert result.find('Total profit 1.00000000 EUR')
|
||||
|
||||
|
||||
def test_has_space(hyperopt):
|
||||
hyperopt.config.update({'spaces': ['buy', 'roi']})
|
||||
assert hyperopt.has_space('roi')
|
||||
assert hyperopt.has_space('buy')
|
||||
assert not hyperopt.has_space('stoploss')
|
||||
|
||||
hyperopt.config.update({'spaces': ['all']})
|
||||
assert hyperopt.has_space('buy')
|
||||
@pytest.mark.parametrize("spaces, expected_results", [
|
||||
(['buy'],
|
||||
{'buy': True, 'sell': False, 'roi': False, 'stoploss': False, 'trailing': False}),
|
||||
(['sell'],
|
||||
{'buy': False, 'sell': True, 'roi': False, 'stoploss': False, 'trailing': False}),
|
||||
(['roi'],
|
||||
{'buy': False, 'sell': False, 'roi': True, 'stoploss': False, 'trailing': False}),
|
||||
(['stoploss'],
|
||||
{'buy': False, 'sell': False, 'roi': False, 'stoploss': True, 'trailing': False}),
|
||||
(['trailing'],
|
||||
{'buy': False, 'sell': False, 'roi': False, 'stoploss': False, 'trailing': True}),
|
||||
(['buy', 'sell', 'roi', 'stoploss'],
|
||||
{'buy': True, 'sell': True, 'roi': True, 'stoploss': True, 'trailing': False}),
|
||||
(['buy', 'sell', 'roi', 'stoploss', 'trailing'],
|
||||
{'buy': True, 'sell': True, 'roi': True, 'stoploss': True, 'trailing': True}),
|
||||
(['buy', 'roi'],
|
||||
{'buy': True, 'sell': False, 'roi': True, 'stoploss': False, 'trailing': False}),
|
||||
(['all'],
|
||||
{'buy': True, 'sell': True, 'roi': True, 'stoploss': True, 'trailing': True}),
|
||||
(['default'],
|
||||
{'buy': True, 'sell': True, 'roi': True, 'stoploss': True, 'trailing': False}),
|
||||
(['default', 'trailing'],
|
||||
{'buy': True, 'sell': True, 'roi': True, 'stoploss': True, 'trailing': True}),
|
||||
(['all', 'buy'],
|
||||
{'buy': True, 'sell': True, 'roi': True, 'stoploss': True, 'trailing': True}),
|
||||
(['default', 'buy'],
|
||||
{'buy': True, 'sell': True, 'roi': True, 'stoploss': True, 'trailing': False}),
|
||||
])
|
||||
def test_has_space(hyperopt, spaces, expected_results):
|
||||
for s in ['buy', 'sell', 'roi', 'stoploss', 'trailing']:
|
||||
hyperopt.config.update({'spaces': spaces})
|
||||
assert hyperopt.has_space(s) == expected_results[s]
|
||||
|
||||
|
||||
def test_populate_indicators(hyperopt, testdatadir) -> None:
|
||||
@ -517,7 +541,7 @@ def test_buy_strategy_generator(hyperopt, testdatadir) -> None:
|
||||
def test_generate_optimizer(mocker, default_conf) -> None:
|
||||
default_conf.update({'config': 'config.json.example'})
|
||||
default_conf.update({'timerange': None})
|
||||
default_conf.update({'spaces': 'all'})
|
||||
default_conf.update({'spaces': 'default'})
|
||||
default_conf.update({'hyperopt_min_trades': 1})
|
||||
|
||||
trades = [
|
||||
@ -584,7 +608,7 @@ def test_clean_hyperopt(mocker, default_conf, caplog):
|
||||
default_conf.update({'config': 'config.json.example',
|
||||
'epochs': 1,
|
||||
'timerange': None,
|
||||
'spaces': 'all',
|
||||
'spaces': 'default',
|
||||
'hyperopt_jobs': 1,
|
||||
})
|
||||
mocker.patch("freqtrade.optimize.hyperopt.Path.is_file", MagicMock(return_value=True))
|
||||
@ -600,7 +624,7 @@ def test_continue_hyperopt(mocker, default_conf, caplog):
|
||||
default_conf.update({'config': 'config.json.example',
|
||||
'epochs': 1,
|
||||
'timerange': None,
|
||||
'spaces': 'all',
|
||||
'spaces': 'default',
|
||||
'hyperopt_jobs': 1,
|
||||
'hyperopt_continue': True
|
||||
})
|
||||
|
Loading…
Reference in New Issue
Block a user