diff --git a/docs/utils.md b/docs/utils.md index a65ba5db4..4a032db26 100644 --- a/docs/utils.md +++ b/docs/utils.md @@ -577,6 +577,46 @@ Common arguments: ``` +## Show previous Backtest results + +Allows you to show previous backtest results. +Adding `--show-pair-list` outputs a sorted pair list you can easily copy/paste into your configuration (omitting bad pairs). + +??? Warning "Strategy overfitting" + Only using winning pairs can lead to an overfitted strategy, which will not work well on future data. Make sure to extensively test your strategy in dry-run before risking real money. + +``` +usage: freqtrade backtesting-show [-h] [-v] [--logfile FILE] [-V] [-c PATH] + [-d PATH] [--userdir PATH] + [--export-filename PATH] [--show-pair-list] + +optional arguments: + -h, --help show this help message and exit + --export-filename PATH + Save backtest results to the file with this filename. + Requires `--export` to be set as well. Example: + `--export-filename=user_data/backtest_results/backtest + _today.json` + --show-pair-list Show backtesting pairlist sorted by profit. + +Common arguments: + -v, --verbose Verbose mode (-vv for more, -vvv to get all messages). + --logfile FILE Log to the file specified. Special values are: + 'syslog', 'journald'. See the documentation for more + details. + -V, --version show program's version number and exit + -c PATH, --config PATH + 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 + Path to userdata directory. + +``` + ## List Hyperopt results You can list the hyperoptimization epochs the Hyperopt module evaluated previously with the `hyperopt-list` sub-command. diff --git a/freqtrade/commands/__init__.py b/freqtrade/commands/__init__.py index 858c99acd..129836000 100644 --- a/freqtrade/commands/__init__.py +++ b/freqtrade/commands/__init__.py @@ -16,7 +16,8 @@ from freqtrade.commands.hyperopt_commands import start_hyperopt_list, start_hype from freqtrade.commands.list_commands import (start_list_exchanges, start_list_markets, start_list_strategies, start_list_timeframes, start_show_trades) -from freqtrade.commands.optimize_commands import start_backtesting, start_edge, start_hyperopt +from freqtrade.commands.optimize_commands import (start_backtesting, start_backtesting_show, + start_edge, start_hyperopt) from freqtrade.commands.pairlist_commands import start_test_pairlist from freqtrade.commands.plot_commands import start_plot_dataframe, start_plot_profit from freqtrade.commands.trade_commands import start_trading diff --git a/freqtrade/commands/arguments.py b/freqtrade/commands/arguments.py index 676e59282..032f7dd51 100644 --- a/freqtrade/commands/arguments.py +++ b/freqtrade/commands/arguments.py @@ -41,6 +41,8 @@ ARGS_LIST_STRATEGIES = ["strategy_path", "print_one_column", "print_colorized"] ARGS_LIST_HYPEROPTS = ["hyperopt_path", "print_one_column", "print_colorized"] +ARGS_BACKTEST_SHOW = ["exportfilename", "backtest_show_pair_list"] + ARGS_LIST_EXCHANGES = ["print_one_column", "list_exchanges_all"] ARGS_LIST_TIMEFRAMES = ["exchange", "print_one_column"] @@ -94,7 +96,7 @@ ARGS_HYPEROPT_SHOW = ["hyperopt_list_best", "hyperopt_list_profitable", "hyperop NO_CONF_REQURIED = ["convert-data", "convert-trade-data", "download-data", "list-timeframes", "list-markets", "list-pairs", "list-strategies", "list-data", - "hyperopt-list", "hyperopt-show", + "hyperopt-list", "hyperopt-show", "backtest-filter", "plot-dataframe", "plot-profit", "show-trades", "trades-to-ohlcv"] NO_CONF_ALLOWED = ["create-userdir", "list-exchanges", "new-strategy"] @@ -173,7 +175,8 @@ class Arguments: self.parser = argparse.ArgumentParser(description='Free, open source crypto trading bot') self._build_args(optionlist=['version'], parser=self.parser) - from freqtrade.commands import (start_backtesting, start_convert_data, start_convert_trades, + from freqtrade.commands import (start_backtesting, start_backtesting_show, + start_convert_data, start_convert_trades, start_create_userdir, start_download_data, start_edge, start_hyperopt, start_hyperopt_list, start_hyperopt_show, start_install_ui, start_list_data, start_list_exchanges, @@ -264,6 +267,15 @@ class Arguments: backtesting_cmd.set_defaults(func=start_backtesting) self._build_args(optionlist=ARGS_BACKTEST, parser=backtesting_cmd) + # Add backtesting-show subcommand + backtesting_show_cmd = subparsers.add_parser( + 'backtesting-show', + help='Show past Backtest results', + parents=[_common_parser], + ) + backtesting_show_cmd.set_defaults(func=start_backtesting_show) + self._build_args(optionlist=ARGS_BACKTEST_SHOW, parser=backtesting_show_cmd) + # Add edge subcommand edge_cmd = subparsers.add_parser('edge', help='Edge module.', parents=[_common_parser, _strategy_parser]) diff --git a/freqtrade/commands/cli_options.py b/freqtrade/commands/cli_options.py index 8d9b28c40..6aa4ed363 100644 --- a/freqtrade/commands/cli_options.py +++ b/freqtrade/commands/cli_options.py @@ -152,6 +152,12 @@ AVAILABLE_CLI_OPTIONS = { action='store_false', default=True, ), + "backtest_show_pair_list": Arg( + '--show-pair-list', + help='Show backtesting pairlist sorted by profit.', + action='store_true', + default=False, + ), "enable_protections": Arg( '--enable-protections', '--enableprotections', help='Enable protections for backtesting.' diff --git a/freqtrade/commands/optimize_commands.py b/freqtrade/commands/optimize_commands.py index 08174bde6..f230b696c 100644 --- a/freqtrade/commands/optimize_commands.py +++ b/freqtrade/commands/optimize_commands.py @@ -54,6 +54,22 @@ def start_backtesting(args: Dict[str, Any]) -> None: backtesting.start() +def start_backtesting_show(args: Dict[str, Any]) -> None: + """ + Show previous backtest result + """ + + config = setup_utils_configuration(args, RunMode.UTIL_NO_EXCHANGE) + + from freqtrade.data.btanalysis import load_backtest_stats + from freqtrade.optimize.optimize_reports import show_backtest_results, show_sorted_pairlist + + results = load_backtest_stats(config['exportfilename']) + + show_backtest_results(config, results) + show_sorted_pairlist(config, results) + + def start_hyperopt(args: Dict[str, Any]) -> None: """ Start hyperopt script diff --git a/freqtrade/configuration/configuration.py b/freqtrade/configuration/configuration.py index 822577916..f5a674878 100644 --- a/freqtrade/configuration/configuration.py +++ b/freqtrade/configuration/configuration.py @@ -245,6 +245,10 @@ class Configuration: self._args_to_config(config, argname='timeframe_detail', logstring='Parameter --timeframe-detail detected, ' 'using {} for intra-candle backtesting ...') + + self._args_to_config(config, argname='backtest_show_pair_list', + logstring='Parameter --show-pair-list detected.') + self._args_to_config(config, argname='stake_amount', logstring='Parameter --stake-amount detected, ' 'overriding stake_amount to: {} ...') diff --git a/freqtrade/optimize/optimize_reports.py b/freqtrade/optimize/optimize_reports.py index 96549316d..09de655ef 100644 --- a/freqtrade/optimize/optimize_reports.py +++ b/freqtrade/optimize/optimize_reports.py @@ -735,3 +735,13 @@ def show_backtest_results(config: Dict, backtest_stats: Dict): print(table) print('=' * len(table.splitlines()[0])) print('\nFor more details, please look at the detail tables above') + + +def show_sorted_pairlist(config: Dict, backtest_stats: Dict): + if config.get('backtest_show_pair_list', False): + for strategy, results in backtest_stats['strategy'].items(): + print(f"Pairs for Strategy {strategy}: \n[") + for result in results['results_per_pair']: + if result["key"] != 'TOTAL': + print(f'"{result["key"]}", // {round(result["profit_mean_pct"], 2)}%') + print("]") diff --git a/tests/commands/test_commands.py b/tests/commands/test_commands.py index 6e717afdf..e0d0cc38d 100644 --- a/tests/commands/test_commands.py +++ b/tests/commands/test_commands.py @@ -8,12 +8,12 @@ from zipfile import ZipFile import arrow import pytest -from freqtrade.commands import (start_convert_data, start_convert_trades, start_create_userdir, - start_download_data, start_hyperopt_list, start_hyperopt_show, - start_install_ui, start_list_data, start_list_exchanges, - start_list_markets, start_list_strategies, start_list_timeframes, - start_new_strategy, start_show_trades, start_test_pairlist, - start_trading, start_webserver) +from freqtrade.commands import (start_backtesting_show, start_convert_data, start_convert_trades, + start_create_userdir, start_download_data, start_hyperopt_list, + start_hyperopt_show, start_install_ui, start_list_data, + start_list_exchanges, start_list_markets, start_list_strategies, + start_list_timeframes, start_new_strategy, start_show_trades, + start_test_pairlist, start_trading, start_webserver) from freqtrade.commands.deploy_commands import (clean_ui_subdir, download_and_install_ui, get_ui_download_url, read_ui_version) from freqtrade.configuration import setup_utils_configuration @@ -1389,3 +1389,19 @@ def test_show_trades(mocker, fee, capsys, caplog): with pytest.raises(OperationalException, match=r"--db-url is required for this command."): start_show_trades(pargs) + + +def test_backtesting_show(mocker, testdatadir, capsys): + sbr = mocker.patch('freqtrade.optimize.optimize_reports.show_backtest_results') + args = [ + "backtesting-show", + "--export-filename", + f"{testdatadir / 'backtest-result_new.json'}", + "--show-pair-list" + ] + pargs = get_args(args) + pargs['config'] = None + start_backtesting_show(pargs) + assert sbr.call_count == 1 + out, err = capsys.readouterr() + assert "Pairs for Strategy" in out diff --git a/tests/optimize/test_optimize_reports.py b/tests/optimize/test_optimize_reports.py index b5eb09923..e56572522 100644 --- a/tests/optimize/test_optimize_reports.py +++ b/tests/optimize/test_optimize_reports.py @@ -10,7 +10,8 @@ from arrow import Arrow from freqtrade.configuration import TimeRange from freqtrade.constants import DATETIME_PRINT_FORMAT, LAST_BT_RESULT_FN from freqtrade.data import history -from freqtrade.data.btanalysis import get_latest_backtest_filename, load_backtest_data +from freqtrade.data.btanalysis import (get_latest_backtest_filename, load_backtest_data, + load_backtest_stats) from freqtrade.edge import PairInfo from freqtrade.enums import SellType from freqtrade.optimize.optimize_reports import (_get_resample_from_period, generate_backtest_stats, @@ -19,9 +20,9 @@ from freqtrade.optimize.optimize_reports import (_get_resample_from_period, gene generate_periodic_breakdown_stats, generate_sell_reason_stats, generate_strategy_comparison, - generate_trading_stats, store_backtest_stats, - text_table_bt_results, text_table_sell_reason, - text_table_strategy) + generate_trading_stats, show_sorted_pairlist, + store_backtest_stats, text_table_bt_results, + text_table_sell_reason, text_table_strategy) from freqtrade.resolvers.strategy_resolver import StrategyResolver from tests.data.test_history import _backup_file, _clean_test_file @@ -407,3 +408,16 @@ def test__get_resample_from_period(): assert _get_resample_from_period('month') == '1M' with pytest.raises(ValueError, match=r"Period noooo is not supported."): _get_resample_from_period('noooo') + + +def test_show_sorted_pairlist(testdatadir, default_conf, capsys): + filename = testdatadir / "backtest-result_new.json" + bt_data = load_backtest_stats(filename) + default_conf['backtest_show_pair_list'] = True + + show_sorted_pairlist(default_conf, bt_data) + + out, err = capsys.readouterr() + assert 'Pairs for Strategy StrategyTestV2: \n[' in out + assert 'TOTAL' not in out + assert '"ETH/BTC", // ' in out