From 7606d814fa3158f7383ecca47e60ebb9c8be9595 Mon Sep 17 00:00:00 2001 From: Fredrik81 Date: Thu, 5 Mar 2020 01:58:33 +0100 Subject: [PATCH 1/8] Initial work on csv-file export. Missing docs and tests --- freqtrade/commands/arguments.py | 3 +- freqtrade/commands/cli_options.py | 6 +++ freqtrade/commands/hyperopt_commands.py | 12 +++++ freqtrade/configuration/configuration.py | 3 ++ freqtrade/optimize/hyperopt.py | 58 ++++++++++++++++++++++++ 5 files changed, 81 insertions(+), 1 deletion(-) diff --git a/freqtrade/commands/arguments.py b/freqtrade/commands/arguments.py index 73e77d69d..8a8b06782 100644 --- a/freqtrade/commands/arguments.py +++ b/freqtrade/commands/arguments.py @@ -69,7 +69,8 @@ ARGS_HYPEROPT_LIST = ["hyperopt_list_best", "hyperopt_list_profitable", "hyperopt_list_min_avg_time", "hyperopt_list_max_avg_time", "hyperopt_list_min_avg_profit", "hyperopt_list_max_avg_profit", "hyperopt_list_min_total_profit", "hyperopt_list_max_total_profit", - "print_colorized", "print_json", "hyperopt_list_no_details"] + "print_colorized", "print_json", "hyperopt_list_no_details", + "export_csv"] ARGS_HYPEROPT_SHOW = ["hyperopt_list_best", "hyperopt_list_profitable", "hyperopt_show_index", "print_json", "hyperopt_show_no_header"] diff --git a/freqtrade/commands/cli_options.py b/freqtrade/commands/cli_options.py index ef674c5c2..77fba8eef 100644 --- a/freqtrade/commands/cli_options.py +++ b/freqtrade/commands/cli_options.py @@ -221,6 +221,12 @@ AVAILABLE_CLI_OPTIONS = { action='store_true', default=False, ), + "export_csv": Arg( + '--export-csv', + help='Export to CSV-File. Put + in front of filename to overwrite.' + 'Example: --export-csv +hyperopt.csv', + metavar='FILE', + ), "hyperopt_jobs": Arg( '-j', '--job-workers', help='The number of concurrently running jobs for hyperoptimization ' diff --git a/freqtrade/commands/hyperopt_commands.py b/freqtrade/commands/hyperopt_commands.py index 4803f6885..f4f119351 100755 --- a/freqtrade/commands/hyperopt_commands.py +++ b/freqtrade/commands/hyperopt_commands.py @@ -21,6 +21,7 @@ def start_hyperopt_list(args: Dict[str, Any]) -> None: print_colorized = config.get('print_colorized', False) print_json = config.get('print_json', False) + export_csv = config.get('export_csv', None) no_details = config.get('hyperopt_list_no_details', False) no_header = False @@ -59,6 +60,17 @@ def start_hyperopt_list(args: Dict[str, Any]) -> None: sorted_trials = sorted(trials, key=itemgetter('loss')) results = sorted_trials[0] Hyperopt.print_epoch_details(results, total_epochs, print_json, no_header) + print(export_csv) + if trials and export_csv: + overwrite_csv = False + if export_csv[0] == '+': + overwrite_csv = True + export_csv = export_csv[1:] + + Hyperopt.export_csv_file( + config, trials, total_epochs, + not filteroptions['only_best'], export_csv, overwrite_csv + ) def start_hyperopt_show(args: Dict[str, Any]) -> None: diff --git a/freqtrade/configuration/configuration.py b/freqtrade/configuration/configuration.py index 6a0441957..0645d72be 100644 --- a/freqtrade/configuration/configuration.py +++ b/freqtrade/configuration/configuration.py @@ -282,6 +282,9 @@ class Configuration: self._args_to_config(config, argname='print_json', logstring='Parameter --print-json detected ...') + self._args_to_config(config, argname='export_csv', + logstring='Parameter --export-csv detected: {}') + self._args_to_config(config, argname='hyperopt_jobs', logstring='Parameter -j/--job-workers detected: {}') diff --git a/freqtrade/optimize/hyperopt.py b/freqtrade/optimize/hyperopt.py index e9ab469f4..d397c720c 100644 --- a/freqtrade/optimize/hyperopt.py +++ b/freqtrade/optimize/hyperopt.py @@ -23,6 +23,8 @@ from joblib import (Parallel, cpu_count, delayed, dump, load, wrap_non_picklable_objects) from pandas import DataFrame, json_normalize, isna import tabulate +from os import path +import io from freqtrade.data.converter import trim_dataframe from freqtrade.data.history import get_timerange @@ -381,6 +383,62 @@ class Hyperopt: ) print(table) + @staticmethod + def export_csv_file(config: dict, results: list, total_epochs: int, highlight_best: bool, + csv_file: str, overwrite: bool) -> None: + """ + Log result to csv-file + """ + if not results: + return + + # Verification for owerwrite + if not overwrite and path.isfile(csv_file): + logging.error("CSV-File already exists and no overwrite specified!") + return + + try: + io.open(csv_file, 'w+').close() + except IOError: + logging.error("Filed to create/overwrite CSV-File!") + return + + trials = json_normalize(results, max_level=1) + trials['Best'] = '' + trials['Stake currency'] = config['stake_currency'] + trials = trials[['Best', 'current_epoch', 'results_metrics.trade_count', + 'results_metrics.avg_profit', 'results_metrics.total_profit', + 'Stake currency', 'results_metrics.profit', 'results_metrics.duration', + 'loss', 'is_initial_point', 'is_best']] + trials.columns = ['Best', 'Epoch', 'Trades', 'Avg profit', 'Total profit', 'Stake currency', + '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.loc[trials['Total profit'] > 0, 'is_profit'] = True + trials['Epoch'] = trials['Epoch'].astype(str) + trials['Trades'] = trials['Trades'].astype(str) + + trials['Total profit'] = trials['Total profit'].apply( + lambda x: '{:,.8f}'.format(x) if x != 0.0 else "--" + ) + trials['Profit'] = trials['Profit'].apply( + lambda x: '{:,.2f}'.format(x) if not isna(x) else "--" + ) + trials['Avg profit'] = trials['Avg profit'].apply( + lambda x: ('{:,.2f}%'.format(x)) if not isna(x) else "--" + ) + trials['Avg duration'] = trials['Avg duration'].apply( + lambda x: ('{:,.1f} m'.format(x)) if not isna(x) else "--" + ) + trials['Objective'] = trials['Objective'].apply( + lambda x: '{:,.5f}'.format(x) if x != 100000 else "N/A" + ) + + trials = trials.drop(columns=['is_initial_point', 'is_best', 'is_profit']) + trials.to_csv(csv_file, index=False, header=True, mode='w', encoding='UTF-8') + print("CSV-File created!") + def has_space(self, space: str) -> bool: """ Tell if the space value is contained in the configuration From 91db75a7073763d8c00c1057b5485d50e5103e46 Mon Sep 17 00:00:00 2001 From: Fredrik81 Date: Thu, 5 Mar 2020 19:43:43 +0100 Subject: [PATCH 2/8] Added tests and updated doc --- docs/utils.md | 3 +++ freqtrade/commands/cli_options.py | 3 ++- freqtrade/commands/hyperopt_commands.py | 13 +++++++------ tests/commands/test_commands.py | 16 +++++++++++++++- 4 files changed, 27 insertions(+), 8 deletions(-) diff --git a/docs/utils.md b/docs/utils.md index cdf0c31af..dd7a9dfe3 100644 --- a/docs/utils.md +++ b/docs/utils.md @@ -450,6 +450,9 @@ optional arguments: 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. + --export-csv FILE Export to CSV-File. Put + in front of filename to + overwrite. This will disable table print. Example: + --export-csv +hyperopt.csv Common arguments: -v, --verbose Verbose mode (-vv for more, -vvv to get all messages). diff --git a/freqtrade/commands/cli_options.py b/freqtrade/commands/cli_options.py index 77fba8eef..b782c2fb9 100644 --- a/freqtrade/commands/cli_options.py +++ b/freqtrade/commands/cli_options.py @@ -224,7 +224,8 @@ AVAILABLE_CLI_OPTIONS = { "export_csv": Arg( '--export-csv', help='Export to CSV-File. Put + in front of filename to overwrite.' - 'Example: --export-csv +hyperopt.csv', + ' This will disable table print.' + ' Example: --export-csv +hyperopt.csv', metavar='FILE', ), "hyperopt_jobs": Arg( diff --git a/freqtrade/commands/hyperopt_commands.py b/freqtrade/commands/hyperopt_commands.py index f4f119351..efc9aba88 100755 --- a/freqtrade/commands/hyperopt_commands.py +++ b/freqtrade/commands/hyperopt_commands.py @@ -50,17 +50,18 @@ def start_hyperopt_list(args: Dict[str, Any]) -> None: if print_colorized: colorama_init(autoreset=True) - try: - Hyperopt.print_result_table(config, trials, total_epochs, - not filteroptions['only_best'], print_colorized, 0) - except KeyboardInterrupt: - print('User interrupted..') + if not export_csv: + try: + Hyperopt.print_result_table(config, trials, total_epochs, + not filteroptions['only_best'], print_colorized, 0) + 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) - print(export_csv) + if trials and export_csv: overwrite_csv = False if export_csv[0] == '+': diff --git a/tests/commands/test_commands.py b/tests/commands/test_commands.py index 3e1c0a581..58e97fd49 100644 --- a/tests/commands/test_commands.py +++ b/tests/commands/test_commands.py @@ -902,7 +902,21 @@ 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", " 7/12", " 8/12" " 9/12", " 10/12", " 11/12", " 12/12"]) - + args = [ + "hyperopt-list", + "--no-details", + "--export-csv", "+test_file.csv" + ] + pargs = get_args(args) + pargs['config'] = None + start_hyperopt_list(pargs) + captured = capsys.readouterr() + assert all(x in captured.out + for x in ["CSV-File created!"]) + f = Path("test_file.csv") + assert 'Best,1,2,-1.25%,-0.00125625,,-2.51,"3,930.0 m",0.43662' in f.read_text() + assert f.is_file() + f.unlink() def test_hyperopt_show(mocker, capsys, hyperopt_results): mocker.patch( From f0d56e23a340e77dac0c6cd64ea3de2eeaedc1c4 Mon Sep 17 00:00:00 2001 From: Fredrik81 Date: Thu, 5 Mar 2020 19:58:01 +0100 Subject: [PATCH 3/8] PEP8 fix --- tests/commands/test_commands.py | 1 + 1 file changed, 1 insertion(+) diff --git a/tests/commands/test_commands.py b/tests/commands/test_commands.py index 58e97fd49..f404a4671 100644 --- a/tests/commands/test_commands.py +++ b/tests/commands/test_commands.py @@ -918,6 +918,7 @@ def test_hyperopt_list(mocker, capsys, hyperopt_results): assert f.is_file() f.unlink() + def test_hyperopt_show(mocker, capsys, hyperopt_results): mocker.patch( 'freqtrade.optimize.hyperopt.Hyperopt.load_previous_results', From 4ad93ed6bbe2f9f5b653552d05f7d9b73fb9a30c Mon Sep 17 00:00:00 2001 From: Fredrik81 Date: Sun, 8 Mar 2020 22:41:05 +0100 Subject: [PATCH 4/8] Changed output for null columns --- freqtrade/optimize/hyperopt.py | 10 +++++----- 1 file changed, 5 insertions(+), 5 deletions(-) diff --git a/freqtrade/optimize/hyperopt.py b/freqtrade/optimize/hyperopt.py index d397c720c..f0c992760 100644 --- a/freqtrade/optimize/hyperopt.py +++ b/freqtrade/optimize/hyperopt.py @@ -420,19 +420,19 @@ class Hyperopt: trials['Trades'] = trials['Trades'].astype(str) trials['Total profit'] = trials['Total profit'].apply( - lambda x: '{:,.8f}'.format(x) if x != 0.0 else "--" + lambda x: '{:,.8f}'.format(x) if x != 0.0 else "" ) trials['Profit'] = trials['Profit'].apply( - lambda x: '{:,.2f}'.format(x) if not isna(x) else "--" + lambda x: '{:,.2f}'.format(x) if not isna(x) else "" ) trials['Avg profit'] = trials['Avg profit'].apply( - lambda x: ('{:,.2f}%'.format(x)) if not isna(x) else "--" + lambda x: ('{:,.2f}%'.format(x)) if not isna(x) else "" ) trials['Avg duration'] = trials['Avg duration'].apply( - lambda x: ('{:,.1f} m'.format(x)) if not isna(x) else "--" + lambda x: ('{:,.1f} m'.format(x)) if not isna(x) else "" ) trials['Objective'] = trials['Objective'].apply( - lambda x: '{:,.5f}'.format(x) if x != 100000 else "N/A" + lambda x: '{:,.5f}'.format(x) if x != 100000 else "" ) trials = trials.drop(columns=['is_initial_point', 'is_best', 'is_profit']) From cb419614cd10ae7e4fb063e4bc2b28c795a0ce31 Mon Sep 17 00:00:00 2001 From: Fredrik81 Date: Sun, 8 Mar 2020 23:00:21 +0100 Subject: [PATCH 5/8] Spelling miss --- freqtrade/optimize/hyperopt.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/freqtrade/optimize/hyperopt.py b/freqtrade/optimize/hyperopt.py index f0c992760..d12e9cd45 100644 --- a/freqtrade/optimize/hyperopt.py +++ b/freqtrade/optimize/hyperopt.py @@ -392,7 +392,7 @@ class Hyperopt: if not results: return - # Verification for owerwrite + # Verification for overwrite if not overwrite and path.isfile(csv_file): logging.error("CSV-File already exists and no overwrite specified!") return From 2f5fc731bba51239a3195cbd0fb79e9e58a20d33 Mon Sep 17 00:00:00 2001 From: Fredrik81 Date: Mon, 9 Mar 2020 18:53:30 +0100 Subject: [PATCH 6/8] Removed overwrite option --- docs/utils.md | 13 +++++++------ freqtrade/commands/cli_options.py | 4 ++-- freqtrade/commands/hyperopt_commands.py | 8 +------- freqtrade/optimize/hyperopt.py | 6 +++--- tests/commands/test_commands.py | 2 +- 5 files changed, 14 insertions(+), 19 deletions(-) diff --git a/docs/utils.md b/docs/utils.md index dd7a9dfe3..eb71c509c 100644 --- a/docs/utils.md +++ b/docs/utils.md @@ -429,6 +429,7 @@ usage: freqtrade hyperopt-list [-h] [-v] [--logfile FILE] [-V] [-c PATH] [--min-total-profit FLOAT] [--max-total-profit FLOAT] [--no-color] [--print-json] [--no-details] + [--export-csv FILE] optional arguments: -h, --help show this help message and exit @@ -450,9 +451,8 @@ optional arguments: 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. - --export-csv FILE Export to CSV-File. Put + in front of filename to - overwrite. This will disable table print. Example: - --export-csv +hyperopt.csv + --export-csv FILE Export to CSV-File. This will disable table print. + Example: --export-csv hyperopt.csv Common arguments: -v, --verbose Verbose mode (-vv for more, -vvv to get all messages). @@ -461,9 +461,10 @@ Common arguments: details. -V, --version show program's version number and exit -c PATH, --config PATH - Specify configuration file (default: `config.json`). - Multiple --config options may be used. Can be set to - `-` to read config from stdin. + Specify configuration file (default: + `userdir/config.json` or `config.json` whichever + exists). Multiple --config options may be used. Can be + set to `-` to read config from stdin. -d PATH, --datadir PATH Path to directory with historical backtesting data. --userdir PATH, --user-data-dir PATH diff --git a/freqtrade/commands/cli_options.py b/freqtrade/commands/cli_options.py index b782c2fb9..8548bd887 100644 --- a/freqtrade/commands/cli_options.py +++ b/freqtrade/commands/cli_options.py @@ -223,9 +223,9 @@ AVAILABLE_CLI_OPTIONS = { ), "export_csv": Arg( '--export-csv', - help='Export to CSV-File. Put + in front of filename to overwrite.' + help='Export to CSV-File.' ' This will disable table print.' - ' Example: --export-csv +hyperopt.csv', + ' Example: --export-csv hyperopt.csv', metavar='FILE', ), "hyperopt_jobs": Arg( diff --git a/freqtrade/commands/hyperopt_commands.py b/freqtrade/commands/hyperopt_commands.py index efc9aba88..5b2388252 100755 --- a/freqtrade/commands/hyperopt_commands.py +++ b/freqtrade/commands/hyperopt_commands.py @@ -63,14 +63,8 @@ def start_hyperopt_list(args: Dict[str, Any]) -> None: Hyperopt.print_epoch_details(results, total_epochs, print_json, no_header) if trials and export_csv: - overwrite_csv = False - if export_csv[0] == '+': - overwrite_csv = True - export_csv = export_csv[1:] - Hyperopt.export_csv_file( - config, trials, total_epochs, - not filteroptions['only_best'], export_csv, overwrite_csv + config, trials, total_epochs, not filteroptions['only_best'], export_csv ) diff --git a/freqtrade/optimize/hyperopt.py b/freqtrade/optimize/hyperopt.py index d12e9cd45..8e0eba4d7 100644 --- a/freqtrade/optimize/hyperopt.py +++ b/freqtrade/optimize/hyperopt.py @@ -385,7 +385,7 @@ class Hyperopt: @staticmethod def export_csv_file(config: dict, results: list, total_epochs: int, highlight_best: bool, - csv_file: str, overwrite: bool) -> None: + csv_file: str) -> None: """ Log result to csv-file """ @@ -393,8 +393,8 @@ class Hyperopt: return # Verification for overwrite - if not overwrite and path.isfile(csv_file): - logging.error("CSV-File already exists and no overwrite specified!") + if path.isfile(csv_file): + logging.error("CSV-File already exists!") return try: diff --git a/tests/commands/test_commands.py b/tests/commands/test_commands.py index f404a4671..4530cd03d 100644 --- a/tests/commands/test_commands.py +++ b/tests/commands/test_commands.py @@ -905,7 +905,7 @@ def test_hyperopt_list(mocker, capsys, hyperopt_results): args = [ "hyperopt-list", "--no-details", - "--export-csv", "+test_file.csv" + "--export-csv", "test_file.csv" ] pargs = get_args(args) pargs['config'] = None From bd158eefd29a3cdec29887e7f54895935a379179 Mon Sep 17 00:00:00 2001 From: Fredrik81 Date: Tue, 10 Mar 2020 03:02:52 +0100 Subject: [PATCH 7/8] Fixed loggin --- freqtrade/optimize/hyperopt.py | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/freqtrade/optimize/hyperopt.py b/freqtrade/optimize/hyperopt.py index 8e0eba4d7..1b6343208 100644 --- a/freqtrade/optimize/hyperopt.py +++ b/freqtrade/optimize/hyperopt.py @@ -394,13 +394,13 @@ class Hyperopt: # Verification for overwrite if path.isfile(csv_file): - logging.error("CSV-File already exists!") + logger.error("CSV-File already exists!") return try: io.open(csv_file, 'w+').close() except IOError: - logging.error("Filed to create/overwrite CSV-File!") + logger.error("Filed to create CSV-File!") return trials = json_normalize(results, max_level=1) From f148b5f73450861d8f416f0e3b03d355c568ec6e Mon Sep 17 00:00:00 2001 From: hroff-1902 <47309513+hroff-1902@users.noreply.github.com> Date: Tue, 10 Mar 2020 10:38:37 +0300 Subject: [PATCH 8/8] cosmetics in lambdas --- freqtrade/optimize/hyperopt.py | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/freqtrade/optimize/hyperopt.py b/freqtrade/optimize/hyperopt.py index 1b6343208..4c32a0543 100644 --- a/freqtrade/optimize/hyperopt.py +++ b/freqtrade/optimize/hyperopt.py @@ -332,10 +332,10 @@ class Hyperopt: lambda x: '{}/{}'.format(str(x).rjust(len(str(total_epochs)), ' '), total_epochs) ) trials['Avg profit'] = trials['Avg profit'].apply( - lambda x: ('{:,.2f}%'.format(x)).rjust(7, ' ') if not isna(x) else "--".rjust(7, ' ') + lambda x: '{:,.2f}%'.format(x).rjust(7, ' ') if not isna(x) else "--".rjust(7, ' ') ) trials['Avg duration'] = trials['Avg duration'].apply( - lambda x: ('{:,.1f} m'.format(x)).rjust(7, ' ') if not isna(x) else "--".rjust(7, ' ') + lambda x: '{:,.1f} m'.format(x).rjust(7, ' ') if not isna(x) else "--".rjust(7, ' ') ) trials['Objective'] = trials['Objective'].apply( lambda x: '{:,.5f}'.format(x).rjust(8, ' ') if x != 100000 else "N/A".rjust(8, ' ') @@ -426,10 +426,10 @@ class Hyperopt: lambda x: '{:,.2f}'.format(x) if not isna(x) else "" ) trials['Avg profit'] = trials['Avg profit'].apply( - lambda x: ('{:,.2f}%'.format(x)) if not isna(x) else "" + lambda x: '{:,.2f}%'.format(x) if not isna(x) else "" ) trials['Avg duration'] = trials['Avg duration'].apply( - lambda x: ('{:,.1f} m'.format(x)) if not isna(x) else "" + lambda x: '{:,.1f} m'.format(x) if not isna(x) else "" ) trials['Objective'] = trials['Objective'].apply( lambda x: '{:,.5f}'.format(x) if x != 100000 else ""