diff --git a/freqtrade/commands/arguments.py b/freqtrade/commands/arguments.py index 11c1e9191..53cdda95d 100644 --- a/freqtrade/commands/arguments.py +++ b/freqtrade/commands/arguments.py @@ -23,7 +23,7 @@ ARGS_COMMON_OPTIMIZE = ["timeframe", "timerange", "dataformat_ohlcv", ARGS_BACKTEST = ARGS_COMMON_OPTIMIZE + ["position_stacking", "use_max_market_positions", "enable_protections", "dry_run_wallet", "timeframe_detail", - "strategy_list", "export", "exportfilename", "show_days"] + "strategy_list", "export", "exportfilename", "backtest_breakdown"] ARGS_HYPEROPT = ARGS_COMMON_OPTIMIZE + ["hyperopt", "hyperopt_path", "position_stacking", "use_max_market_positions", @@ -89,7 +89,7 @@ ARGS_HYPEROPT_LIST = ["hyperopt_list_best", "hyperopt_list_profitable", ARGS_HYPEROPT_SHOW = ["hyperopt_list_best", "hyperopt_list_profitable", "hyperopt_show_index", "print_json", "hyperoptexportfilename", "hyperopt_show_no_header", - "disableparamexport", "show_days"] + "disableparamexport", "backtest_breakdown"] NO_CONF_REQURIED = ["convert-data", "convert-trade-data", "download-data", "list-timeframes", "list-markets", "list-pairs", "list-strategies", "list-data", diff --git a/freqtrade/commands/cli_options.py b/freqtrade/commands/cli_options.py index 758e1d9ec..2c2c957df 100644 --- a/freqtrade/commands/cli_options.py +++ b/freqtrade/commands/cli_options.py @@ -193,11 +193,11 @@ AVAILABLE_CLI_OPTIONS = { type=float, metavar='FLOAT', ), - "show_days": Arg( - '--show-days', - help='Print days breakdown for backtest results', - action='store_true', - default=False, + "backtest_breakdown": Arg( + '--breakdown', + help='Show backtesting breakdown per [day, week, month].', + nargs='+', + choices=constants.BACKTEST_BREAKDOWNS ), # Edge "stoploss_range": Arg( diff --git a/freqtrade/commands/hyperopt_commands.py b/freqtrade/commands/hyperopt_commands.py index d2f8c188c..344828282 100755 --- a/freqtrade/commands/hyperopt_commands.py +++ b/freqtrade/commands/hyperopt_commands.py @@ -96,7 +96,7 @@ def start_hyperopt_show(args: Dict[str, Any]) -> None: if 'strategy_name' in metrics: strategy_name = metrics['strategy_name'] show_backtest_result(strategy_name, metrics, - metrics['stake_currency'], config.get('show_days', False)) + metrics['stake_currency'], config.get('backtest_breakdown', [])) HyperoptTools.try_export_params(config, strategy_name, val) diff --git a/freqtrade/configuration/configuration.py b/freqtrade/configuration/configuration.py index 845e87b83..c6bad9305 100644 --- a/freqtrade/configuration/configuration.py +++ b/freqtrade/configuration/configuration.py @@ -269,8 +269,8 @@ class Configuration: self._args_to_config(config, argname='export', logstring='Parameter --export detected: {} ...') - self._args_to_config(config, argname='show_days', - logstring='Parameter --show-days detected ...') + self._args_to_config(config, argname='backtest_breakdown', + logstring='Parameter --breakdown detected ...') self._args_to_config(config, argname='disableparamexport', logstring='Parameter --disableparamexport detected: {} ...') diff --git a/freqtrade/constants.py b/freqtrade/constants.py index c6b8f0e62..8bef6610c 100644 --- a/freqtrade/constants.py +++ b/freqtrade/constants.py @@ -32,6 +32,7 @@ AVAILABLE_PAIRLISTS = ['StaticPairList', 'VolumePairList', 'ShuffleFilter', 'SpreadFilter', 'VolatilityFilter'] AVAILABLE_PROTECTIONS = ['CooldownPeriod', 'LowProfitPairs', 'MaxDrawdown', 'StoplossGuard'] AVAILABLE_DATAHANDLERS = ['json', 'jsongz', 'hdf5'] +BACKTEST_BREAKDOWNS = ['day', 'week', 'month'] DRY_RUN_WALLET = 1000 DATETIME_PRINT_FORMAT = '%Y-%m-%d %H:%M:%S' MATH_CLOSE_PREC = 1e-14 # Precision used for float comparisons @@ -146,6 +147,10 @@ CONF_SCHEMA = { 'sell_profit_offset': {'type': 'number'}, 'ignore_roi_if_buy_signal': {'type': 'boolean'}, 'ignore_buying_expired_candle_after': {'type': 'number'}, + 'backtest_breakdown': { + 'type': 'array', + 'items': {'type': 'string', 'enum': BACKTEST_BREAKDOWNS} + }, 'bot_name': {'type': 'string'}, 'unfilledtimeout': { 'type': 'object', diff --git a/freqtrade/optimize/optimize_reports.py b/freqtrade/optimize/optimize_reports.py index 9a4591e67..a97f85637 100644 --- a/freqtrade/optimize/optimize_reports.py +++ b/freqtrade/optimize/optimize_reports.py @@ -213,17 +213,28 @@ def generate_edge_table(results: dict) -> str: floatfmt=floatfmt, tablefmt="orgtbl", stralign="right") # type: ignore -def generate_days_breakdown_stats(trade_list: List, starting_balance: int) -> List[Dict[str, Any]]: +def _get_resample_from_period(period: str) -> str: + if period == 'day': + return '1d' + if period == 'week': + return '1w' + if period == 'month': + return '1m' + raise ValueError(f"Period {period} is not supported.") + + +def generate_periodic_breakdown_stats(trade_list: List, period: str) -> List[Dict[str, Any]]: results = DataFrame.from_records(trade_list) results['close_date'] = to_datetime(results['close_date'], utc=True) - days = results.resample('1d', on='close_date') - days_stats = [] - for name, day in days: + resample = _get_resample_from_period(period) + period = results.resample(resample, on='close_date') + stats = [] + for name, day in period: profit_abs = day['profit_abs'].sum().round(10) wins = sum(day['profit_abs'] > 0) draws = sum(day['profit_abs'] == 0) loses = sum(day['profit_abs'] < 0) - days_stats.append( + stats.append( { 'date': name.strftime('%d/%m/%Y'), 'profit_abs': profit_abs, @@ -232,7 +243,7 @@ def generate_days_breakdown_stats(trade_list: List, starting_balance: int) -> Li 'loses': loses } ) - return days_stats + return stats def generate_trading_stats(results: DataFrame) -> Dict[str, Any]: @@ -529,8 +540,8 @@ def text_table_sell_reason(sell_reason_stats: List[Dict[str, Any]], stake_curren return tabulate(output, headers=headers, tablefmt="orgtbl", stralign="right") -def text_table_days_breakdown(days_breakdown_stats: List[Dict[str, Any]], - stake_currency: str) -> str: +def text_table_periodic_breakdown(days_breakdown_stats: List[Dict[str, Any]], + stake_currency: str, period: str) -> str: """ Generate small table with Backtest results by days :param days_breakdown_stats: Days breakdown metrics @@ -538,7 +549,7 @@ def text_table_days_breakdown(days_breakdown_stats: List[Dict[str, Any]], :return: pretty printed table with tabulate as string """ headers = [ - 'Day', + period.capitalize(), f'Tot Profit {stake_currency}', 'Wins', 'Draws', @@ -663,7 +674,7 @@ def text_table_add_metrics(strat_results: Dict) -> str: def show_backtest_result(strategy: str, results: Dict[str, Any], stake_currency: str, - show_days=False): + backtest_breakdown=[]): """ Print results for one strategy """ @@ -685,13 +696,13 @@ def show_backtest_result(strategy: str, results: Dict[str, Any], stake_currency: print(' LEFT OPEN TRADES REPORT '.center(len(table.splitlines()[0]), '=')) print(table) - if show_days: - days_breakdown_stats = generate_days_breakdown_stats( - trade_list=results['trades'], starting_balance=results['starting_balance']) - table = text_table_days_breakdown(days_breakdown_stats=days_breakdown_stats, - stake_currency=stake_currency) + for period in backtest_breakdown: + days_breakdown_stats = generate_periodic_breakdown_stats( + trade_list=results['trades'], period=period) + table = text_table_periodic_breakdown(days_breakdown_stats=days_breakdown_stats, + stake_currency=stake_currency, period=period) if isinstance(table, str) and len(table) > 0: - print(' DAYS BREAKDOWN '.center(len(table.splitlines()[0]), '=')) + print(f' {period.upper()} BREAKDOWN '.center(len(table.splitlines()[0]), '=')) print(table) table = text_table_add_metrics(results) @@ -708,7 +719,9 @@ def show_backtest_results(config: Dict, backtest_stats: Dict): stake_currency = config['stake_currency'] for strategy, results in backtest_stats['strategy'].items(): - show_backtest_result(strategy, results, stake_currency, config.get('show_days', False)) + show_backtest_result( + strategy, results, stake_currency, + config.get('backtest_breakdown', [])) if len(backtest_stats['strategy']) > 1: # Print Strategy summary table