Repository: freqtrade/freqtrade
Pull request: Add trailing stoploss hyperspace
Adds the new 'trailing' hyperspace for Hyperopt for optimizing trailing stoploss parameters.

Resolves #2363

* Change to the `--spaces`:
I do not think this should be in the optimization path by default. Optimization for the trailing stop parameters should better be done differently (or specified explicitly, if user wants to use it along with optimizing buy, sell, roi and stoploss). So the default value for `--spaces` was changed to `default` (new value) from `all`, and this new `trailing` hyperspace is not included into `default` set of hyperspaces.

So, use `--space all` or `--space trailing` or `--spaces defailt trailing` to switch it on.

* The `trailing_stop` setting is set to True always when the `trailing` space is specified (so it's not varied between True and False).

Usage:
```
$ freqtrade hyperopt --spaces trailing
$ freqtrade hyperopt --spaces stoploss trailing --print-json
```


It requires some additional love, so PoC for now.


TODO:
* [x] more tests
* [x] docs



Problem statement:
Hyperopt trailing stoploss hyperoptimization space
## My environment

* Operating system: Windows
* Python Version: Python 3.7.4
* Branch: freqtrade develop-7d1f66cc

### The suggestion
In hyperopt I currently can't find an easy way to hyperopt values like `trailing_stop_positive` or `trailing_stop_positive_offset`. Maybe make a way that you can easily add them like the `stoploss` value.

Hints from issue discussion:
this is probably not easy to implement...
at first look it looks rather straightforward...

it's another space - almost identical to stoploss...
could probably also be implemented into the stoploss_space() - which will make it a 8 line change (excluding tests and such).

``` python
    def stoploss_space() -> List[Dimension]:

        return [
            Real(-0.35, -0.02, name='stoploss'),
            Real(-0.35, -0.02, name='trailing_stoploss_positive'),
            Categorical([True, False], name='tsl_enabled'),
            Real(0, 0.1, name='trailing_stoploss_positive_offset'),  # (?)
            Categorical([True, False], name='tsl_positive_enabled'),
        ]
```

and in hyperopt.py:
``` python
        if self.has_space('stoploss'):
            self.backtesting.strategy.stoploss = params['stoploss']
            self.backtesting.strategy.trailing_stoploss_positive = params['trailing_stoploss_positive']
           ...

```

Now i didn't test this and it may not be THAT straightforward  ... but it's not that difficult either ... 

**edit** a few changes to `interface.py` will be needed to take settings from the strategy instead of config - but again, nothing major ...
Great,

this should probably better be a separate optimization space, to control it if I don't want to optimize it along with stoploss...

Like `--spaces stoploss trailing_stoploss` or `--spaces stoploss trailing`
As I was thinking about this, I think the pairs in your whitelist are also a BIG part of your tot profit, maybe a hyperopt function that checks your whitelist (or blacklist) and filters out the bad ones? But looking at the message above I think this one will require more programming and is not that easy to implement :3
Updated the title to reflect the content of the issue.

Commit message: Add trailing_space() into AdvancedSampleHyperOpt

Patch to review:
diff --git a/docs/hyperopt.md b/docs/hyperopt.md
index 5a3ae7e3ad2..640ea6b74db 100644
--- a/docs/hyperopt.md
+++ b/docs/hyperopt.md
@@ -46,8 +46,9 @@ Optional - can also be loaded from a strategy:
 Rarely you may also need to override:
 
 * `roi_space` - for custom ROI optimization (if you need the ranges for the ROI parameters in the optimization hyperspace that differ from default)
-* `generate_roi_table` - for custom ROI optimization (if you need more than 4 entries in the ROI table)
+* `generate_roi_table` - for custom ROI optimization (if you need more than 4 entries in the ROI table or the ranges of the values in the ROI table that differ from default)
 * `stoploss_space` - for custom stoploss optimization (if you need the range for the stoploss parameter in the optimization hyperspace that differs from default)
+* `trailing_space` - for custom trailing stop optimization (if you need the ranges for the trailing stopl parameters in the optimization hyperspace that differ from default)
 
 ### 1. Install a Custom Hyperopt File
 
@@ -253,10 +254,10 @@ freqtrade hyperopt --config config.json --hyperopt <hyperoptname> -e 5000 --spac
 
 Use  `<hyperoptname>` as the name of the custom hyperopt used.
 
-The `-e` flag will set how many evaluations hyperopt will do. We recommend
+The `-e` option will set how many evaluations hyperopt will do. We recommend
 running at least several thousand evaluations.
 
-The `--spaces all` flag determines that all possible parameters should be optimized. Possibilities are listed below.
+The `--spaces all` option determines that all possible parameters should be optimized. Possibilities are listed below.
 
 !!! Note
     By default, hyperopt will erase previous results and start from scratch. Continuation can be archived by using `--continue`.
@@ -289,7 +290,7 @@ freqtrade hyperopt --strategy SampleStrategy --customhyperopt SampleHyperopt
 
 ### Running Hyperopt with Smaller Search Space
 
-Use the `--spaces` argument to limit the search space used by hyperopt.
+Use the `--spaces` option to limit the search space used by hyperopt.
 Letting Hyperopt optimize everything is a huuuuge search space. Often it
 might make more sense to start by just searching for initial buy algorithm.
 Or maybe you just want to optimize your stoploss or roi table for that awesome
@@ -302,8 +303,12 @@ Legal values are:
 * `sell`: just search for a new sell strategy
 * `roi`: just optimize the minimal profit table for your strategy
 * `stoploss`: search for the best stoploss value
+* `trailing`: search for the best trailing stop values
+* `default`: `all` except `trailing`
 * space-separated list of any of the above values for example `--spaces roi stoploss`
 
+The default Hyperopt Search Space, used when no `--space` command line option is specified, does not include the `trailing` hyperspace. We recommend you to run optimization for the `trailing` hyperspace separately, when the best parameters for other hyperspaces were found, validated and pasted into your custom strategy.
+
 ### Position stacking and disabling max market positions
 
 In some situations, you may need to run Hyperopt (and Backtesting) with the 
@@ -377,19 +382,13 @@ You can use the `--print-all` command line option if you would like to see all r
 
 ### Understand Hyperopt ROI results
 
-If you are optimizing ROI (i.e. if optimization search-space contains 'all' or 'roi'), your result will look as follows and include a ROI table:
+If you are optimizing ROI (i.e. if optimization search-space contains 'all', 'default' or 'roi'), your result will look as follows and include a ROI table:
 
 ```
 Best result:
 
     44/100:    135 trades. Avg profit  0.57%. Total profit  0.03871918 BTC (0.7722Σ%). Avg duration 180.4 mins. Objective: 1.94367
 
-Buy hyperspace params:
-{   'adx-value': 44,
-    'rsi-value': 29,
-    'adx-enabled': False,
-    'rsi-enabled': True,
-    'trigger': 'bb_lower'}
 ROI table:
 {   0: 0.10674,
     21: 0.09158,
@@ -413,7 +412,7 @@ As stated in the comment, you can also use it as the value of the `minimal_roi`
 
 #### Default ROI Search Space
 
-If you are optimizing ROI, Freqtrade creates the 'roi' optimization hyperspace for you -- it's the hyperspace of components for the ROI tables. By default, each ROI table generated by the Freqtrade consists of 4 rows (steps). Hyperopt implements adaptive ranges for ROI tables with ranges for values in the ROI steps that depend on the ticker_interval used. By default the values can vary in the following ranges (for some of the most used ticker intervals, values are rounded to 5 digits after the decimal point):
+If you are optimizing ROI, Freqtrade creates the 'roi' optimization hyperspace for you -- it's the hyperspace of components for the ROI tables. By default, each ROI table generated by the Freqtrade consists of 4 rows (steps). Hyperopt implements adaptive ranges for ROI tables with ranges for values in the ROI steps that depend on the ticker_interval used. By default the values vary in the following ranges (for some of the most used ticker intervals, values are rounded to 5 digits after the decimal point):
 
 | # step | 1m |  | 5m |  | 1h |  | 1d |  |
 |---|---|---|---|---|---|---|---|---|
@@ -430,7 +429,7 @@ Override the `roi_space()` method if you need components of the ROI tables to va
 
 ### Understand Hyperopt Stoploss results
 
-If you are optimizing stoploss values (i.e. if optimization search-space contains 'all' or 'stoploss'), your result will look as follows and include stoploss:
+If you are optimizing stoploss values (i.e. if optimization search-space contains 'all', 'default' or 'stoploss'), your result will look as follows and include stoploss:
 
 ```
 Best result:
@@ -457,12 +456,46 @@ As stated in the comment, you can also use it as the value of the `stoploss` set
 
 #### Default Stoploss Search Space
 
-If you are optimizing stoploss values, Freqtrade creates the 'stoploss' optimization hyperspace for you. By default, the stoploss values in that hyperspace can vary in the range -0.35...-0.02, which is sufficient in most cases.
+If you are optimizing stoploss values, Freqtrade creates the 'stoploss' optimization hyperspace for you. By default, the stoploss values in that hyperspace vary in the range -0.35...-0.02, which is sufficient in most cases.
 
 If you have the `stoploss_space()` method in your custom hyperopt file, remove it in order to utilize Stoploss hyperoptimization space generated by Freqtrade by default.
 
 Override the `stoploss_space()` method and define the desired range in it if you need stoploss values to vary in other range during hyperoptimization. A sample for this method can be found in [user_data/hyperopts/sample_hyperopt_advanced.py](https://github.com/freqtrade/freqtrade/blob/develop/freqtrade/templates/sample_hyperopt_advanced.py).
 
+### Understand Hyperopt Trailing Stop results
+
+If you are optimizing trailing stop values (i.e. if optimization search-space contains 'all' or 'trailing'), your result will look as follows and include trailing stop parameters:
+
+```
+Best result:
+
+    45/100:    606 trades. Avg profit  1.04%. Total profit  0.31555614 BTC ( 630.48Σ%). Avg duration 150.3 mins. Objective: -1.10161
+
+Trailing stop:
+{   'trailing_only_offset_is_reached': True,
+    'trailing_stop': True,
+    'trailing_stop_positive': 0.02001,
+    'trailing_stop_positive_offset': 0.06038}
+```
+
+In order to use these best trailing stop parameters found by Hyperopt in backtesting and for live trades/dry-run, copy-paste them as the values of the corresponding attributes of your custom strategy:
+
+```
+    # Trailing stop
+    # These attributes will be overridden if the config file contains corresponding values.
+    trailing_stop = True
+    trailing_stop_positive = 0.02001
+    trailing_stop_positive_offset = 0.06038
+    trailing_only_offset_is_reached = True
+```
+As stated in the comment, you can also use it as the values of the corresponding settings in the configuration file.
+
+#### Default Trailing Stop Search Space
+
+If you are optimizing trailing stop values, Freqtrade creates the 'trailing' optimization hyperspace for you. By default, the `trailing_stop` parameter is always set to True in that hyperspace, the value of the `trailing_only_offset_is_reached` vary between True and False, the values of the `trailing_stop_positive` and `trailing_stop_positive_offset` parameters vary in the ranges 0.02...0.35 and 0.01...0.1 correspondingly, which is sufficient in most cases.
+
+Override the `trailing_space()` method and define the desired range in it if you need values of the trailing stop parameters to vary in other ranges during hyperoptimization. A sample for this method can be found in [user_data/hyperopts/sample_hyperopt_advanced.py](https://github.com/freqtrade/freqtrade/blob/develop/user_data/hyperopts/sample_hyperopt_advanced.py).
+
 ### Validate backtesting results
 
 Once the optimized strategy has been implemented into your strategy, you should backtest this strategy to make sure everything is working as expected.
diff --git a/freqtrade/configuration/cli_options.py b/freqtrade/configuration/cli_options.py
index 2061534e786..f84e63756ac 100644
--- a/freqtrade/configuration/cli_options.py
+++ b/freqtrade/configuration/cli_options.py
@@ -183,11 +183,10 @@ def __init__(self, *args, **kwargs):
     ),
     "spaces": Arg(
         '--spaces',
-        help='Specify which parameters to hyperopt. Space-separated list. '
-        'Default: `%(default)s`.',
-        choices=['all', 'buy', 'sell', 'roi', 'stoploss'],
+        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',
diff --git a/freqtrade/optimize/hyperopt.py b/freqtrade/optimize/hyperopt.py
index 836309a6276..d79e1336e92 100644
--- a/freqtrade/optimize/hyperopt.py
+++ b/freqtrade/optimize/hyperopt.py
@@ -156,7 +156,7 @@ def read_trials(self) -> List:
         self.trials_file.unlink()
         return trials
 
-    def log_trials_result(self) -> None:
+    def log_trials_result(self) -> None:  # noqa: C901
         """
         Display Best hyperopt result
         """
@@ -174,14 +174,16 @@ def log_trials_result(self) -> None:
 
         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...
@@ -190,25 +192,35 @@ def log_trials_result(self) -> None:
                 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 is_best(self, results) -> bool:
         return results['loss'] < self.current_best_loss
@@ -250,9 +262,13 @@ def format_results_logstring(self, results) -> str:
 
     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]:
         """
@@ -262,20 +278,34 @@ def hyperopt_space(self, space: Optional[str] = None) -> List[Dimension]:
         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.
@@ -298,6 +328,14 @@ def generate_optimizer(self, _params: Dict, iteration=None) -> Dict:
         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)
diff --git a/freqtrade/optimize/hyperopt_interface.py b/freqtrade/optimize/hyperopt_interface.py
index 2b2a81f003a..958fb8d660a 100644
--- a/freqtrade/optimize/hyperopt_interface.py
+++ b/freqtrade/optimize/hyperopt_interface.py
@@ -8,7 +8,7 @@
 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,27 @@ def stoploss_space() -> List[Dimension]:
             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 [
+            # It was decided to always set trailing_stop is to True if the 'trailing' hyperspace
+            # is used. Otherwise hyperopt will vary other parameters that won't have effect if
+            # trailing_stop is set False.
+            # This parameter is included into the hyperspace dimensions rather than assigning
+            # it explicitly in the code in order to have it printed in the results along with
+            # other 'trailing' hyperspace parameters.
+            Categorical([True], name='trailing_stop'),
+
+            Real(0.02, 0.35, 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?
diff --git a/freqtrade/templates/sample_hyperopt_advanced.py b/freqtrade/templates/sample_hyperopt_advanced.py
index 5634c21ea5e..b4bbee3fb4f 100644
--- a/freqtrade/templates/sample_hyperopt_advanced.py
+++ b/freqtrade/templates/sample_hyperopt_advanced.py
@@ -233,6 +233,27 @@ def stoploss_space() -> List[Dimension]:
             Real(-0.5, -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 [
+            # It was decided to always set trailing_stop is to True if the 'trailing' hyperspace
+            # is used. Otherwise hyperopt will vary other parameters that won't have effect if
+            # trailing_stop is set False.
+            # This parameter is included into the hyperspace dimensions rather than assigning
+            # it explicitly in the code in order to have it printed in the results along with
+            # other 'trailing' hyperspace parameters.
+            Categorical([True], name='trailing_stop'),
+
+            Real(0.02, 0.35, name='trailing_stop_positive'),
+            Real(0.01, 0.1, name='trailing_stop_positive_offset'),
+            Categorical([True, False], name='trailing_only_offset_is_reached'),
+        ]
+
     def populate_buy_trend(self, dataframe: DataFrame, metadata: dict) -> DataFrame:
         """
         Based on TA indicators.
diff --git a/tests/optimize/test_hyperopt.py b/tests/optimize/test_hyperopt.py
index d3d544502ad..cd96d0ed6ee 100644
--- a/tests/optimize/test_hyperopt.py
+++ b/tests/optimize/test_hyperopt.py
@@ -27,7 +27,7 @@
 @pytest.fixture(scope='function')
 def hyperopt(default_conf, mocker):
     default_conf.update({
-        'spaces': ['all'],
+        'spaces': ['default'],
         'hyperopt': 'DefaultHyperOpt',
     })
     patch_exchange(mocker)
@@ -113,7 +113,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'
     ]
 
@@ -440,7 +440,7 @@ def test_start_calls_optimizer(mocker, default_conf, caplog, capsys) -> None:
                          'hyperopt': 'DefaultHyperOpt',
                          'epochs': 1,
                          'timerange': None,
-                         'spaces': 'all',
+                         'spaces': 'default',
                          'hyperopt_jobs': 1, })
 
     hyperopt = Hyperopt(default_conf)
@@ -489,14 +489,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:
@@ -591,6 +615,10 @@ def test_generate_optimizer(mocker, default_conf) -> None:
         'roi_p2': 0.01,
         'roi_p3': 0.1,
         'stoploss': -0.4,
+        'trailing_stop': True,
+        'trailing_stop_positive': 0.02,
+        'trailing_stop_positive_offset': 0.1,
+        'trailing_only_offset_is_reached': False,
     }
     response_expected = {
         'loss': 1.9840569076926293,
@@ -613,7 +641,7 @@ def test_clean_hyperopt(mocker, default_conf, caplog):
                          'hyperopt': 'DefaultHyperOpt',
                          'epochs': 1,
                          'timerange': None,
-                         'spaces': 'all',
+                         'spaces': 'default',
                          'hyperopt_jobs': 1,
                          })
     mocker.patch("freqtrade.optimize.hyperopt.Path.is_file", MagicMock(return_value=True))
@@ -630,7 +658,7 @@ def test_continue_hyperopt(mocker, default_conf, caplog):
                          'hyperopt': 'DefaultHyperOpt',
                          'epochs': 1,
                          'timerange': None,
-                         'spaces': 'all',
+                         'spaces': 'default',
                          'hyperopt_jobs': 1,
                          'hyperopt_continue': True
                          })
@@ -674,6 +702,45 @@ def test_print_json_spaces_all(mocker, default_conf, caplog, capsys) -> None:
 
     parallel.assert_called_once()
 
+    out, err = capsys.readouterr()
+    assert '{"params":{"mfi-value":null,"fastd-value":null,"adx-value":null,"rsi-value":null,"mfi-enabled":null,"fastd-enabled":null,"adx-enabled":null,"rsi-enabled":null,"trigger":null,"sell-mfi-value":null,"sell-fastd-value":null,"sell-adx-value":null,"sell-rsi-value":null,"sell-mfi-enabled":null,"sell-fastd-enabled":null,"sell-adx-enabled":null,"sell-rsi-enabled":null,"sell-trigger":null},"minimal_roi":{},"stoploss":null,"trailing_stop":null,"trailing_stop_positive":null,"trailing_stop_positive_offset":null,"trailing_only_offset_is_reached":null}' in out  # noqa: E501
+    assert dumper.called
+    # Should be called twice, once for tickerdata, once to save evaluations
+    assert dumper.call_count == 2
+
+
+def test_print_json_spaces_default(mocker, default_conf, caplog, capsys) -> None:
+    dumper = mocker.patch('freqtrade.optimize.hyperopt.dump', MagicMock())
+    mocker.patch('freqtrade.optimize.backtesting.Backtesting.load_bt_data',
+                 MagicMock(return_value=(MagicMock(), None)))
+    mocker.patch(
+        'freqtrade.optimize.hyperopt.get_timeframe',
+        MagicMock(return_value=(datetime(2017, 12, 10), datetime(2017, 12, 13)))
+    )
+
+    parallel = mocker.patch(
+        'freqtrade.optimize.hyperopt.Hyperopt.run_optimizer_parallel',
+        MagicMock(return_value=[{'loss': 1, 'results_explanation': 'foo result', 'params': {}}])
+    )
+    patch_exchange(mocker)
+
+    default_conf.update({'config': 'config.json.example',
+                         'hyperopt': 'DefaultHyperOpt',
+                         'epochs': 1,
+                         'timerange': None,
+                         'spaces': 'default',
+                         'hyperopt_jobs': 1,
+                         'print_json': True,
+                         })
+
+    hyperopt = Hyperopt(default_conf)
+    hyperopt.backtesting.strategy.tickerdata_to_dataframe = MagicMock()
+    hyperopt.custom_hyperopt.generate_roi_table = MagicMock(return_value={})
+
+    hyperopt.start()
+
+    parallel.assert_called_once()
+
     out, err = capsys.readouterr()
     assert '{"params":{"mfi-value":null,"fastd-value":null,"adx-value":null,"rsi-value":null,"mfi-enabled":null,"fastd-enabled":null,"adx-enabled":null,"rsi-enabled":null,"trigger":null,"sell-mfi-value":null,"sell-fastd-value":null,"sell-adx-value":null,"sell-rsi-value":null,"sell-mfi-enabled":null,"sell-fastd-enabled":null,"sell-adx-enabled":null,"sell-rsi-enabled":null,"sell-trigger":null},"minimal_roi":{},"stoploss":null}' in out  # noqa: E501
     assert dumper.called


Code review comments:
diff_hunk: @@ -46,8 +46,9 @@ Optional - can also be loaded from a strategy:
 Rarely you may also need to override:
 
 * `roi_space` - for custom ROI optimization (if you need the ranges for the ROI parameters in the optimization hyperspace that differ from default)
-* `generate_roi_table` - for custom ROI optimization (if you need more than 4 entries in the ROI table)
+* `generate_roi_table` - for custom ROI optimization (if you need more than 4 entries in the ROI table or the ranges of the values in the ROI table that differ from default)
 * `stoploss_space` - for custom stoploss optimization (if you need the range for the stoploss parameter in the optimization hyperspace that differs from default)
+* `trailing_space` - for custom trailing stop optimization (if you need the ranges for the trailing stopl parameters in the optimization hyperspace that differ from default)
path: docs/hyperopt.md
text: @user1:
`stopl` ... ?  i understand what you mean - but  ... :laughing:

diff_hunk: @@ -190,25 +192,35 @@ def log_trials_result(self) -> None:
                 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)
path: freqtrade/optimize/hyperopt.py
text: @user1:
I think these 3 lines (and subsquent ones) could be extracted into a method - which loops all spaces, does an `if self.has_space(space)` - and then prints that space.

All options below are identical (apart from ROI) ... 
this should eliminate the need to "noqa" this method because it's too nested  - and more importantly, will remove a lot of code duplication).

@author:
Refactored, pls review