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