diff --git a/freqtrade/commands/hyperopt_commands.py b/freqtrade/commands/hyperopt_commands.py index 8c1c80d98..ccaa59e54 100755 --- a/freqtrade/commands/hyperopt_commands.py +++ b/freqtrade/commands/hyperopt_commands.py @@ -46,18 +46,12 @@ def start_hyperopt_list(args: Dict[str, Any]) -> None: trials = _hyperopt_filter_trials(trials, filteroptions) - # 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 filteroptions['only_best'], print_colorized) - + Hyperopt.print_result_table(config, trials, total_epochs, + not filteroptions['only_best'], print_colorized) except KeyboardInterrupt: print('User interrupted..') @@ -75,6 +69,12 @@ def start_hyperopt_show(args: Dict[str, Any]) -> None: config = setup_utils_configuration(args, RunMode.UTIL_NO_EXCHANGE) + print_json = config.get('print_json', False) + no_header = config.get('hyperopt_show_no_header', False) + trials_file = (config['user_data_dir'] / + 'hyperopt_results' / 'hyperopt_results.pickle') + n = config.get('hyperopt_show_index', -1) + filteroptions = { 'only_best': config.get('hyperopt_list_best', False), 'only_profitable': config.get('hyperopt_list_profitable', False), @@ -87,10 +87,6 @@ def start_hyperopt_show(args: Dict[str, Any]) -> None: 'filter_min_total_profit': config.get('hyperopt_list_min_total_profit', None), 'filter_max_total_profit': config.get('hyperopt_list_max_total_profit', None) } - 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) @@ -99,7 +95,6 @@ def start_hyperopt_show(args: Dict[str, Any]) -> None: trials = _hyperopt_filter_trials(trials, filteroptions) trials_epochs = len(trials) - n = config.get('hyperopt_show_index', -1) if n > trials_epochs: raise OperationalException( f"The index of the epoch to show should be less than {trials_epochs + 1}.") @@ -111,8 +106,6 @@ def start_hyperopt_show(args: Dict[str, Any]) -> None: 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, diff --git a/freqtrade/optimize/hyperopt.py b/freqtrade/optimize/hyperopt.py index 0f9076770..66ea18bd1 100644 --- a/freqtrade/optimize/hyperopt.py +++ b/freqtrade/optimize/hyperopt.py @@ -20,7 +20,8 @@ from colorama import Fore, Style from colorama import init as colorama_init from joblib import (Parallel, cpu_count, delayed, dump, load, wrap_non_picklable_objects) -from pandas import DataFrame +from pandas import DataFrame, json_normalize, isna +from tabulate import tabulate from freqtrade.data.converter import trim_dataframe from freqtrade.data.history import get_timerange @@ -296,6 +297,56 @@ class Hyperopt: f"{results['results_explanation']} " + f"Objective: {results['loss']:.5f}") + @staticmethod + def print_result_table(config: dict, results: list, total_epochs: int, highlight_best: bool, + print_colorized: bool) -> None: + """ + Log result table + """ + if not results: + return + + trials = json_normalize(results, max_level=1) + trials['Best'] = '' + trials = trials[['Best', 'current_epoch', 'results_metrics.trade_count', + 'results_metrics.avg_profit', 'results_metrics.total_profit', + 'results_metrics.profit', 'results_metrics.duration', + 'loss', 'is_initial_point', 'is_best']] + trials.columns = ['Best', 'Epoch', 'Trades', 'Avg profit', 'Total profit', + 'Profit', 'Avg duration', 'Objective', 'is_initial_point', 'is_best'] + trials['is_profit'] = False + trials.loc[trials['is_initial_point'], 'Best'] = '*' + trials.loc[trials['is_best'], 'Best'] = 'Best' + trials['Objective'] = trials['Objective'].astype(str) + trials.loc[trials['Total profit'] > 0, 'is_profit'] = True + trials['Trades'] = trials['Trades'].astype(str) + + trials['Epoch'] = trials['Epoch'].apply( + lambda x: "{}/{}".format(x, total_epochs)) + trials['Avg profit'] = trials['Avg profit'].apply( + lambda x: '{:,.2f}%'.format(x) if not isna(x) else x) + trials['Profit'] = trials['Profit'].apply( + lambda x: '{:,.2f}%'.format(x) if not isna(x) else x) + trials['Total profit'] = trials['Total profit'].apply( + lambda x: '{: 11.8f} '.format(x) + config['stake_currency'] if not isna(x) else x) + trials['Avg duration'] = trials['Avg duration'].apply( + lambda x: '{:,.1f}m'.format(x) if not isna(x) else x) + if print_colorized: + for i in range(len(trials)): + if trials.loc[i]['is_profit']: + for z in range(len(trials.loc[i])-3): + trials.iat[i, z] = "{}{}{}".format(Fore.GREEN, + str(trials.loc[i][z]), Fore.RESET) + if trials.loc[i]['is_best'] and highlight_best: + for z in range(len(trials.loc[i])-3): + trials.iat[i, z] = "{}{}{}".format(Style.BRIGHT, + str(trials.loc[i][z]), Style.RESET_ALL) + + trials = trials.drop(columns=['is_initial_point', 'is_best', 'is_profit']) + + print(tabulate(trials.to_dict(orient='list'), headers='keys', tablefmt='psql', + stralign="right")) + def has_space(self, space: str) -> bool: """ Tell if the space value is contained in the configuration diff --git a/tests/commands/test_commands.py b/tests/commands/test_commands.py index fd8df4b56..5f9bc0aa2 100644 --- a/tests/commands/test_commands.py +++ b/tests/commands/test_commands.py @@ -779,6 +779,20 @@ def test_hyperopt_list(mocker, capsys, hyperopt_results): 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"]) + args = [ + "hyperopt-list", + "--profitable" + ] + 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", "Best result:", "Buy hyperspace params", + "Sell hyperspace params", "ROI table", "Stoploss"]) + 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"]) args = [ "hyperopt-list", "--no-details",