feat: add wallet based metrics to backtest output

This commit is contained in:
Matthias
2026-04-11 17:25:45 +02:00
parent 624be0c469
commit bc0c8ffb71
2 changed files with 58 additions and 8 deletions
@@ -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
@@ -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),