From 9994fce5778efe937866a8fdc26dc95abe8fdfa7 Mon Sep 17 00:00:00 2001 From: Matthias Date: Sun, 25 Apr 2021 15:44:21 +0200 Subject: [PATCH] Extract generation of report for one strategy to it's own method --- freqtrade/optimize/optimize_reports.py | 257 +++++++++++++------------ 1 file changed, 139 insertions(+), 118 deletions(-) diff --git a/freqtrade/optimize/optimize_reports.py b/freqtrade/optimize/optimize_reports.py index 91ca619be..682483adb 100644 --- a/freqtrade/optimize/optimize_reports.py +++ b/freqtrade/optimize/optimize_reports.py @@ -235,6 +235,142 @@ def generate_daily_stats(results: DataFrame) -> Dict[str, Any]: } +def generate_strategy_stats(btdata: Dict[str, DataFrame], + strategy: str, + content: Dict[str, Any], + min_date: Arrow, max_date: Arrow, + market_change: float + ) -> Dict[str, Any]: + """ + :param btdata: Backtest data + :param strategy: Strategy name + :param content: Backtest result data in the format: + {'results: results, 'config: config}}. + :param min_date: Backtest start date + :param max_date: Backtest end date + :param market_change: float indicating the market change + :return: Dictionary containing results per strategy and a stratgy summary. + """ + results: Dict[str, DataFrame] = content['results'] + if not isinstance(results, DataFrame): + return + config = content['config'] + max_open_trades = min(config['max_open_trades'], len(btdata.keys())) + starting_balance = config['dry_run_wallet'] + stake_currency = config['stake_currency'] + + pair_results = generate_pair_metrics(btdata, stake_currency=stake_currency, + starting_balance=starting_balance, + results=results, skip_nan=False) + sell_reason_stats = generate_sell_reason_stats(max_open_trades=max_open_trades, + results=results) + left_open_results = generate_pair_metrics(btdata, stake_currency=stake_currency, + starting_balance=starting_balance, + results=results.loc[results['is_open']], + skip_nan=True) + daily_stats = generate_daily_stats(results) + best_pair = max([pair for pair in pair_results if pair['key'] != 'TOTAL'], + key=lambda x: x['profit_sum']) if len(pair_results) > 1 else None + worst_pair = min([pair for pair in pair_results if pair['key'] != 'TOTAL'], + key=lambda x: x['profit_sum']) if len(pair_results) > 1 else None + results['open_timestamp'] = results['open_date'].astype(int64) // 1e6 + results['close_timestamp'] = results['close_date'].astype(int64) // 1e6 + + backtest_days = (max_date - min_date).days + strat_stats = { + 'trades': results.to_dict(orient='records'), + 'locks': [lock.to_json() for lock in content['locks']], + 'best_pair': best_pair, + 'worst_pair': worst_pair, + 'results_per_pair': pair_results, + 'sell_reason_summary': sell_reason_stats, + 'left_open_trades': left_open_results, + 'total_trades': len(results), + 'total_volume': float(results['stake_amount'].sum()), + 'avg_stake_amount': results['stake_amount'].mean() if len(results) > 0 else 0, + 'profit_mean': results['profit_ratio'].mean() if len(results) > 0 else 0, + 'profit_total': results['profit_abs'].sum() / starting_balance, + 'profit_total_abs': results['profit_abs'].sum(), + 'backtest_start': min_date.datetime, + 'backtest_start_ts': min_date.int_timestamp * 1000, + 'backtest_end': max_date.datetime, + 'backtest_end_ts': max_date.int_timestamp * 1000, + 'backtest_days': backtest_days, + + 'backtest_run_start_ts': content['backtest_start_time'], + 'backtest_run_end_ts': content['backtest_end_time'], + + 'trades_per_day': round(len(results) / backtest_days, 2) if backtest_days > 0 else 0, + 'market_change': market_change, + 'pairlist': list(btdata.keys()), + 'stake_amount': config['stake_amount'], + 'stake_currency': config['stake_currency'], + 'stake_currency_decimals': decimals_per_coin(config['stake_currency']), + 'starting_balance': starting_balance, + 'dry_run_wallet': starting_balance, + 'final_balance': content['final_balance'], + 'max_open_trades': max_open_trades, + 'max_open_trades_setting': (config['max_open_trades'] + if config['max_open_trades'] != float('inf') else -1), + 'timeframe': config['timeframe'], + 'timerange': config.get('timerange', ''), + 'enable_protections': config.get('enable_protections', False), + 'strategy_name': strategy, + # Parameters relevant for backtesting + 'stoploss': config['stoploss'], + 'trailing_stop': config.get('trailing_stop', False), + 'trailing_stop_positive': config.get('trailing_stop_positive'), + 'trailing_stop_positive_offset': config.get('trailing_stop_positive_offset', 0.0), + 'trailing_only_offset_is_reached': config.get('trailing_only_offset_is_reached', False), + 'use_custom_stoploss': config.get('use_custom_stoploss', False), + 'minimal_roi': config['minimal_roi'], + 'use_sell_signal': config['ask_strategy']['use_sell_signal'], + 'sell_profit_only': config['ask_strategy']['sell_profit_only'], + 'sell_profit_offset': config['ask_strategy']['sell_profit_offset'], + 'ignore_roi_if_buy_signal': config['ask_strategy']['ignore_roi_if_buy_signal'], + **daily_stats, + } + + try: + max_drawdown, _, _, _, _ = calculate_max_drawdown( + results, value_col='profit_ratio') + drawdown_abs, drawdown_start, drawdown_end, high_val, low_val = calculate_max_drawdown( + results, value_col='profit_abs') + strat_stats.update({ + 'max_drawdown': max_drawdown, + 'max_drawdown_abs': drawdown_abs, + 'drawdown_start': drawdown_start, + 'drawdown_start_ts': drawdown_start.timestamp() * 1000, + 'drawdown_end': drawdown_end, + 'drawdown_end_ts': drawdown_end.timestamp() * 1000, + + 'max_drawdown_low': low_val, + 'max_drawdown_high': high_val, + }) + + csum_min, csum_max = calculate_csum(results, starting_balance) + strat_stats.update({ + 'csum_min': csum_min, + 'csum_max': csum_max + }) + + except ValueError: + strat_stats.update({ + 'max_drawdown': 0.0, + 'max_drawdown_abs': 0.0, + 'max_drawdown_low': 0.0, + 'max_drawdown_high': 0.0, + 'drawdown_start': datetime(1970, 1, 1, tzinfo=timezone.utc), + 'drawdown_start_ts': 0, + 'drawdown_end': datetime(1970, 1, 1, tzinfo=timezone.utc), + 'drawdown_end_ts': 0, + 'csum_min': 0, + 'csum_max': 0 + }) + + return strat_stats + + def generate_backtest_stats(btdata: Dict[str, DataFrame], all_results: Dict[str, Dict[str, Union[DataFrame, Dict]]], min_date: Arrow, max_date: Arrow @@ -245,131 +381,16 @@ def generate_backtest_stats(btdata: Dict[str, DataFrame], { Strategy: {'results: results, 'config: config}}. :param min_date: Backtest start date :param max_date: Backtest end date - :return: - Dictionary containing results per strategy and a stratgy summary. + :return: Dictionary containing results per strategy and a stratgy summary. """ result: Dict[str, Any] = {'strategy': {}} market_change = calculate_market_change(btdata, 'close') for strategy, content in all_results.items(): - results: Dict[str, DataFrame] = content['results'] - if not isinstance(results, DataFrame): - continue - config = content['config'] - max_open_trades = min(config['max_open_trades'], len(btdata.keys())) - starting_balance = config['dry_run_wallet'] - stake_currency = config['stake_currency'] - - pair_results = generate_pair_metrics(btdata, stake_currency=stake_currency, - starting_balance=starting_balance, - results=results, skip_nan=False) - sell_reason_stats = generate_sell_reason_stats(max_open_trades=max_open_trades, - results=results) - left_open_results = generate_pair_metrics(btdata, stake_currency=stake_currency, - starting_balance=starting_balance, - results=results.loc[results['is_open']], - skip_nan=True) - daily_stats = generate_daily_stats(results) - best_pair = max([pair for pair in pair_results if pair['key'] != 'TOTAL'], - key=lambda x: x['profit_sum']) if len(pair_results) > 1 else None - worst_pair = min([pair for pair in pair_results if pair['key'] != 'TOTAL'], - key=lambda x: x['profit_sum']) if len(pair_results) > 1 else None - results['open_timestamp'] = results['open_date'].astype(int64) // 1e6 - results['close_timestamp'] = results['close_date'].astype(int64) // 1e6 - - backtest_days = (max_date - min_date).days - strat_stats = { - 'trades': results.to_dict(orient='records'), - 'locks': [lock.to_json() for lock in content['locks']], - 'best_pair': best_pair, - 'worst_pair': worst_pair, - 'results_per_pair': pair_results, - 'sell_reason_summary': sell_reason_stats, - 'left_open_trades': left_open_results, - 'total_trades': len(results), - 'total_volume': float(results['stake_amount'].sum()), - 'avg_stake_amount': results['stake_amount'].mean() if len(results) > 0 else 0, - 'profit_mean': results['profit_ratio'].mean() if len(results) > 0 else 0, - 'profit_total': results['profit_abs'].sum() / starting_balance, - 'profit_total_abs': results['profit_abs'].sum(), - 'backtest_start': min_date.datetime, - 'backtest_start_ts': min_date.int_timestamp * 1000, - 'backtest_end': max_date.datetime, - 'backtest_end_ts': max_date.int_timestamp * 1000, - 'backtest_days': backtest_days, - - 'backtest_run_start_ts': content['backtest_start_time'], - 'backtest_run_end_ts': content['backtest_end_time'], - - 'trades_per_day': round(len(results) / backtest_days, 2) if backtest_days > 0 else 0, - 'market_change': market_change, - 'pairlist': list(btdata.keys()), - 'stake_amount': config['stake_amount'], - 'stake_currency': config['stake_currency'], - 'stake_currency_decimals': decimals_per_coin(config['stake_currency']), - 'starting_balance': starting_balance, - 'dry_run_wallet': starting_balance, - 'final_balance': content['final_balance'], - 'max_open_trades': max_open_trades, - 'max_open_trades_setting': (config['max_open_trades'] - if config['max_open_trades'] != float('inf') else -1), - 'timeframe': config['timeframe'], - 'timerange': config.get('timerange', ''), - 'enable_protections': config.get('enable_protections', False), - 'strategy_name': strategy, - # Parameters relevant for backtesting - 'stoploss': config['stoploss'], - 'trailing_stop': config.get('trailing_stop', False), - 'trailing_stop_positive': config.get('trailing_stop_positive'), - 'trailing_stop_positive_offset': config.get('trailing_stop_positive_offset', 0.0), - 'trailing_only_offset_is_reached': config.get('trailing_only_offset_is_reached', False), - 'use_custom_stoploss': config.get('use_custom_stoploss', False), - 'minimal_roi': config['minimal_roi'], - 'use_sell_signal': config['ask_strategy']['use_sell_signal'], - 'sell_profit_only': config['ask_strategy']['sell_profit_only'], - 'sell_profit_offset': config['ask_strategy']['sell_profit_offset'], - 'ignore_roi_if_buy_signal': config['ask_strategy']['ignore_roi_if_buy_signal'], - **daily_stats, - } + strat_stats = generate_strategy_stats(btdata, strategy, content, + min_date, max_date, market_change=market_change) result['strategy'][strategy] = strat_stats - try: - max_drawdown, _, _, _, _ = calculate_max_drawdown( - results, value_col='profit_ratio') - drawdown_abs, drawdown_start, drawdown_end, high_val, low_val = calculate_max_drawdown( - results, value_col='profit_abs') - strat_stats.update({ - 'max_drawdown': max_drawdown, - 'max_drawdown_abs': drawdown_abs, - 'drawdown_start': drawdown_start, - 'drawdown_start_ts': drawdown_start.timestamp() * 1000, - 'drawdown_end': drawdown_end, - 'drawdown_end_ts': drawdown_end.timestamp() * 1000, - - 'max_drawdown_low': low_val, - 'max_drawdown_high': high_val, - }) - - csum_min, csum_max = calculate_csum(results, starting_balance) - strat_stats.update({ - 'csum_min': csum_min, - 'csum_max': csum_max - }) - - except ValueError: - strat_stats.update({ - 'max_drawdown': 0.0, - 'max_drawdown_abs': 0.0, - 'max_drawdown_low': 0.0, - 'max_drawdown_high': 0.0, - 'drawdown_start': datetime(1970, 1, 1, tzinfo=timezone.utc), - 'drawdown_start_ts': 0, - 'drawdown_end': datetime(1970, 1, 1, tzinfo=timezone.utc), - 'drawdown_end_ts': 0, - 'csum_min': 0, - 'csum_max': 0 - }) - strategy_results = generate_strategy_comparison(all_results=all_results) result['strategy_comparison'] = strategy_results