diff --git a/freqtrade/optimize/optimize_reports/bt_output.py b/freqtrade/optimize/optimize_reports/bt_output.py index 149f886a2..c8b4b50db 100644 --- a/freqtrade/optimize/optimize_reports/bt_output.py +++ b/freqtrade/optimize/optimize_reports/bt_output.py @@ -289,7 +289,7 @@ def text_table_add_metrics(strat_results: dict) -> None: ) wallet_metrics: list[tuple[str, str]] = [ ( - "Min/Max balance realized", + "Min/Max balance (realized)", f"{fmt_coin(strat_results['csum_min'], stake)} / " f"{fmt_coin(strat_results['csum_max'], stake)}", ), @@ -298,22 +298,38 @@ def text_table_add_metrics(strat_results: dict) -> None: wallet_metrics.extend( [ ( - "Min/Max balance unrealized", + "Min/Max balance (unrealized)", f"{fmt_coin(wallet_stats['low_balance'], stake)} / " f"{fmt_coin(wallet_stats['high_balance'], stake)}", ), ( - "Min/Max balance dates", + "Min/Max balance dates (unrealized)", f"{wallet_stats['low_date']} / {wallet_stats['high_date']}", ), ] ) if "sharpe" in wallet_stats: - wallet_metrics.append( - ( - "Sharpe ratio balance", - f"{wallet_stats['sharpe']:.2f}", - ) + # Assume that if sharpe is there, all others are there as well. + wallet_metrics.extend( + [ + ( + "Sharpe (unrealized)", + f"{wallet_stats['sharpe']:.2f}", + ), + ( + "Sortino (unrealized)", + f"{wallet_stats['sortino']:.2f}", + ), + ( + "Calmar (unrealized)", + f"{wallet_stats['calmar']:.2f}", + ), + ( + "Max drawdown (unrealized)", + f"{fmt_coin(wallet_stats['max_drawdown_abs'], stake)} " + f"({wallet_stats['max_drawdown_account']:.2%})", + ), + ] ) # Newly added fields should be ignored if they are missing in strat_results. hyperopt-show diff --git a/freqtrade/optimize/optimize_reports/optimize_reports.py b/freqtrade/optimize/optimize_reports/optimize_reports.py index 24c683482..a17d92b59 100644 --- a/freqtrade/optimize/optimize_reports/optimize_reports.py +++ b/freqtrade/optimize/optimize_reports/optimize_reports.py @@ -10,13 +10,16 @@ from freqtrade.constants import BACKTEST_BREAKDOWNS, DATETIME_PRINT_FORMAT from freqtrade.data.metrics import ( calculate_cagr, calculate_calmar, + calculate_calmar_from_balance, calculate_csum, calculate_expectancy, calculate_market_change, calculate_max_drawdown, + calculate_max_drawdown_from_balance, calculate_sharpe, calculate_sharpe_from_balance, calculate_sortino, + calculate_sortino_from_balance, calculate_sqn, ) from freqtrade.ft_types import ( @@ -61,12 +64,43 @@ def generate_wallet_stats(wallet_df: DataFrame, stake_currency: str) -> dict[str low_date = wallet.loc[low_idx, "date"] high_date = wallet.loc[high_idx, "date"] sharpe = calculate_sharpe_from_balance(wallet) + sortino = calculate_sortino_from_balance(wallet) + calmar = calculate_calmar_from_balance(wallet) + try: + drawdown = calculate_max_drawdown_from_balance(wallet) + except ValueError: + drawdown = None + return { "start_balance": start_balance, "end_balance": end_balance, "high_balance": high_balance, "low_balance": low_balance, "sharpe": sharpe, + "sortino": sortino, + "calmar": calmar, + "max_drawdown_account": drawdown.relative_account_drawdown if drawdown else 0.0, + "max_drawdown_abs": drawdown.drawdown_abs if drawdown else 0.0, + "drawdown_start": ( + drawdown.high_date.strftime(DATETIME_PRINT_FORMAT) + if drawdown and drawdown.high_date is not None + else None + ), + "drawdown_start_ts": ( + int(drawdown.high_date.timestamp() * 1000) + if drawdown and drawdown.high_date is not None + else None + ), + "drawdown_end": ( + drawdown.low_date.strftime(DATETIME_PRINT_FORMAT) + if drawdown and drawdown.low_date is not None + else None + ), + "drawdown_end_ts": ( + int(drawdown.low_date.timestamp() * 1000) + if drawdown and drawdown.low_date is not None + else None + ), "low_date": low_date.strftime(DATETIME_PRINT_FORMAT), "low_ts": int(low_date.timestamp() * 1000), "high_date": high_date.strftime(DATETIME_PRINT_FORMAT),