From 8e7512161ad3ecfa82a06197b0e5a0241737e753 Mon Sep 17 00:00:00 2001 From: hroff-1902 Date: Tue, 26 Nov 2019 15:01:42 +0300 Subject: [PATCH 01/17] Add hyperopt-list and hyperopt-show commands --- freqtrade/configuration/arguments.py | 27 +- freqtrade/configuration/cli_options.py | 39 +++ freqtrade/configuration/configuration.py | 15 ++ freqtrade/optimize/hyperopt.py | 304 +++++++++++++---------- freqtrade/utils.py | 99 ++++++++ 5 files changed, 353 insertions(+), 131 deletions(-) diff --git a/freqtrade/configuration/arguments.py b/freqtrade/configuration/arguments.py index 29d0d98a2..fa1d77407 100644 --- a/freqtrade/configuration/arguments.py +++ b/freqtrade/configuration/arguments.py @@ -49,8 +49,14 @@ ARGS_PLOT_DATAFRAME = ["pairs", "indicators1", "indicators2", "plot_limit", ARGS_PLOT_PROFIT = ["pairs", "timerange", "export", "exportfilename", "db_url", "trade_source", "ticker_interval"] +ARGS_HYPEROPT_LIST = ["hyperopt_list_best", "hyperopt_list_profitable", "print_colorized", + "print_json", "hyperopt_list_no_details"] + +ARGS_HYPEROPT_SHOW = ["hyperopt_list_best", "hyperopt_list_profitable", "hyperopt_show_index", + "print_json", "hyperopt_show_no_header"] + NO_CONF_REQURIED = ["download-data", "list-timeframes", "list-markets", "list-pairs", - "plot-dataframe", "plot-profit"] + "hyperopt_list", "hyperopt_show", "plot-dataframe", "plot-profit"] NO_CONF_ALLOWED = ["create-userdir", "list-exchanges"] @@ -116,6 +122,7 @@ class Arguments: from freqtrade.optimize import start_backtesting, start_hyperopt, start_edge from freqtrade.utils import (start_create_userdir, start_download_data, + start_hyperopt_list, start_hyperopt_show, start_list_exchanges, start_list_markets, start_list_timeframes, start_trading) from freqtrade.plot.plot_utils import start_plot_dataframe, start_plot_profit @@ -220,3 +227,21 @@ class Arguments: ) plot_profit_cmd.set_defaults(func=start_plot_profit) self._build_args(optionlist=ARGS_PLOT_PROFIT, parser=plot_profit_cmd) + + # Add hyperopt-list subcommand + hyperopt_list_cmd = subparsers.add_parser( + 'hyperopt-list', + help='List Hyperopt results', + parents=[_common_parser], + ) + hyperopt_list_cmd.set_defaults(func=start_hyperopt_list) + self._build_args(optionlist=ARGS_HYPEROPT_LIST, parser=hyperopt_list_cmd) + + # Add hyperopt-show subcommand + hyperopt_show_cmd = subparsers.add_parser( + 'hyperopt-show', + help='Show details of Hyperopt results', + parents=[_common_parser], + ) + hyperopt_show_cmd.set_defaults(func=start_hyperopt_show) + self._build_args(optionlist=ARGS_HYPEROPT_SHOW, parser=hyperopt_show_cmd) diff --git a/freqtrade/configuration/cli_options.py b/freqtrade/configuration/cli_options.py index 6dc5ef026..beed2b835 100644 --- a/freqtrade/configuration/cli_options.py +++ b/freqtrade/configuration/cli_options.py @@ -18,6 +18,18 @@ def check_int_positive(value: str) -> int: return uint +def check_int_nonzero(value: str) -> int: + try: + uint = int(value) + if uint == 0: + raise ValueError + except ValueError: + raise argparse.ArgumentTypeError( + f"{value} is invalid for this parameter, should be a non-zero integer value" + ) + return uint + + class Arg: # Optional CLI arguments def __init__(self, *args, **kwargs): @@ -364,4 +376,31 @@ AVAILABLE_CLI_OPTIONS = { choices=["DB", "file"], default="file", ), + # hyperopt-list, hyperopt-show + "hyperopt_list_profitable": Arg( + '--profitable', + help='Select only profitable epochs.', + action='store_true', + ), + "hyperopt_list_best": Arg( + '--best', + help='Select only best epochs.', + action='store_true', + ), + "hyperopt_list_no_details": Arg( + '--no-details', + help='Do not print best epoch details.', + action='store_true', + ), + "hyperopt_show_index": Arg( + '-n', '--index', + help='Specify the index of the epoch to print details for.', + type=check_int_nonzero, + metavar='INT', + ), + "hyperopt_show_no_header": Arg( + '--no-header', + help='Do not print epoch details header.', + action='store_true', + ), } diff --git a/freqtrade/configuration/configuration.py b/freqtrade/configuration/configuration.py index 93eee3912..6552078c8 100644 --- a/freqtrade/configuration/configuration.py +++ b/freqtrade/configuration/configuration.py @@ -308,6 +308,21 @@ class Configuration: self._args_to_config(config, argname='hyperopt_loss', logstring='Using Hyperopt loss class name: {}') + self._args_to_config(config, argname='hyperopt_show_index', + logstring='Parameter -n/--index detected: {}') + + self._args_to_config(config, argname='hyperopt_list_best', + logstring='Parameter --best detected: {}') + + self._args_to_config(config, argname='hyperopt_list_profitable', + logstring='Parameter --profitable detected: {}') + + self._args_to_config(config, argname='hyperopt_list_no_details', + logstring='Parameter --no-details detected: {}') + + self._args_to_config(config, argname='hyperopt_show_no_header', + logstring='Parameter --no-header detected: {}') + def _process_plot_options(self, config: Dict[str, Any]) -> None: self._args_to_config(config, argname='pairs', diff --git a/freqtrade/optimize/hyperopt.py b/freqtrade/optimize/hyperopt.py index 836309a62..2ef8ddc2f 100644 --- a/freqtrade/optimize/hyperopt.py +++ b/freqtrade/optimize/hyperopt.py @@ -74,11 +74,11 @@ class Hyperopt: else: logger.info("Continuing on previous hyperopt results.") + self.num_trials_saved = 0 + # Previous evaluations self.trials: List = [] - self.num_trials_saved = 0 - # Populate functions here (hasattr is slow so should not be run during "regular" operations) if hasattr(self.custom_hyperopt, 'populate_indicators'): self.backtesting.strategy.advise_indicators = \ @@ -104,6 +104,10 @@ class Hyperopt: self.config['ask_strategy'] = {} self.config['ask_strategy']['use_sell_signal'] = True + self.print_all = self.config.get('print_all', False) + self.print_colorized = self.config.get('print_colorized', False) + self.print_json = self.config.get('print_json', False) + @staticmethod def get_lock_filename(config) -> str: @@ -119,20 +123,18 @@ class Hyperopt: logger.info(f"Removing `{p}`.") p.unlink() - def get_args(self, params): + def _get_params_dict(self, raw_params: List[Any]) -> Dict: - dimensions = self.dimensions + dimensions: List[Dimension] = self.dimensions # Ensure the number of dimensions match - # the number of parameters in the list x. - if len(params) != len(dimensions): - raise ValueError('Mismatch in number of search-space dimensions. ' - f'len(dimensions)=={len(dimensions)} and len(x)=={len(params)}') + # the number of parameters in the list. + if len(raw_params) != len(dimensions): + raise ValueError('Mismatch in number of search-space dimensions.') - # Create a dict where the keys are the names of the dimensions - # and the values are taken from the list of parameters x. - arg_dict = {dim.name: value for dim, value in zip(dimensions, params)} - return arg_dict + # Return a dict where the keys are the names of the dimensions + # and the values are taken from the list of parameters. + return {d.name: v for d, v in zip(dimensions, raw_params)} def save_trials(self, final: bool = False) -> None: """ @@ -147,106 +149,126 @@ class Hyperopt: logger.info(f"{num_trials} {plural(num_trials, 'epoch')} " f"saved to '{self.trials_file}'.") - def read_trials(self) -> List: + @staticmethod + def _read_trials(trials_file) -> List: """ Read hyperopt trials file """ - logger.info("Reading Trials from '%s'", self.trials_file) - trials = load(self.trials_file) - self.trials_file.unlink() + logger.info("Reading Trials from '%s'", trials_file) + trials = load(trials_file) return trials - def log_trials_result(self) -> None: + def _get_params_details(self, params: Dict) -> Dict: """ - Display Best hyperopt result + Return the params for each space """ - # This is printed when Ctrl+C is pressed quickly, before first epochs have - # a chance to be evaluated. - if not self.trials: - print("No epochs evaluated yet, no best result.") - return + result: Dict = {} - results = sorted(self.trials, key=itemgetter('loss')) - best_result = results[0] - params = best_result['params'] - log_str = self.format_results_logstring(best_result) - print(f"\nBest result:\n\n{log_str}\n") + if self.has_space('buy'): + result['buy'] = {p.name: params.get(p.name) for p in self.hyperopt_space('buy')} + if self.has_space('sell'): + result['sell'] = {p.name: params.get(p.name) for p in self.hyperopt_space('sell')} + if self.has_space('roi'): + result['roi'] = self.custom_hyperopt.generate_roi_table(params) + if self.has_space('stoploss'): + result['stoploss'] = params.get('stoploss') - if self.config.get('print_json'): + return result + + @staticmethod # noqa: C901 + def print_epoch_details(results, total_epochs, print_json: bool, + no_header: bool = False, header_str: str = None) -> None: + """ + Display details of the hyperopt result + """ + params = results['params_details'] + + # Default header string + if header_str is None: + header_str = "Best result" + + if not no_header: + explanation_str = Hyperopt._format_explanation_string(results, total_epochs) + print(f"\n{header_str}:\n\n{explanation_str}\n") + + if 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')}) - if self.has_space('sell'): - result_dict['params'].update({p.name: params.get(p.name) - for p in self.hyperopt_space('sell')}) - if self.has_space('roi'): + result_params_dict: Dict = {} + if 'buy' in params: + result_params_dict.update(params['buy']) + if 'sell' in params: + result_params_dict.update(params['sell']) + if result_params_dict: + result_dict['params'] = result_params_dict + if 'roi' in params: # Convert keys in min_roi dict to strings because # rapidjson cannot dump dicts with integer keys... # OrderedDict is used to keep the numeric order of the items # in the dict. 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 params['roi'].items() ) - if self.has_space('stoploss'): - result_dict['stoploss'] = params.get('stoploss') + if 'stoploss' in params: + result_dict['stoploss'] = params['stoploss'] print(rapidjson.dumps(result_dict, default=str, number_mode=rapidjson.NM_NATIVE)) else: - if self.has_space('buy'): + if 'buy' in params: print('Buy hyperspace params:') - pprint({p.name: params.get(p.name) for p in self.hyperopt_space('buy')}, - indent=4) - if self.has_space('sell'): + pprint(params['buy'], indent=4) + if 'sell' in params: print('Sell hyperspace params:') - pprint({p.name: params.get(p.name) for p in self.hyperopt_space('sell')}, - indent=4) - if self.has_space('roi'): + pprint(params['sell'], indent=4) + if 'roi' in params: 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'): + pprint(round_dict(params['roi'], 5), indent=4) + if 'stoploss' in params: # Also round to 5 digits after the decimal point - print(f"Stoploss: {round(params.get('stoploss'), 5)}") + print(f"Stoploss: {round(params['stoploss'], 5)}") - def is_best(self, results) -> bool: - return results['loss'] < self.current_best_loss + @staticmethod + def is_best_loss(results, current_best_loss) -> bool: + return results['loss'] < current_best_loss - def log_results(self, results) -> None: + def print_results(self, results) -> None: """ Log results if it is better than any previous evaluation """ - print_all = self.config.get('print_all', False) - is_best_loss = self.is_best(results) - - if not print_all: + is_best = results['is_best'] + if not self.print_all: + # Print '\n' after each 100th epoch to separate dots from the log messages. + # Otherwise output is messy on a terminal. print('.', end='' if results['current_epoch'] % 100 != 0 else None) # type: ignore sys.stdout.flush() - if print_all or is_best_loss: - if is_best_loss: - self.current_best_loss = results['loss'] - log_str = self.format_results_logstring(results) - # Colorize output - if self.config.get('print_colorized', False): - if results['total_profit'] > 0: - log_str = Fore.GREEN + log_str - if print_all and is_best_loss: - log_str = Style.BRIGHT + log_str - if print_all: - print(log_str) - else: - print(f'\n{log_str}') + if self.print_all or is_best: + if not self.print_all: + # Separate the results explanation string from dots + print("\n") + self.print_results_explanation(results, self.total_epochs, self.print_all, + self.print_colorized) - def format_results_logstring(self, results) -> str: - current = results['current_epoch'] - total = self.total_epochs - res = results['results_explanation'] - loss = results['loss'] - log_str = f'{current:5d}/{total}: {res} Objective: {loss:.5f}' - log_str = f'*{log_str}' if results['is_initial_point'] else f' {log_str}' - return log_str + @staticmethod + def print_results_explanation(results, total_epochs, highlight_best: bool, + print_colorized: bool) -> None: + """ + Log results explanation string + """ + explanation_str = Hyperopt._format_explanation_string(results, total_epochs) + # Colorize output + if print_colorized: + if results['total_profit'] > 0: + explanation_str = Fore.GREEN + explanation_str + if highlight_best and results['is_best']: + explanation_str = Style.BRIGHT + explanation_str + print(explanation_str) + + @staticmethod + def _format_explanation_string(results, total_epochs) -> str: + return (("*" if results['is_initial_point'] else " ") + + f"{results['current_epoch']:5d}/{total_epochs}: " + + f"{results['results_explanation']} " + + f"Objective: {results['loss']:.5f}") def has_space(self, space: str) -> bool: """ @@ -276,33 +298,34 @@ class Hyperopt: spaces += self.custom_hyperopt.stoploss_space() return spaces - def generate_optimizer(self, _params: Dict, iteration=None) -> Dict: + def generate_optimizer(self, raw_params: List[Any], iteration=None) -> Dict: """ Used Optimize function. Called once per epoch to optimize whatever is configured. Keep this function as optimized as possible! """ - params = self.get_args(_params) + params_dict = self._get_params_dict(raw_params) + params_details = self._get_params_details(params_dict) if self.has_space('roi'): self.backtesting.strategy.minimal_roi = \ - self.custom_hyperopt.generate_roi_table(params) + self.custom_hyperopt.generate_roi_table(params_dict) if self.has_space('buy'): self.backtesting.strategy.advise_buy = \ - self.custom_hyperopt.buy_strategy_generator(params) + self.custom_hyperopt.buy_strategy_generator(params_dict) if self.has_space('sell'): self.backtesting.strategy.advise_sell = \ - self.custom_hyperopt.sell_strategy_generator(params) + self.custom_hyperopt.sell_strategy_generator(params_dict) if self.has_space('stoploss'): - self.backtesting.strategy.stoploss = params['stoploss'] + self.backtesting.strategy.stoploss = params_dict['stoploss'] processed = load(self.tickerdata_pickle) min_date, max_date = get_timeframe(processed) - results = self.backtesting.backtest( + backtesting_results = self.backtesting.backtest( { 'stake_amount': self.config['stake_amount'], 'processed': processed, @@ -312,58 +335,58 @@ class Hyperopt: 'end_date': max_date, } ) - results_explanation = self.format_results(results) + results_metrics = self._calculate_results_metrics(backtesting_results) + results_explanation = self._format_results_explanation_string(results_metrics) - trade_count = len(results.index) - total_profit = results.profit_abs.sum() + trade_count = results_metrics['trade_count'] + total_profit = results_metrics['total_profit'] # If this evaluation contains too short amount of trades to be # interesting -- consider it as 'bad' (assigned max. loss value) # in order to cast this hyperspace point away from optimization # path. We do not want to optimize 'hodl' strategies. - if trade_count < self.config['hyperopt_min_trades']: - return { - 'loss': MAX_LOSS, - 'params': params, - 'results_explanation': results_explanation, - 'total_profit': total_profit, - } - - loss = self.calculate_loss(results=results, trade_count=trade_count, - min_date=min_date.datetime, max_date=max_date.datetime) - + loss: float = MAX_LOSS + if trade_count >= self.config['hyperopt_min_trades']: + loss = self.calculate_loss(results=backtesting_results, trade_count=trade_count, + min_date=min_date.datetime, max_date=max_date.datetime) return { 'loss': loss, - 'params': params, + 'params_dict': params_dict, + 'params_details': params_details, + 'results_metrics': results_metrics, 'results_explanation': results_explanation, 'total_profit': total_profit, } - def format_results(self, results: DataFrame) -> str: + def _calculate_results_metrics(self, backtesting_results: DataFrame) -> Dict: + return { + 'trade_count': len(backtesting_results.index), + 'avg_profit': backtesting_results.profit_percent.mean() * 100.0, + 'total_profit': backtesting_results.profit_abs.sum(), + 'profit': backtesting_results.profit_percent.sum() * 100.0, + 'duration': backtesting_results.trade_duration.mean(), + } + + def _format_results_explanation_string(self, results_metrics: Dict) -> str: """ Return the formatted results explanation in a string """ - trades = len(results.index) - avg_profit = results.profit_percent.mean() * 100.0 - total_profit = results.profit_abs.sum() stake_cur = self.config['stake_currency'] - profit = results.profit_percent.sum() * 100.0 - duration = results.trade_duration.mean() - - return (f'{trades:6d} trades. Avg profit {avg_profit: 5.2f}%. ' - f'Total profit {total_profit: 11.8f} {stake_cur} ' - f'({profit: 7.2f}\N{GREEK CAPITAL LETTER SIGMA}%). ' - f'Avg duration {duration:5.1f} mins.' + return (f"{results_metrics['trade_count']:6d} trades. " + f"Avg profit {results_metrics['avg_profit']: 6.2f}%. " + f"Total profit {results_metrics['total_profit']: 11.8f} {stake_cur} " + f"({results_metrics['profit']: 7.2f}\N{GREEK CAPITAL LETTER SIGMA}%). " + f"Avg duration {results_metrics['duration']:5.1f} mins." ).encode(locale.getpreferredencoding(), 'replace').decode('utf-8') - def get_optimizer(self, dimensions, cpu_count) -> Optimizer: + def get_optimizer(self, dimensions: List[Dimension], cpu_count) -> Optimizer: return Optimizer( dimensions, base_estimator="ET", acq_optimizer="auto", n_initial_points=INITIAL_POINTS, acq_optimizer_kwargs={'n_jobs': cpu_count}, - random_state=self.config.get('hyperopt_random_state', None) + random_state=self.config.get('hyperopt_random_state', None), ) def fix_optimizer_models_list(self): @@ -387,14 +410,16 @@ class Hyperopt: return parallel(delayed( wrap_non_picklable_objects(self.generate_optimizer))(v, i) for v in asked) - def load_previous_results(self): - """ read trials file if we have one """ - if self.trials_file.is_file() and self.trials_file.stat().st_size > 0: - self.trials = self.read_trials() - logger.info( - 'Loaded %d previous evaluations from disk.', - len(self.trials) - ) + @staticmethod + def load_previous_results(trials_file) -> List: + """ + Load data for epochs from the file if we have one + """ + trials: List = [] + if trials_file.is_file() and trials_file.stat().st_size > 0: + trials = Hyperopt._read_trials(trials_file) + logger.info(f"Loaded {len(trials)} previous evaluations from disk.") + return trials def start(self) -> None: data, timerange = self.backtesting.load_bt_data() @@ -415,17 +440,17 @@ class Hyperopt: # We don't need exchange instance anymore while running hyperopt self.backtesting.exchange = None # type: ignore - self.load_previous_results() + self.trials = self.load_previous_results(self.trials_file) cpus = cpu_count() logger.info(f"Found {cpus} CPU cores. Let's make them scream!") config_jobs = self.config.get('hyperopt_jobs', -1) logger.info(f'Number of parallel jobs set as: {config_jobs}') - self.dimensions = self.hyperopt_space() + self.dimensions: List[Dimension] = self.hyperopt_space() self.opt = self.get_optimizer(self.dimensions, config_jobs) - if self.config.get('print_colorized', False): + if self.print_colorized: colorama_init(autoreset=True) try: @@ -439,19 +464,38 @@ class Hyperopt: self.opt.tell(asked, [v['loss'] for v in f_val]) self.fix_optimizer_models_list() for j in range(jobs): - # Use human-friendly index here (starting from 1) + # Use human-friendly indexes here (starting from 1) current = i * jobs + j + 1 val = f_val[j] val['current_epoch'] = current val['is_initial_point'] = current <= INITIAL_POINTS logger.debug(f"Optimizer epoch evaluated: {val}") - is_best = self.is_best(val) - self.log_results(val) + + is_best = self.is_best_loss(val, self.current_best_loss) + # This value is assigned here and not in the optimization method + # to keep proper order in the list of results. That's because + # evaluations can take different time. Here they are aligned in the + # order they will be shown to the user. + val['is_best'] = is_best + + self.print_results(val) + + if is_best: + self.current_best_loss = val['loss'] self.trials.append(val) + # Save results after each best epoch and every 100 epochs if is_best or current % 100 == 0: self.save_trials() except KeyboardInterrupt: print('User interrupted..') self.save_trials(final=True) - self.log_trials_result() + + if self.trials: + sorted_trials = sorted(self.trials, key=itemgetter('loss')) + results = sorted_trials[0] + self.print_epoch_details(results, self.total_epochs, self.print_json) + else: + # This is printed when Ctrl+C is pressed quickly, before first epochs have + # a chance to be evaluated. + print("No epochs evaluated yet, no best result.") diff --git a/freqtrade/utils.py b/freqtrade/utils.py index b8ab7504e..0139006d6 100644 --- a/freqtrade/utils.py +++ b/freqtrade/utils.py @@ -1,12 +1,14 @@ import logging import sys from collections import OrderedDict +from operator import itemgetter from pathlib import Path from typing import Any, Dict, List import arrow import csv import rapidjson +from colorama import init as colorama_init from tabulate import tabulate from freqtrade import OperationalException @@ -18,6 +20,7 @@ from freqtrade.data.history import (convert_trades_to_ohlcv, from freqtrade.exchange import (available_exchanges, ccxt_exchanges, market_is_active, symbol_is_pair) from freqtrade.misc import plural +from freqtrade.optimize.hyperopt import Hyperopt from freqtrade.resolvers import ExchangeResolver from freqtrade.state import RunMode @@ -236,3 +239,99 @@ def start_list_markets(args: Dict[str, Any], pairs_only: bool = False) -> None: args.get('list_pairs_print_json', False) or args.get('print_csv', False)): print(f"{summary_str}.") + + +def start_hyperopt_list(args: Dict[str, Any]) -> None: + """ + """ + config = setup_utils_configuration(args, RunMode.UTIL_NO_EXCHANGE) + + only_best = config.get('hyperopt_list_best', False) + only_profitable = config.get('hyperopt_list_profitable', False) + print_colorized = config.get('print_colorized', False) + print_json = config.get('print_json', False) + no_details = config.get('hyperopt_list_no_details', False) + no_header = False + + trials_file = (config['user_data_dir'] / + 'hyperopt_results' / 'hyperopt_results.pickle') + + # Previous evaluations + trials = Hyperopt.load_previous_results(trials_file) + total_epochs = len(trials) + + trials = _hyperopt_filter_trials(trials, only_best, only_profitable) + + # TODO: fetch the interval for epochs to print from the cli option + epoch_start, epoch_stop = 0, None + + if print_colorized: + colorama_init(autoreset=True) + + try: + # Human-friendly indexes used here (starting from 1) + for val in trials[epoch_start:epoch_stop]: + Hyperopt.print_results_explanation(val, total_epochs, not only_best, print_colorized) + + except KeyboardInterrupt: + print('User interrupted..') + + if trials and not no_details: + sorted_trials = sorted(trials, key=itemgetter('loss')) + results = sorted_trials[0] + Hyperopt.print_epoch_details(results, total_epochs, print_json, no_header) + + +def start_hyperopt_show(args: Dict[str, Any]) -> None: + """ + """ + config = setup_utils_configuration(args, RunMode.UTIL_NO_EXCHANGE) + + only_best = config.get('hyperopt_list_best', False) + only_profitable = config.get('hyperopt_list_profitable', False) + no_header = config.get('hyperopt_show_no_header', False) + + trials_file = (config['user_data_dir'] / + 'hyperopt_results' / 'hyperopt_results.pickle') + + # Previous evaluations + trials = Hyperopt.load_previous_results(trials_file) + total_epochs = len(trials) + + trials = _hyperopt_filter_trials(trials, only_best, only_profitable) + + n = config.get('hyperopt_show_index', -1) + if n > total_epochs: + raise OperationalException( + f"The index of the epoch to show should be less than {total_epochs + 1}.") + if n < -total_epochs: + raise OperationalException( + f"The index of the epoch to showshould be greater than {-total_epochs - 1}.") + + # Translate epoch index from human-readable format to pythonic + if n > 0: + n -= 1 + + print_json = config.get('print_json', False) + + if trials: + val = trials[n] + Hyperopt.print_epoch_details(val, total_epochs, print_json, no_header, + header_str="Epoch details") + + +def _hyperopt_filter_trials(trials: List, only_best: bool, only_profitable: bool) -> List: + """ + Filter our items from the list of hyperopt results + """ + if only_best: + trials = [x for x in trials if x['is_best']] + if only_profitable: + trials = [x for x in trials if x['results_metrics']['profit'] > 0] + + logger.info(f"{len(trials)} " + + ("best " if only_best else "") + + ("profitable " if only_profitable else "") + + "epochs found.") + + return trials From c3d7411668f306c77a5c256b5131a49156f7891e Mon Sep 17 00:00:00 2001 From: hroff-1902 Date: Wed, 27 Nov 2019 19:35:22 +0300 Subject: [PATCH 02/17] Fix imports --- freqtrade/utils.py | 5 ++++- 1 file changed, 4 insertions(+), 1 deletion(-) diff --git a/freqtrade/utils.py b/freqtrade/utils.py index 99e8c3918..3fc0c245c 100644 --- a/freqtrade/utils.py +++ b/freqtrade/utils.py @@ -23,7 +23,6 @@ from freqtrade.data.history import (convert_trades_to_ohlcv, from freqtrade.exchange import (available_exchanges, ccxt_exchanges, market_is_active, symbol_is_pair) from freqtrade.misc import plural, render_template -from freqtrade.optimize.hyperopt import Hyperopt from freqtrade.resolvers import ExchangeResolver from freqtrade.state import RunMode @@ -330,6 +329,8 @@ def start_list_markets(args: Dict[str, Any], pairs_only: bool = False) -> None: def start_hyperopt_list(args: Dict[str, Any]) -> None: """ """ + from freqtrade.optimize.hyperopt import Hyperopt + config = setup_utils_configuration(args, RunMode.UTIL_NO_EXCHANGE) only_best = config.get('hyperopt_list_best', False) @@ -371,6 +372,8 @@ def start_hyperopt_list(args: Dict[str, Any]) -> None: def start_hyperopt_show(args: Dict[str, Any]) -> None: """ """ + from freqtrade.optimize.hyperopt import Hyperopt + config = setup_utils_configuration(args, RunMode.UTIL_NO_EXCHANGE) only_best = config.get('hyperopt_list_best', False) From 8f9b5095b5a467d1b0f328de5cae92ffb659daf4 Mon Sep 17 00:00:00 2001 From: hroff-1902 Date: Wed, 27 Nov 2019 22:52:43 +0300 Subject: [PATCH 03/17] Fix some tests --- freqtrade/optimize/hyperopt.py | 7 ++++- tests/optimize/test_hyperopt.py | 50 +++++++++++++++++++++++++++------ 2 files changed, 47 insertions(+), 10 deletions(-) diff --git a/freqtrade/optimize/hyperopt.py b/freqtrade/optimize/hyperopt.py index 2ef8ddc2f..d2404beb5 100644 --- a/freqtrade/optimize/hyperopt.py +++ b/freqtrade/optimize/hyperopt.py @@ -181,7 +181,7 @@ class Hyperopt: """ Display details of the hyperopt result """ - params = results['params_details'] + params = results.get('params_details', {}) # Default header string if header_str is None: @@ -335,6 +335,11 @@ class Hyperopt: 'end_date': max_date, } ) + return self._get_results_dict(backtesting_results, min_date, max_date, + params_dict, params_details) + + def _get_results_dict(self, backtesting_results, min_date, max_date, + params_dict, params_details): results_metrics = self._calculate_results_metrics(backtesting_results) results_explanation = self._format_results_explanation_string(results_metrics) diff --git a/tests/optimize/test_hyperopt.py b/tests/optimize/test_hyperopt.py index d3d544502..d18263ea2 100644 --- a/tests/optimize/test_hyperopt.py +++ b/tests/optimize/test_hyperopt.py @@ -357,8 +357,9 @@ def test_onlyprofit_loss_prefers_higher_profits(default_conf, hyperopt_results) def test_log_results_if_loss_improves(hyperopt, capsys) -> None: hyperopt.current_best_loss = 2 hyperopt.total_epochs = 2 - hyperopt.log_results( + hyperopt.print_results( { + 'is_best': False, 'loss': 1, 'current_epoch': 2, # This starts from 1 (in a human-friendly manner) 'results_explanation': 'foo.', @@ -371,8 +372,9 @@ def test_log_results_if_loss_improves(hyperopt, capsys) -> None: def test_no_log_if_loss_does_not_improve(hyperopt, caplog) -> None: hyperopt.current_best_loss = 2 - hyperopt.log_results( + hyperopt.print_results( { + 'is_best': False, 'loss': 3, 'current_epoch': 1, } @@ -400,8 +402,8 @@ def test_save_trials_saves_trials(mocker, hyperopt, testdatadir, caplog) -> None def test_read_trials_returns_trials_file(mocker, hyperopt, testdatadir, caplog) -> None: trials = create_trials(mocker, hyperopt, testdatadir) mock_load = mocker.patch('freqtrade.optimize.hyperopt.load', return_value=trials) - hyperopt_trial = hyperopt.read_trials() trials_file = testdatadir / 'optimize' / 'ut_trials.pickle' + hyperopt_trial = hyperopt._read_trials(trials_file) assert log_has(f"Reading Trials from '{trials_file}'", caplog) assert hyperopt_trial == trials mock_load.assert_called_once() @@ -472,8 +474,8 @@ def test_format_results(hyperopt): ] labels = ['currency', 'profit_percent', 'profit_abs', 'trade_duration'] df = pd.DataFrame.from_records(trades, columns=labels) - - result = hyperopt.format_results(df) + results_metrics = hyperopt._calculate_results_metrics(df) + result = hyperopt._format_explanation_string(results_metrics, 1) assert result.find(' 66.67%') assert result.find('Total profit 1.00000000 BTC') assert result.find('2.0000Σ %') @@ -485,7 +487,8 @@ def test_format_results(hyperopt): ('XPR/EUR', -1, -2, -246) ] df = pd.DataFrame.from_records(trades, columns=labels) - result = hyperopt.format_results(df) + results_metrics = hyperopt._calculate_results_metrics(df) + result = hyperopt._format_explanation_string(results_metrics, 1) assert result.find('Total profit 1.00000000 EUR') @@ -594,10 +597,38 @@ def test_generate_optimizer(mocker, default_conf) -> None: } response_expected = { 'loss': 1.9840569076926293, - 'results_explanation': (' 1 trades. Avg profit 2.31%. Total profit 0.00023300 BTC ' + 'results_explanation': (' 1 trades. Avg profit 2.31%. Total profit 0.00023300 BTC ' '( 2.31\N{GREEK CAPITAL LETTER SIGMA}%). Avg duration 100.0 mins.' ).encode(locale.getpreferredencoding(), 'replace').decode('utf-8'), - 'params': optimizer_param, + 'params_details': {'buy': {'adx-enabled': False, + 'adx-value': 0, + 'fastd-enabled': True, + 'fastd-value': 35, + 'mfi-enabled': False, + 'mfi-value': 0, + 'rsi-enabled': False, + 'rsi-value': 0, + 'trigger': 'macd_cross_signal'}, + 'roi': {0: 0.12000000000000001, + 20.0: 0.02, + 50.0: 0.01, + 110.0: 0}, + 'sell': {'sell-adx-enabled': False, + 'sell-adx-value': 0, + 'sell-fastd-enabled': True, + 'sell-fastd-value': 75, + 'sell-mfi-enabled': False, + 'sell-mfi-value': 0, + 'sell-rsi-enabled': False, + 'sell-rsi-value': 0, + 'sell-trigger': 'macd_cross_signal'}, + 'stoploss': -0.4}, + 'params_dict': optimizer_param, + 'results_metrics': {'avg_profit': 2.3117, + 'duration': 100.0, + 'profit': 2.3117, + 'total_profit': 0.000233, + 'trade_count': 1}, 'total_profit': 0.00023300 } @@ -692,7 +723,8 @@ def test_print_json_spaces_roi_stoploss(mocker, default_conf, caplog, capsys) -> parallel = mocker.patch( 'freqtrade.optimize.hyperopt.Hyperopt.run_optimizer_parallel', - MagicMock(return_value=[{'loss': 1, 'results_explanation': 'foo result', 'params': {}}]) + MagicMock(return_value=[{'loss': 1, 'results_explanation': 'foo result', 'params': {}, + 'params_details': {'roi': {}, 'stoploss': None}}]) ) patch_exchange(mocker) From d6b587678eec22d9d6ff135a3a6ce9d3cec1de04 Mon Sep 17 00:00:00 2001 From: hroff-1902 Date: Sun, 1 Dec 2019 17:44:14 +0300 Subject: [PATCH 04/17] Adjust test --- tests/optimize/test_hyperopt.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/tests/optimize/test_hyperopt.py b/tests/optimize/test_hyperopt.py index d18263ea2..08bef11a1 100644 --- a/tests/optimize/test_hyperopt.py +++ b/tests/optimize/test_hyperopt.py @@ -359,7 +359,7 @@ def test_log_results_if_loss_improves(hyperopt, capsys) -> None: hyperopt.total_epochs = 2 hyperopt.print_results( { - 'is_best': False, + 'is_best': True, 'loss': 1, 'current_epoch': 2, # This starts from 1 (in a human-friendly manner) 'results_explanation': 'foo.', From 05967442c3d03a450440d77b1df344e54f100bee Mon Sep 17 00:00:00 2001 From: hroff-1902 Date: Sun, 1 Dec 2019 18:01:59 +0300 Subject: [PATCH 05/17] Adjust test --- tests/optimize/test_hyperopt.py | 19 +++++++++++++++++-- 1 file changed, 17 insertions(+), 2 deletions(-) diff --git a/tests/optimize/test_hyperopt.py b/tests/optimize/test_hyperopt.py index 08bef11a1..ff485d423 100644 --- a/tests/optimize/test_hyperopt.py +++ b/tests/optimize/test_hyperopt.py @@ -475,7 +475,21 @@ def test_format_results(hyperopt): labels = ['currency', 'profit_percent', 'profit_abs', 'trade_duration'] df = pd.DataFrame.from_records(trades, columns=labels) results_metrics = hyperopt._calculate_results_metrics(df) - result = hyperopt._format_explanation_string(results_metrics, 1) + results_explanation = hyperopt._format_results_explanation_string(results_metrics) + total_profit = results_metrics['total_profit'] + + results = { + 'loss': 0.0, + 'params_dict': None, + 'params_details': None, + 'results_metrics': results_metrics, + 'results_explanation': results_explanation, + 'total_profit': total_profit, + 'current_epoch': 1, + 'is_initial_point': True, + } + + result = hyperopt._format_explanation_string(results, 1) assert result.find(' 66.67%') assert result.find('Total profit 1.00000000 BTC') assert result.find('2.0000Σ %') @@ -488,7 +502,8 @@ def test_format_results(hyperopt): ] df = pd.DataFrame.from_records(trades, columns=labels) results_metrics = hyperopt._calculate_results_metrics(df) - result = hyperopt._format_explanation_string(results_metrics, 1) + results['total_profit'] = results_metrics['total_profit'] + result = hyperopt._format_explanation_string(results, 1) assert result.find('Total profit 1.00000000 EUR') From 86342efa7abd62d406e369211b317d418af02655 Mon Sep 17 00:00:00 2001 From: hroff-1902 Date: Sun, 1 Dec 2019 18:34:25 +0300 Subject: [PATCH 06/17] Adjust test --- tests/optimize/test_hyperopt.py | 7 +++++-- 1 file changed, 5 insertions(+), 2 deletions(-) diff --git a/tests/optimize/test_hyperopt.py b/tests/optimize/test_hyperopt.py index ff485d423..6a2d3f251 100644 --- a/tests/optimize/test_hyperopt.py +++ b/tests/optimize/test_hyperopt.py @@ -699,7 +699,10 @@ def test_print_json_spaces_all(mocker, default_conf, caplog, capsys) -> None: parallel = mocker.patch( 'freqtrade.optimize.hyperopt.Hyperopt.run_optimizer_parallel', - MagicMock(return_value=[{'loss': 1, 'results_explanation': 'foo result', 'params': {}}]) + MagicMock(return_value=[{'loss': 1, 'results_explanation': 'foo result', 'params': {}, + 'params_details': {'buy': {'mfi-value': None}, + 'sell': {'sell-mfi-value': None}, + 'roi': {}, 'stoploss': None}}]) ) patch_exchange(mocker) @@ -721,7 +724,7 @@ 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}' in out # noqa: E501 + assert '{"params":{"mfi-value":null,"sell-mfi-value":null},"minimal_roi":{},"stoploss":null}' in out # noqa: E501 assert dumper.called # Should be called twice, once for tickerdata, once to save evaluations assert dumper.call_count == 2 From 54694dd3a4e8db83e6ad6d9354c89be0dc7bd81b Mon Sep 17 00:00:00 2001 From: hroff-1902 Date: Wed, 4 Dec 2019 23:14:47 +0300 Subject: [PATCH 07/17] Manual merge of some conflicts in hyperopt --- freqtrade/optimize/hyperopt.py | 71 ++++++++++++++++++++-------------- 1 file changed, 42 insertions(+), 29 deletions(-) diff --git a/freqtrade/optimize/hyperopt.py b/freqtrade/optimize/hyperopt.py index d2404beb5..f932393aa 100644 --- a/freqtrade/optimize/hyperopt.py +++ b/freqtrade/optimize/hyperopt.py @@ -165,13 +165,16 @@ class Hyperopt: result: Dict = {} if self.has_space('buy'): - result['buy'] = {p.name: params.get(p.name) for p in self.hyperopt_space('buy')} + result['buy'] = {p.name: params.get(p.name) + for p in self.hyperopt_space('buy')} if self.has_space('sell'): - result['sell'] = {p.name: params.get(p.name) for p in self.hyperopt_space('sell')} + result['sell'] = {p.name: params.get(p.name) + for p in self.hyperopt_space('sell')} if self.has_space('roi'): result['roi'] = self.custom_hyperopt.generate_roi_table(params) if self.has_space('stoploss'): - result['stoploss'] = params.get('stoploss') + result['stoploss'] = {p.name: params.get(p.name) + for p in self.hyperopt_space('stoploss')} return result @@ -193,38 +196,48 @@ class Hyperopt: if print_json: result_dict: Dict = {} - result_params_dict: Dict = {} - if 'buy' in params: - result_params_dict.update(params['buy']) - if 'sell' in params: - result_params_dict.update(params['sell']) - if result_params_dict: - result_dict['params'] = result_params_dict - if 'roi' in params: + for s in ['buy', 'sell', 'roi', 'stoploss']: + Hyperopt._params_update_for_json(result_dict, params, s) + print(rapidjson.dumps(result_dict, default=str, number_mode=rapidjson.NM_NATIVE)) + + else: + Hyperopt._params_pretty_print(params, 'buy', "Buy hyperspace params:") + Hyperopt._params_pretty_print(params, 'sell', "Sell hyperspace params:") + Hyperopt._params_pretty_print(params, 'roi', "ROI table:") + Hyperopt._params_pretty_print(params, 'stoploss', "Stoploss:") + + @staticmethod + def _params_update_for_json(result_dict, params, space: str): + if space in params: + space_params = Hyperopt._space_params(params, space) + if space in ['buy', 'sell']: + result_dict.setdefault('params', {}).update(space_params) + elif space == 'roi': # Convert keys in min_roi dict to strings because # rapidjson cannot dump dicts with integer keys... # OrderedDict is used to keep the numeric order of the items # in the dict. result_dict['minimal_roi'] = OrderedDict( - (str(k), v) for k, v in params['roi'].items() + (str(k), v) for k, v in space_params.items() ) - if 'stoploss' in params: - result_dict['stoploss'] = params['stoploss'] - print(rapidjson.dumps(result_dict, default=str, number_mode=rapidjson.NM_NATIVE)) - else: - if 'buy' in params: - print('Buy hyperspace params:') - pprint(params['buy'], indent=4) - if 'sell' in params: - print('Sell hyperspace params:') - pprint(params['sell'], indent=4) - if 'roi' in params: - print("ROI table:") - # Round printed values to 5 digits after the decimal point - pprint(round_dict(params['roi'], 5), indent=4) - if 'stoploss' in params: - # Also round to 5 digits after the decimal point - print(f"Stoploss: {round(params['stoploss'], 5)}") + else: # 'stoploss' + result_dict.update(space_params) + + @staticmethod + def _params_pretty_print(params, space: str, header: str): + if space in params: + space_params = Hyperopt._space_params(params, space, 5) + if space == 'stoploss': + print(header, space_params.get('stoploss')) + else: + print(header) + pprint(space_params, indent=4) + + @staticmethod + def _space_params(params, space: str, r: int = None) -> Dict: + d = params[space] + # Round floats to `r` digits after the decimal point if requested + return round_dict(d, r) if r else d @staticmethod def is_best_loss(results, current_best_loss) -> bool: From b20bea8492cebaea3fb3fee50b96087b9d8263be Mon Sep 17 00:00:00 2001 From: hroff-1902 Date: Wed, 4 Dec 2019 23:15:19 +0300 Subject: [PATCH 08/17] Adjust tests --- tests/optimize/test_hyperopt.py | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/tests/optimize/test_hyperopt.py b/tests/optimize/test_hyperopt.py index 6a2d3f251..5617ee4db 100644 --- a/tests/optimize/test_hyperopt.py +++ b/tests/optimize/test_hyperopt.py @@ -637,7 +637,7 @@ def test_generate_optimizer(mocker, default_conf) -> None: 'sell-rsi-enabled': False, 'sell-rsi-value': 0, 'sell-trigger': 'macd_cross_signal'}, - 'stoploss': -0.4}, + 'stoploss': {'stoploss': -0.4}}, 'params_dict': optimizer_param, 'results_metrics': {'avg_profit': 2.3117, 'duration': 100.0, @@ -702,7 +702,7 @@ def test_print_json_spaces_all(mocker, default_conf, caplog, capsys) -> None: MagicMock(return_value=[{'loss': 1, 'results_explanation': 'foo result', 'params': {}, 'params_details': {'buy': {'mfi-value': None}, 'sell': {'sell-mfi-value': None}, - 'roi': {}, 'stoploss': None}}]) + 'roi': {}, 'stoploss': {'stoploss': None}}}]) ) patch_exchange(mocker) @@ -742,7 +742,7 @@ def test_print_json_spaces_roi_stoploss(mocker, default_conf, caplog, capsys) -> parallel = mocker.patch( 'freqtrade.optimize.hyperopt.Hyperopt.run_optimizer_parallel', MagicMock(return_value=[{'loss': 1, 'results_explanation': 'foo result', 'params': {}, - 'params_details': {'roi': {}, 'stoploss': None}}]) + 'params_details': {'roi': {}, 'stoploss': {'stoploss': None}}}]) ) patch_exchange(mocker) From b61f43835d20a5b3b18120bcc944fcbf244de8de Mon Sep 17 00:00:00 2001 From: hroff-1902 Date: Thu, 5 Dec 2019 01:11:06 +0300 Subject: [PATCH 09/17] Make flake happy --- freqtrade/optimize/hyperopt.py | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/freqtrade/optimize/hyperopt.py b/freqtrade/optimize/hyperopt.py index 97810d75a..eb3546756 100644 --- a/freqtrade/optimize/hyperopt.py +++ b/freqtrade/optimize/hyperopt.py @@ -354,11 +354,11 @@ class Hyperopt: if self.has_space('trailing'): self.backtesting.strategy.trailing_stop = params_dict['trailing_stop'] self.backtesting.strategy.trailing_stop_positive = \ - params_dict['trailing_stop_positive'] + params_dict['trailing_stop_positive'] self.backtesting.strategy.trailing_stop_positive_offset = \ - params_dict['trailing_stop_positive_offset'] + params_dict['trailing_stop_positive_offset'] self.backtesting.strategy.trailing_only_offset_is_reached = \ - params_dict['trailing_only_offset_is_reached'] + params_dict['trailing_only_offset_is_reached'] processed = load(self.tickerdata_pickle) From 4efd8b96e54120dccb84bf8580284343d0f06c56 Mon Sep 17 00:00:00 2001 From: hroff-1902 <47309513+hroff-1902@users.noreply.github.com> Date: Thu, 5 Dec 2019 14:16:18 +0300 Subject: [PATCH 10/17] Add description for hyperopt-list and hyperopt-show to the docs --- docs/utils.md | 65 +++++++++++++++++++++++++++++++++++++++++++++++++++ 1 file changed, 65 insertions(+) diff --git a/docs/utils.md b/docs/utils.md index 4ad32055a..e5359af83 100644 --- a/docs/utils.md +++ b/docs/utils.md @@ -262,3 +262,68 @@ Show whitelist when using a [dynamic pairlist](configuration.md#pairlists). ``` freqtrade test-pairlist --config config.json --quote USDT BTC ``` + +## List Hyperopt results + +You can list the hyperoptimization epochs the Hyperopt module evaluated previously with the `hyperopt-list` subcommand. + +``` +usage: freqtrade hyperopt-list [-h] [-v] [--logfile FILE] [-V] [-c PATH] + [-d PATH] [--userdir PATH] [--best] + [--profitable] [--no-color] [--print-json] + [--no-details] + +optional arguments: + -h, --help show this help message and exit + --best Select only best epochs. + --profitable Select only profitable epochs. + --no-color Disable colorization of hyperopt results. May be + useful if you are redirecting output to a file. + --print-json Print best result detailization in JSON format. + --no-details Do not print best epoch details. + ``` + +### Examples + +List all results, print details of the best result at the end: +``` +freqtrade hyperopt-list +``` + +List only epochs with positive profit. Do not print the details of the best epoch, so that the list can be iterated in a script: +``` +freqtrade hyperopt-list --profitable --no-details +``` + +## Show details of Hyperopt results + +You can show the details of any hyperoptimization epoch previously evaluated by the Hyperopt module with the `hyperopt-show` subcommand. + +``` +usage: freqtrade hyperopt-show [-h] [-v] [--logfile FILE] [-V] [-c PATH] + [-d PATH] [--userdir PATH] [--best] + [--profitable] [-n INT] [--print-json] + [--no-header] + +optional arguments: + -h, --help show this help message and exit + --best Select only best epochs. + --profitable Select only profitable epochs. + -n INT, --index INT Specify the index of the epoch to print details for. + --print-json Print best result detailization in JSON format. + --no-header Do not print epoch details header. +``` + +### Examples + +Print details for the epoch 168 (the number of the epoch is shown by the `hyperopt-list` subcommand or by Hyperopt itself during hyperoptimization run): + +``` +freqtrade hyperopt-show -n 168 +``` + +Prints JSON data with details for the last best epoch (i.e., the best of all epochs): + +``` +freqtrade hyperopt-show --best -n -1 --print-json --no-header +``` From 216094a7616cd3a5c68a2db46a22f099bb8bda9b Mon Sep 17 00:00:00 2001 From: hroff-1902 <47309513+hroff-1902@users.noreply.github.com> Date: Thu, 5 Dec 2019 14:30:55 +0300 Subject: [PATCH 11/17] Add reference to hyperopt-list and hyperopt-show to the Hyperopt doc --- docs/hyperopt.md | 6 +++++- 1 file changed, 5 insertions(+), 1 deletion(-) diff --git a/docs/hyperopt.md b/docs/hyperopt.md index 0661896a0..29a526bd0 100644 --- a/docs/hyperopt.md +++ b/docs/hyperopt.md @@ -440,7 +440,11 @@ If you are optimizing trailing stop values, Freqtrade creates the 'trailing' opt 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 +## Show details of Hyperopt results + +After you run Hyperopt for the desired amount of epochs, you can later list all results for analysis, select only best or profitable once, and show the details for any of the epochs previously evaluated. This can be done with the `hyperopt-list` and `hyperopt-show` subcommands. The usage of these subcommands is described in the [Utils](utils.md#list-hyperopt-results) chapter. + +## 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. From d21ae4edd35e48be9278c510325a256a66ea9eee Mon Sep 17 00:00:00 2001 From: hroff-1902 Date: Thu, 5 Dec 2019 23:29:31 +0300 Subject: [PATCH 12/17] Add fixes for comments in the review --- freqtrade/optimize/hyperopt.py | 7 ++++++- freqtrade/utils.py | 2 ++ 2 files changed, 8 insertions(+), 1 deletion(-) diff --git a/freqtrade/optimize/hyperopt.py b/freqtrade/optimize/hyperopt.py index eb3546756..277aee2fa 100644 --- a/freqtrade/optimize/hyperopt.py +++ b/freqtrade/optimize/hyperopt.py @@ -22,6 +22,7 @@ from pandas import DataFrame from skopt import Optimizer from skopt.space import Dimension +from freqtrade import OperationalException from freqtrade.data.history import get_timeframe, trim_dataframe from freqtrade.misc import plural, round_dict from freqtrade.optimize.backtesting import Backtesting @@ -181,7 +182,7 @@ class Hyperopt: return result - @staticmethod # noqa: C901 + @staticmethod def print_epoch_details(results, total_epochs, print_json: bool, no_header: bool = False, header_str: str = None) -> None: """ @@ -462,6 +463,10 @@ class Hyperopt: trials: List = [] if trials_file.is_file() and trials_file.stat().st_size > 0: trials = Hyperopt._read_trials(trials_file) + if trials[0].get('is_best') is None: + raise OperationalException( + "The file with Hyperopt results is incompatible with this version " + "of Freqtrade and cannot be loaded.") logger.info(f"Loaded {len(trials)} previous evaluations from disk.") return trials diff --git a/freqtrade/utils.py b/freqtrade/utils.py index 4d170985c..91758737e 100644 --- a/freqtrade/utils.py +++ b/freqtrade/utils.py @@ -360,6 +360,7 @@ def start_test_pairlist(args: Dict[str, Any]) -> None: def start_hyperopt_list(args: Dict[str, Any]) -> None: """ + List hyperopt epochs previously evaluated """ from freqtrade.optimize.hyperopt import Hyperopt @@ -403,6 +404,7 @@ def start_hyperopt_list(args: Dict[str, Any]) -> None: def start_hyperopt_show(args: Dict[str, Any]) -> None: """ + Show details of a hyperopt epoch previously evaluated """ from freqtrade.optimize.hyperopt import Hyperopt From 4b560880fd198c55809369bee208f0a0a3294728 Mon Sep 17 00:00:00 2001 From: hroff-1902 Date: Mon, 9 Dec 2019 04:37:58 +0300 Subject: [PATCH 13/17] Add tests for hyperopt-list, hyperopt-show --- tests/conftest.py | 5 +++ tests/test_utils.py | 93 ++++++++++++++++++++++++++++++++++++++++++++- 2 files changed, 97 insertions(+), 1 deletion(-) diff --git a/tests/conftest.py b/tests/conftest.py index 6c567dda8..020c29eb1 100644 --- a/tests/conftest.py +++ b/tests/conftest.py @@ -1414,3 +1414,8 @@ def open_trade(): open_date=arrow.utcnow().shift(minutes=-601).datetime, is_open=True ) + + +@pytest.fixture +def hyperopt_results(): + return [{'loss': 0.4366182531161519, 'params_dict': {'mfi-value': 15, 'fastd-value': 20, 'adx-value': 25, 'rsi-value': 28, 'mfi-enabled': False, 'fastd-enabled': True, 'adx-enabled': True, 'rsi-enabled': True, 'trigger': 'macd_cross_signal', 'sell-mfi-value': 88, 'sell-fastd-value': 97, 'sell-adx-value': 51, 'sell-rsi-value': 67, 'sell-mfi-enabled': False, 'sell-fastd-enabled': False, 'sell-adx-enabled': True, 'sell-rsi-enabled': True, 'sell-trigger': 'sell-bb_upper', 'roi_t1': 1190, 'roi_t2': 541, 'roi_t3': 408, 'roi_p1': 0.026035863879169705, 'roi_p2': 0.12508730043628782, 'roi_p3': 0.27766427921605896, 'stoploss': -0.2562930402099556}, 'params_details': {'buy': {'mfi-value': 15, 'fastd-value': 20, 'adx-value': 25, 'rsi-value': 28, 'mfi-enabled': False, 'fastd-enabled': True, 'adx-enabled': True, 'rsi-enabled': True, 'trigger': 'macd_cross_signal'}, 'sell': {'sell-mfi-value': 88, 'sell-fastd-value': 97, 'sell-adx-value': 51, 'sell-rsi-value': 67, 'sell-mfi-enabled': False, 'sell-fastd-enabled': False, 'sell-adx-enabled': True, 'sell-rsi-enabled': True, 'sell-trigger': 'sell-bb_upper'}, 'roi': {0: 0.4287874435315165, 408: 0.15112316431545753, 949: 0.026035863879169705, 2139: 0}, 'stoploss': {'stoploss': -0.2562930402099556}}, 'results_metrics': {'trade_count': 2, 'avg_profit': -1.254995, 'total_profit': -0.00125625, 'profit': -2.50999, 'duration': 3930.0}, 'results_explanation': ' 2 trades. Avg profit -1.25%. Total profit -0.00125625 BTC ( -2.51Σ%). Avg duration 3930.0 mins.', 'total_profit': -0.00125625, 'current_epoch': 1, 'is_initial_point': True, 'is_best': True}, {'loss': 20.0, 'params_dict': {'mfi-value': 17, 'fastd-value': 38, 'adx-value': 48, 'rsi-value': 22, 'mfi-enabled': True, 'fastd-enabled': False, 'adx-enabled': True, 'rsi-enabled': True, 'trigger': 'macd_cross_signal', 'sell-mfi-value': 96, 'sell-fastd-value': 68, 'sell-adx-value': 63, 'sell-rsi-value': 81, 'sell-mfi-enabled': False, 'sell-fastd-enabled': True, 'sell-adx-enabled': True, 'sell-rsi-enabled': True, 'sell-trigger': 'sell-sar_reversal', 'roi_t1': 334, 'roi_t2': 683, 'roi_t3': 140, 'roi_p1': 0.06403981740598495, 'roi_p2': 0.055519840060645045, 'roi_p3': 0.3253712811342459, 'stoploss': -0.338070047333259}, 'params_details': {'buy': {'mfi-value': 17, 'fastd-value': 38, 'adx-value': 48, 'rsi-value': 22, 'mfi-enabled': True, 'fastd-enabled': False, 'adx-enabled': True, 'rsi-enabled': True, 'trigger': 'macd_cross_signal'}, 'sell': {'sell-mfi-value': 96, 'sell-fastd-value': 68, 'sell-adx-value': 63, 'sell-rsi-value': 81, 'sell-mfi-enabled': False, 'sell-fastd-enabled': True, 'sell-adx-enabled': True, 'sell-rsi-enabled': True, 'sell-trigger': 'sell-sar_reversal'}, 'roi': {0: 0.4449309386008759, 140: 0.11955965746663, 823: 0.06403981740598495, 1157: 0}, 'stoploss': {'stoploss': -0.338070047333259}}, 'results_metrics': {'trade_count': 1, 'avg_profit': 0.12357, 'total_profit': 6.185e-05, 'profit': 0.12357, 'duration': 1200.0}, 'results_explanation': ' 1 trades. Avg profit 0.12%. Total profit 0.00006185 BTC ( 0.12Σ%). Avg duration 1200.0 mins.', 'total_profit': 6.185e-05, 'current_epoch': 2, 'is_initial_point': True, 'is_best': False}, {'loss': 14.241196856510731, 'params_dict': {'mfi-value': 25, 'fastd-value': 16, 'adx-value': 29, 'rsi-value': 20, 'mfi-enabled': False, 'fastd-enabled': False, 'adx-enabled': False, 'rsi-enabled': False, 'trigger': 'macd_cross_signal', 'sell-mfi-value': 98, 'sell-fastd-value': 72, 'sell-adx-value': 51, 'sell-rsi-value': 82, 'sell-mfi-enabled': True, 'sell-fastd-enabled': True, 'sell-adx-enabled': True, 'sell-rsi-enabled': True, 'sell-trigger': 'sell-macd_cross_signal', 'roi_t1': 889, 'roi_t2': 533, 'roi_t3': 263, 'roi_p1': 0.04759065393663096, 'roi_p2': 0.1488819964638463, 'roi_p3': 0.4102801822104605, 'stoploss': -0.05394588767607611}, 'params_details': {'buy': {'mfi-value': 25, 'fastd-value': 16, 'adx-value': 29, 'rsi-value': 20, 'mfi-enabled': False, 'fastd-enabled': False, 'adx-enabled': False, 'rsi-enabled': False, 'trigger': 'macd_cross_signal'}, 'sell': {'sell-mfi-value': 98, 'sell-fastd-value': 72, 'sell-adx-value': 51, 'sell-rsi-value': 82, 'sell-mfi-enabled': True, 'sell-fastd-enabled': True, 'sell-adx-enabled': True, 'sell-rsi-enabled': True, 'sell-trigger': 'sell-macd_cross_signal'}, 'roi': {0: 0.6067528326109377, 263: 0.19647265040047726, 796: 0.04759065393663096, 1685: 0}, 'stoploss': {'stoploss': -0.05394588767607611}}, 'results_metrics': {'trade_count': 621, 'avg_profit': -0.43883302093397747, 'total_profit': -0.13639474, 'profit': -272.515306, 'duration': 1691.207729468599}, 'results_explanation': ' 621 trades. Avg profit -0.44%. Total profit -0.13639474 BTC (-272.52Σ%). Avg duration 1691.2 mins.', 'total_profit': -0.13639474, 'current_epoch': 3, 'is_initial_point': True, 'is_best': False}, {'loss': 100000, 'params_dict': {'mfi-value': 13, 'fastd-value': 35, 'adx-value': 39, 'rsi-value': 29, 'mfi-enabled': True, 'fastd-enabled': False, 'adx-enabled': False, 'rsi-enabled': True, 'trigger': 'macd_cross_signal', 'sell-mfi-value': 87, 'sell-fastd-value': 54, 'sell-adx-value': 63, 'sell-rsi-value': 93, 'sell-mfi-enabled': False, 'sell-fastd-enabled': True, 'sell-adx-enabled': True, 'sell-rsi-enabled': True, 'sell-trigger': 'sell-bb_upper', 'roi_t1': 1402, 'roi_t2': 676, 'roi_t3': 215, 'roi_p1': 0.06264755784937427, 'roi_p2': 0.14258587851894644, 'roi_p3': 0.20671291201040828, 'stoploss': -0.11818343570194478}, 'params_details': {'buy': {'mfi-value': 13, 'fastd-value': 35, 'adx-value': 39, 'rsi-value': 29, 'mfi-enabled': True, 'fastd-enabled': False, 'adx-enabled': False, 'rsi-enabled': True, 'trigger': 'macd_cross_signal'}, 'sell': {'sell-mfi-value': 87, 'sell-fastd-value': 54, 'sell-adx-value': 63, 'sell-rsi-value': 93, 'sell-mfi-enabled': False, 'sell-fastd-enabled': True, 'sell-adx-enabled': True, 'sell-rsi-enabled': True, 'sell-trigger': 'sell-bb_upper'}, 'roi': {0: 0.411946348378729, 215: 0.2052334363683207, 891: 0.06264755784937427, 2293: 0}, 'stoploss': {'stoploss': -0.11818343570194478}}, 'results_metrics': {'trade_count': 0, 'avg_profit': None, 'total_profit': 0, 'profit': 0.0, 'duration': None}, 'results_explanation': ' 0 trades. Avg profit nan%. Total profit 0.00000000 BTC ( 0.00Σ%). Avg duration nan mins.', 'total_profit': 0, 'current_epoch': 4, 'is_initial_point': True, 'is_best': False}, {'loss': 0.22195522184191518, 'params_dict': {'mfi-value': 17, 'fastd-value': 21, 'adx-value': 38, 'rsi-value': 33, 'mfi-enabled': True, 'fastd-enabled': False, 'adx-enabled': True, 'rsi-enabled': False, 'trigger': 'macd_cross_signal', 'sell-mfi-value': 87, 'sell-fastd-value': 82, 'sell-adx-value': 78, 'sell-rsi-value': 69, 'sell-mfi-enabled': True, 'sell-fastd-enabled': False, 'sell-adx-enabled': True, 'sell-rsi-enabled': False, 'sell-trigger': 'sell-macd_cross_signal', 'roi_t1': 1269, 'roi_t2': 601, 'roi_t3': 444, 'roi_p1': 0.07280999507931168, 'roi_p2': 0.08946698095898986, 'roi_p3': 0.1454876733325284, 'stoploss': -0.18181041180901014}, 'params_details': {'buy': {'mfi-value': 17, 'fastd-value': 21, 'adx-value': 38, 'rsi-value': 33, 'mfi-enabled': True, 'fastd-enabled': False, 'adx-enabled': True, 'rsi-enabled': False, 'trigger': 'macd_cross_signal'}, 'sell': {'sell-mfi-value': 87, 'sell-fastd-value': 82, 'sell-adx-value': 78, 'sell-rsi-value': 69, 'sell-mfi-enabled': True, 'sell-fastd-enabled': False, 'sell-adx-enabled': True, 'sell-rsi-enabled': False, 'sell-trigger': 'sell-macd_cross_signal'}, 'roi': {0: 0.3077646493708299, 444: 0.16227697603830155, 1045: 0.07280999507931168, 2314: 0}, 'stoploss': {'stoploss': -0.18181041180901014}}, 'results_metrics': {'trade_count': 14, 'avg_profit': -0.3539515, 'total_profit': -0.002480140000000001, 'profit': -4.955321, 'duration': 3402.8571428571427}, 'results_explanation': ' 14 trades. Avg profit -0.35%. Total profit -0.00248014 BTC ( -4.96Σ%). Avg duration 3402.9 mins.', 'total_profit': -0.002480140000000001, 'current_epoch': 5, 'is_initial_point': True, 'is_best': True}, {'loss': 0.545315889154162, 'params_dict': {'mfi-value': 22, 'fastd-value': 43, 'adx-value': 46, 'rsi-value': 20, 'mfi-enabled': False, 'fastd-enabled': False, 'adx-enabled': True, 'rsi-enabled': True, 'trigger': 'bb_lower', 'sell-mfi-value': 87, 'sell-fastd-value': 65, 'sell-adx-value': 94, 'sell-rsi-value': 63, 'sell-mfi-enabled': False, 'sell-fastd-enabled': True, 'sell-adx-enabled': True, 'sell-rsi-enabled': True, 'sell-trigger': 'sell-macd_cross_signal', 'roi_t1': 319, 'roi_t2': 556, 'roi_t3': 216, 'roi_p1': 0.06251955472249589, 'roi_p2': 0.11659519602202795, 'roi_p3': 0.0953744132197762, 'stoploss': -0.024551752215582423}, 'params_details': {'buy': {'mfi-value': 22, 'fastd-value': 43, 'adx-value': 46, 'rsi-value': 20, 'mfi-enabled': False, 'fastd-enabled': False, 'adx-enabled': True, 'rsi-enabled': True, 'trigger': 'bb_lower'}, 'sell': {'sell-mfi-value': 87, 'sell-fastd-value': 65, 'sell-adx-value': 94, 'sell-rsi-value': 63, 'sell-mfi-enabled': False, 'sell-fastd-enabled': True, 'sell-adx-enabled': True, 'sell-rsi-enabled': True, 'sell-trigger': 'sell-macd_cross_signal'}, 'roi': {0: 0.2744891639643, 216: 0.17911475074452382, 772: 0.06251955472249589, 1091: 0}, 'stoploss': {'stoploss': -0.024551752215582423}}, 'results_metrics': {'trade_count': 39, 'avg_profit': -0.21400679487179478, 'total_profit': -0.0041773, 'profit': -8.346264999999997, 'duration': 636.9230769230769}, 'results_explanation': ' 39 trades. Avg profit -0.21%. Total profit -0.00417730 BTC ( -8.35Σ%). Avg duration 636.9 mins.', 'total_profit': -0.0041773, 'current_epoch': 6, 'is_initial_point': True, 'is_best': False}, {'loss': 4.713497421432944, 'params_dict': {'mfi-value': 13, 'fastd-value': 41, 'adx-value': 21, 'rsi-value': 29, 'mfi-enabled': False, 'fastd-enabled': True, 'adx-enabled': False, 'rsi-enabled': False, 'trigger': 'bb_lower', 'sell-mfi-value': 99, 'sell-fastd-value': 60, 'sell-adx-value': 81, 'sell-rsi-value': 69, 'sell-mfi-enabled': True, 'sell-fastd-enabled': True, 'sell-adx-enabled': True, 'sell-rsi-enabled': False, 'sell-trigger': 'sell-macd_cross_signal', 'roi_t1': 771, 'roi_t2': 620, 'roi_t3': 145, 'roi_p1': 0.0586919200378493, 'roi_p2': 0.04984118697312542, 'roi_p3': 0.37521058680247044, 'stoploss': -0.14613268022709905}, 'params_details': {'buy': {'mfi-value': 13, 'fastd-value': 41, 'adx-value': 21, 'rsi-value': 29, 'mfi-enabled': False, 'fastd-enabled': True, 'adx-enabled': False, 'rsi-enabled': False, 'trigger': 'bb_lower'}, 'sell': {'sell-mfi-value': 99, 'sell-fastd-value': 60, 'sell-adx-value': 81, 'sell-rsi-value': 69, 'sell-mfi-enabled': True, 'sell-fastd-enabled': True, 'sell-adx-enabled': True, 'sell-rsi-enabled': False, 'sell-trigger': 'sell-macd_cross_signal'}, 'roi': {0: 0.4837436938134452, 145: 0.10853310701097472, 765: 0.0586919200378493, 1536: 0}, 'stoploss': {'stoploss': -0.14613268022709905}}, 'results_metrics': {'trade_count': 318, 'avg_profit': -0.39833954716981146, 'total_profit': -0.06339929, 'profit': -126.67197600000004, 'duration': 3140.377358490566}, 'results_explanation': ' 318 trades. Avg profit -0.40%. Total profit -0.06339929 BTC (-126.67Σ%). Avg duration 3140.4 mins.', 'total_profit': -0.06339929, 'current_epoch': 7, 'is_initial_point': True, 'is_best': False}, {'loss': 20.0, 'params_dict': {'mfi-value': 24, 'fastd-value': 43, 'adx-value': 33, 'rsi-value': 20, 'mfi-enabled': False, 'fastd-enabled': True, 'adx-enabled': True, 'rsi-enabled': True, 'trigger': 'sar_reversal', 'sell-mfi-value': 89, 'sell-fastd-value': 74, 'sell-adx-value': 70, 'sell-rsi-value': 70, 'sell-mfi-enabled': False, 'sell-fastd-enabled': False, 'sell-adx-enabled': False, 'sell-rsi-enabled': True, 'sell-trigger': 'sell-sar_reversal', 'roi_t1': 1149, 'roi_t2': 375, 'roi_t3': 289, 'roi_p1': 0.05571820757172588, 'roi_p2': 0.0606240398618907, 'roi_p3': 0.1729012220156157, 'stoploss': -0.1588514289110401}, 'params_details': {'buy': {'mfi-value': 24, 'fastd-value': 43, 'adx-value': 33, 'rsi-value': 20, 'mfi-enabled': False, 'fastd-enabled': True, 'adx-enabled': True, 'rsi-enabled': True, 'trigger': 'sar_reversal'}, 'sell': {'sell-mfi-value': 89, 'sell-fastd-value': 74, 'sell-adx-value': 70, 'sell-rsi-value': 70, 'sell-mfi-enabled': False, 'sell-fastd-enabled': False, 'sell-adx-enabled': False, 'sell-rsi-enabled': True, 'sell-trigger': 'sell-sar_reversal'}, 'roi': {0: 0.2892434694492323, 289: 0.11634224743361658, 664: 0.05571820757172588, 1813: 0}, 'stoploss': {'stoploss': -0.1588514289110401}}, 'results_metrics': {'trade_count': 1, 'avg_profit': 0.0, 'total_profit': 0.0, 'profit': 0.0, 'duration': 5340.0}, 'results_explanation': ' 1 trades. Avg profit 0.00%. Total profit 0.00000000 BTC ( 0.00Σ%). Avg duration 5340.0 mins.', 'total_profit': 0.0, 'current_epoch': 8, 'is_initial_point': True, 'is_best': False}, {'loss': 2.4731817780991223, 'params_dict': {'mfi-value': 22, 'fastd-value': 20, 'adx-value': 29, 'rsi-value': 40, 'mfi-enabled': False, 'fastd-enabled': False, 'adx-enabled': False, 'rsi-enabled': False, 'trigger': 'sar_reversal', 'sell-mfi-value': 97, 'sell-fastd-value': 65, 'sell-adx-value': 81, 'sell-rsi-value': 64, 'sell-mfi-enabled': True, 'sell-fastd-enabled': True, 'sell-adx-enabled': True, 'sell-rsi-enabled': True, 'sell-trigger': 'sell-bb_upper', 'roi_t1': 1012, 'roi_t2': 584, 'roi_t3': 422, 'roi_p1': 0.036764323603472565, 'roi_p2': 0.10335480573205287, 'roi_p3': 0.10322347377503042, 'stoploss': -0.2780610808108503}, 'params_details': {'buy': {'mfi-value': 22, 'fastd-value': 20, 'adx-value': 29, 'rsi-value': 40, 'mfi-enabled': False, 'fastd-enabled': False, 'adx-enabled': False, 'rsi-enabled': False, 'trigger': 'sar_reversal'}, 'sell': {'sell-mfi-value': 97, 'sell-fastd-value': 65, 'sell-adx-value': 81, 'sell-rsi-value': 64, 'sell-mfi-enabled': True, 'sell-fastd-enabled': True, 'sell-adx-enabled': True, 'sell-rsi-enabled': True, 'sell-trigger': 'sell-bb_upper'}, 'roi': {0: 0.2433426031105559, 422: 0.14011912933552545, 1006: 0.036764323603472565, 2018: 0}, 'stoploss': {'stoploss': -0.2780610808108503}}, 'results_metrics': {'trade_count': 229, 'avg_profit': -0.38433433624454144, 'total_profit': -0.044050070000000004, 'profit': -88.01256299999999, 'duration': 6505.676855895196}, 'results_explanation': ' 229 trades. Avg profit -0.38%. Total profit -0.04405007 BTC ( -88.01Σ%). Avg duration 6505.7 mins.', 'total_profit': -0.044050070000000004, 'current_epoch': 9, 'is_initial_point': True, 'is_best': False}, {'loss': -0.2604606005845212, 'params_dict': {'mfi-value': 23, 'fastd-value': 24, 'adx-value': 22, 'rsi-value': 24, 'mfi-enabled': False, 'fastd-enabled': False, 'adx-enabled': False, 'rsi-enabled': True, 'trigger': 'macd_cross_signal', 'sell-mfi-value': 97, 'sell-fastd-value': 70, 'sell-adx-value': 64, 'sell-rsi-value': 80, 'sell-mfi-enabled': False, 'sell-fastd-enabled': True, 'sell-adx-enabled': True, 'sell-rsi-enabled': True, 'sell-trigger': 'sell-sar_reversal', 'roi_t1': 792, 'roi_t2': 464, 'roi_t3': 215, 'roi_p1': 0.04594053535385903, 'roi_p2': 0.09623192684243963, 'roi_p3': 0.04428219070850663, 'stoploss': -0.16992287161634415}, 'params_details': {'buy': {'mfi-value': 23, 'fastd-value': 24, 'adx-value': 22, 'rsi-value': 24, 'mfi-enabled': False, 'fastd-enabled': False, 'adx-enabled': False, 'rsi-enabled': True, 'trigger': 'macd_cross_signal'}, 'sell': {'sell-mfi-value': 97, 'sell-fastd-value': 70, 'sell-adx-value': 64, 'sell-rsi-value': 80, 'sell-mfi-enabled': False, 'sell-fastd-enabled': True, 'sell-adx-enabled': True, 'sell-rsi-enabled': True, 'sell-trigger': 'sell-sar_reversal'}, 'roi': {0: 0.18645465290480528, 215: 0.14217246219629864, 679: 0.04594053535385903, 1471: 0}, 'stoploss': {'stoploss': -0.16992287161634415}}, 'results_metrics': {'trade_count': 4, 'avg_profit': 0.1080385, 'total_profit': 0.00021629, 'profit': 0.432154, 'duration': 2850.0}, 'results_explanation': ' 4 trades. Avg profit 0.11%. Total profit 0.00021629 BTC ( 0.43Σ%). Avg duration 2850.0 mins.', 'total_profit': 0.00021629, 'current_epoch': 10, 'is_initial_point': True, 'is_best': True}, {'loss': 4.876465945994304, 'params_dict': {'mfi-value': 20, 'fastd-value': 32, 'adx-value': 49, 'rsi-value': 23, 'mfi-enabled': True, 'fastd-enabled': True, 'adx-enabled': False, 'rsi-enabled': False, 'trigger': 'bb_lower', 'sell-mfi-value': 75, 'sell-fastd-value': 56, 'sell-adx-value': 61, 'sell-rsi-value': 62, 'sell-mfi-enabled': False, 'sell-fastd-enabled': False, 'sell-adx-enabled': True, 'sell-rsi-enabled': True, 'sell-trigger': 'sell-macd_cross_signal', 'roi_t1': 579, 'roi_t2': 614, 'roi_t3': 273, 'roi_p1': 0.05307643172744114, 'roi_p2': 0.1352282078262871, 'roi_p3': 0.1913307406325751, 'stoploss': -0.25728526022513887}, 'params_details': {'buy': {'mfi-value': 20, 'fastd-value': 32, 'adx-value': 49, 'rsi-value': 23, 'mfi-enabled': True, 'fastd-enabled': True, 'adx-enabled': False, 'rsi-enabled': False, 'trigger': 'bb_lower'}, 'sell': {'sell-mfi-value': 75, 'sell-fastd-value': 56, 'sell-adx-value': 61, 'sell-rsi-value': 62, 'sell-mfi-enabled': False, 'sell-fastd-enabled': False, 'sell-adx-enabled': True, 'sell-rsi-enabled': True, 'sell-trigger': 'sell-macd_cross_signal'}, 'roi': {0: 0.3796353801863034, 273: 0.18830463955372825, 887: 0.05307643172744114, 1466: 0}, 'stoploss': {'stoploss': -0.25728526022513887}}, 'results_metrics': {'trade_count': 117, 'avg_profit': -1.2698609145299145, 'total_profit': -0.07436117, 'profit': -148.573727, 'duration': 4282.5641025641025}, 'results_explanation': ' 117 trades. Avg profit -1.27%. Total profit -0.07436117 BTC (-148.57Σ%). Avg duration 4282.6 mins.', 'total_profit': -0.07436117, 'current_epoch': 11, 'is_initial_point': True, 'is_best': False}, {'loss': 100000, 'params_dict': {'mfi-value': 10, 'fastd-value': 36, 'adx-value': 31, 'rsi-value': 22, 'mfi-enabled': True, 'fastd-enabled': True, 'adx-enabled': True, 'rsi-enabled': False, 'trigger': 'sar_reversal', 'sell-mfi-value': 80, 'sell-fastd-value': 71, 'sell-adx-value': 60, 'sell-rsi-value': 85, 'sell-mfi-enabled': False, 'sell-fastd-enabled': False, 'sell-adx-enabled': True, 'sell-rsi-enabled': True, 'sell-trigger': 'sell-bb_upper', 'roi_t1': 1156, 'roi_t2': 581, 'roi_t3': 408, 'roi_p1': 0.06860454019988212, 'roi_p2': 0.12473718444931989, 'roi_p3': 0.2896360635226823, 'stoploss': -0.30889015124682806}, 'params_details': {'buy': {'mfi-value': 10, 'fastd-value': 36, 'adx-value': 31, 'rsi-value': 22, 'mfi-enabled': True, 'fastd-enabled': True, 'adx-enabled': True, 'rsi-enabled': False, 'trigger': 'sar_reversal'}, 'sell': {'sell-mfi-value': 80, 'sell-fastd-value': 71, 'sell-adx-value': 60, 'sell-rsi-value': 85, 'sell-mfi-enabled': False, 'sell-fastd-enabled': False, 'sell-adx-enabled': True, 'sell-rsi-enabled': True, 'sell-trigger': 'sell-bb_upper'}, 'roi': {0: 0.4829777881718843, 408: 0.19334172464920202, 989: 0.06860454019988212, 2145: 0}, 'stoploss': {'stoploss': -0.30889015124682806}}, 'results_metrics': {'trade_count': 0, 'avg_profit': None, 'total_profit': 0, 'profit': 0.0, 'duration': None}, 'results_explanation': ' 0 trades. Avg profit nan%. Total profit 0.00000000 BTC ( 0.00Σ%). Avg duration nan mins.', 'total_profit': 0, 'current_epoch': 12, 'is_initial_point': True, 'is_best': False}] # noqa: E501 diff --git a/tests/test_utils.py b/tests/test_utils.py index 67ec8409b..3fc92e14b 100644 --- a/tests/test_utils.py +++ b/tests/test_utils.py @@ -10,7 +10,8 @@ from freqtrade.utils import (setup_utils_configuration, start_create_userdir, start_download_data, start_list_exchanges, start_list_markets, start_list_timeframes, start_new_hyperopt, start_new_strategy, - start_test_pairlist, start_trading) + start_test_pairlist, start_trading, + start_hyperopt_list, start_hyperopt_show) from tests.conftest import (get_args, log_has, log_has_re, patch_exchange, patched_configuration_load_config_file) @@ -657,3 +658,93 @@ def test_start_test_pairlist(mocker, caplog, markets, tickers, default_conf, cap captured = capsys.readouterr() assert re.match(r"Pairs for .*", captured.out) assert re.match("['ETH/BTC', 'TKN/BTC', 'BLK/BTC', 'LTC/BTC', 'XRP/BTC']", captured.out) + + +def test_hyperopt_list(mocker, capsys, hyperopt_results): + mocker.patch( + 'freqtrade.optimize.hyperopt.Hyperopt.load_previous_results', + MagicMock(return_value=hyperopt_results) + ) + + args = [ + "hyperopt-list", + "--no-details" + ] + start_hyperopt_list(get_args(args)) + captured = capsys.readouterr() + assert all(x in captured.out + for x in [" 1/12", " 2/12", " 3/12", " 4/12", " 5/12", + " 6/12", " 7/12", " 8/12", " 9/12", " 10/12", + " 11/12", " 12/12"]) + args = [ + "hyperopt-list", + "--best", + "--no-details" + ] + start_hyperopt_list(get_args(args)) + captured = capsys.readouterr() + assert all(x in captured.out + for x in [" 1/12", " 5/12", " 10/12"]) + assert all(x not in captured.out + for x in [" 2/12", " 3/12", " 4/12", " 6/12", " 7/12", " 8/12", " 9/12", + " 11/12", " 12/12"]) + args = [ + "hyperopt-list", + "--profitable", + "--no-details" + ] + start_hyperopt_list(get_args(args)) + captured = capsys.readouterr() + assert all(x in captured.out + for x in [" 2/12", " 10/12"]) + assert all(x not in captured.out + for x in [" 1/12", " 3/12", " 4/12", " 5/12", " 6/12", " 7/12", " 8/12", " 9/12", + " 11/12", " 12/12"]) + + +def test_hyperopt_show(mocker, capsys, hyperopt_results): + mocker.patch( + 'freqtrade.optimize.hyperopt.Hyperopt.load_previous_results', + MagicMock(return_value=hyperopt_results) + ) + + args = [ + "hyperopt-show", + ] + start_hyperopt_show(get_args(args)) + captured = capsys.readouterr() + assert " 12/12" in captured.out + + args = [ + "hyperopt-show", + "--best" + ] + start_hyperopt_show(get_args(args)) + captured = capsys.readouterr() + assert " 10/12" in captured.out + + args = [ + "hyperopt-show", + "-n", "1" + ] + start_hyperopt_show(get_args(args)) + captured = capsys.readouterr() + assert " 1/12" in captured.out + + args = [ + "hyperopt-show", + "--best", + "-n", "2" + ] + start_hyperopt_show(get_args(args)) + captured = capsys.readouterr() + assert " 5/12" in captured.out + + args = [ + "hyperopt-show", + "--best", + "-n", "-1" + ] + start_hyperopt_show(get_args(args)) + captured = capsys.readouterr() + assert " 10/12" in captured.out From a9f7e9fb7ab3dd9fcc286846b159575ee8b43ba7 Mon Sep 17 00:00:00 2001 From: hroff-1902 Date: Mon, 9 Dec 2019 12:49:04 +0300 Subject: [PATCH 14/17] Fix NO_CONF; fix tests --- freqtrade/configuration/arguments.py | 2 +- tests/test_utils.py | 32 +++++++++++++++++++++------- 2 files changed, 25 insertions(+), 9 deletions(-) diff --git a/freqtrade/configuration/arguments.py b/freqtrade/configuration/arguments.py index 799cd7d20..41c5c3957 100644 --- a/freqtrade/configuration/arguments.py +++ b/freqtrade/configuration/arguments.py @@ -62,7 +62,7 @@ ARGS_HYPEROPT_SHOW = ["hyperopt_list_best", "hyperopt_list_profitable", "hyperop "print_json", "hyperopt_show_no_header"] NO_CONF_REQURIED = ["download-data", "list-timeframes", "list-markets", "list-pairs", - "hyperopt_list", "hyperopt_show", "plot-dataframe", "plot-profit"] + "hyperopt-list", "hyperopt-show", "plot-dataframe", "plot-profit"] NO_CONF_ALLOWED = ["create-userdir", "list-exchanges", "new-hyperopt", "new-strategy"] diff --git a/tests/test_utils.py b/tests/test_utils.py index 3fc92e14b..f7bb12799 100644 --- a/tests/test_utils.py +++ b/tests/test_utils.py @@ -670,7 +670,9 @@ def test_hyperopt_list(mocker, capsys, hyperopt_results): "hyperopt-list", "--no-details" ] - start_hyperopt_list(get_args(args)) + pargs = get_args(args) + pargs['config'] = None + start_hyperopt_list(pargs) captured = capsys.readouterr() assert all(x in captured.out for x in [" 1/12", " 2/12", " 3/12", " 4/12", " 5/12", @@ -681,7 +683,9 @@ def test_hyperopt_list(mocker, capsys, hyperopt_results): "--best", "--no-details" ] - start_hyperopt_list(get_args(args)) + pargs = get_args(args) + pargs['config'] = None + start_hyperopt_list(pargs) captured = capsys.readouterr() assert all(x in captured.out for x in [" 1/12", " 5/12", " 10/12"]) @@ -693,7 +697,9 @@ def test_hyperopt_list(mocker, capsys, hyperopt_results): "--profitable", "--no-details" ] - start_hyperopt_list(get_args(args)) + pargs = get_args(args) + pargs['config'] = None + start_hyperopt_list(pargs) captured = capsys.readouterr() assert all(x in captured.out for x in [" 2/12", " 10/12"]) @@ -711,7 +717,9 @@ def test_hyperopt_show(mocker, capsys, hyperopt_results): args = [ "hyperopt-show", ] - start_hyperopt_show(get_args(args)) + pargs = get_args(args) + pargs['config'] = None + start_hyperopt_show(pargs) captured = capsys.readouterr() assert " 12/12" in captured.out @@ -719,7 +727,9 @@ def test_hyperopt_show(mocker, capsys, hyperopt_results): "hyperopt-show", "--best" ] - start_hyperopt_show(get_args(args)) + pargs = get_args(args) + pargs['config'] = None + start_hyperopt_show(pargs) captured = capsys.readouterr() assert " 10/12" in captured.out @@ -727,7 +737,9 @@ def test_hyperopt_show(mocker, capsys, hyperopt_results): "hyperopt-show", "-n", "1" ] - start_hyperopt_show(get_args(args)) + pargs = get_args(args) + pargs['config'] = None + start_hyperopt_show(pargs) captured = capsys.readouterr() assert " 1/12" in captured.out @@ -736,7 +748,9 @@ def test_hyperopt_show(mocker, capsys, hyperopt_results): "--best", "-n", "2" ] - start_hyperopt_show(get_args(args)) + pargs = get_args(args) + pargs['config'] = None + start_hyperopt_show(pargs) captured = capsys.readouterr() assert " 5/12" in captured.out @@ -745,6 +759,8 @@ def test_hyperopt_show(mocker, capsys, hyperopt_results): "--best", "-n", "-1" ] - start_hyperopt_show(get_args(args)) + pargs = get_args(args) + pargs['config'] = None + start_hyperopt_show(pargs) captured = capsys.readouterr() assert " 10/12" in captured.out From 5fc357ee10cdd98e932270fc2e64ac4d83fa57f6 Mon Sep 17 00:00:00 2001 From: hroff-1902 Date: Mon, 9 Dec 2019 23:43:50 +0300 Subject: [PATCH 15/17] Fix typo --- freqtrade/utils.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/freqtrade/utils.py b/freqtrade/utils.py index 91758737e..0ff71e45b 100644 --- a/freqtrade/utils.py +++ b/freqtrade/utils.py @@ -429,7 +429,7 @@ def start_hyperopt_show(args: Dict[str, Any]) -> None: f"The index of the epoch to show should be less than {total_epochs + 1}.") if n < -total_epochs: raise OperationalException( - f"The index of the epoch to showshould be greater than {-total_epochs - 1}.") + f"The index of the epoch to show should be greater than {-total_epochs - 1}.") # Translate epoch index from human-readable format to pythonic if n > 0: From 8431b54b216396f757e680c195cb49bbf116a156 Mon Sep 17 00:00:00 2001 From: hroff-1902 Date: Mon, 9 Dec 2019 23:50:40 +0300 Subject: [PATCH 16/17] Fix index limits handling --- freqtrade/utils.py | 9 +++++---- 1 file changed, 5 insertions(+), 4 deletions(-) diff --git a/freqtrade/utils.py b/freqtrade/utils.py index 0ff71e45b..230fcf268 100644 --- a/freqtrade/utils.py +++ b/freqtrade/utils.py @@ -422,14 +422,15 @@ def start_hyperopt_show(args: Dict[str, Any]) -> None: total_epochs = len(trials) trials = _hyperopt_filter_trials(trials, only_best, only_profitable) + trials_epochs = len(trials) n = config.get('hyperopt_show_index', -1) - if n > total_epochs: + if n > trials_epochs: raise OperationalException( - f"The index of the epoch to show should be less than {total_epochs + 1}.") - if n < -total_epochs: + f"The index of the epoch to show should be less than {trials_epochs + 1}.") + if n < -trials_epochs: raise OperationalException( - f"The index of the epoch to show should be greater than {-total_epochs - 1}.") + f"The index of the epoch to show should be greater than {-trials_epochs - 1}.") # Translate epoch index from human-readable format to pythonic if n > 0: From 18c73ceb907e7d9ce2c9252a48e2a13bbb36e0f3 Mon Sep 17 00:00:00 2001 From: hroff-1902 Date: Tue, 10 Dec 2019 00:22:11 +0300 Subject: [PATCH 17/17] Add tests for the last commit --- tests/test_utils.py | 22 ++++++++++++++++++++++ 1 file changed, 22 insertions(+) diff --git a/tests/test_utils.py b/tests/test_utils.py index f7bb12799..feba1ed59 100644 --- a/tests/test_utils.py +++ b/tests/test_utils.py @@ -764,3 +764,25 @@ def test_hyperopt_show(mocker, capsys, hyperopt_results): start_hyperopt_show(pargs) captured = capsys.readouterr() assert " 10/12" in captured.out + + args = [ + "hyperopt-show", + "--best", + "-n", "-4" + ] + pargs = get_args(args) + pargs['config'] = None + with pytest.raises(OperationalException, + match="The index of the epoch to show should be greater than -4."): + start_hyperopt_show(pargs) + + args = [ + "hyperopt-show", + "--best", + "-n", "4" + ] + pargs = get_args(args) + pargs['config'] = None + with pytest.raises(OperationalException, + match="The index of the epoch to show should be less than 4."): + start_hyperopt_show(pargs)