diff --git a/freqtrade/data/metrics.py b/freqtrade/data/metrics.py index 6a1ad2766..d7b3e0162 100644 --- a/freqtrade/data/metrics.py +++ b/freqtrade/data/metrics.py @@ -231,6 +231,49 @@ def calculate_max_drawdown( ) +def calculate_current_drawdown(trades: pd.DataFrame, starting_balance: float): + """ + Calculates the current drawdown (loss from historical maximum) based on closed trades. + + :param trades: DataFrame containing trades (requires columns close_date_dt and profit_abs) + :param starting_balance: Initial account balance + :return: DrawDownResult object including: + - drawdown_abs: Drawdown in absolute terms + - relative_account_drawdown: Drawdown relative to max balance + - high_value: Maximum profit reached + - high_date: Date when the max profit was reached + """ + + if len(trades) == 0: + raise ValueError("Trade dataframe empty.") + + # Sort trades by close date + sorted_df = trades.sort_values("close_date_dt").reset_index(drop=True) + + # Calculate cumulative profit + cum_profit = sorted_df["profit_abs"].cumsum() + + # Find historical maximum profit and its date + max_profit_idx = cum_profit.idxmax() + max_profit = cum_profit.iloc[max_profit_idx] + max_date = sorted_df.iloc[max_profit_idx]["close_date_dt"] + + # Calculate current and max balance + current_balance = starting_balance + cum_profit.iloc[-1] + max_balance = starting_balance + max_profit + + # Calculate drawdown + drawdown_abs = max_balance - current_balance + drawdown_relative = drawdown_abs / max_balance + + return DrawDownResult( + drawdown_abs=drawdown_abs, + relative_account_drawdown=drawdown_relative, + high_value=max_profit, + high_date=max_date, + ) + + def calculate_csum(trades: pd.DataFrame, starting_balance: float = 0) -> tuple[float, float]: """ Calculate min/max cumsum of trades, to show if the wallet/stake amount ratio is sane diff --git a/freqtrade/rpc/rpc.py b/freqtrade/rpc/rpc.py index 01f649ba3..afb03e630 100644 --- a/freqtrade/rpc/rpc.py +++ b/freqtrade/rpc/rpc.py @@ -19,7 +19,12 @@ from freqtrade import __version__ from freqtrade.configuration.timerange import TimeRange from freqtrade.constants import CANCEL_REASON, DEFAULT_DATAFRAME_COLUMNS, Config from freqtrade.data.history import load_data -from freqtrade.data.metrics import DrawDownResult, calculate_expectancy, calculate_max_drawdown +from freqtrade.data.metrics import ( + DrawDownResult, + calculate_current_drawdown, + calculate_expectancy, + calculate_max_drawdown, +) from freqtrade.enums import ( CandleType, ExitCheckTuple, @@ -612,17 +617,23 @@ class RPC: expectancy, expectancy_ratio = calculate_expectancy(trades_df) - drawdown = DrawDownResult() + max_drawdown = DrawDownResult() + current_drawdown = DrawDownResult() + if len(trades_df) > 0: try: - drawdown = calculate_max_drawdown( + max_drawdown = calculate_max_drawdown( trades_df, value_col="profit_abs", date_col="close_date_dt", starting_balance=starting_balance, ) except ValueError: - # ValueError if no losing trade. + pass + + try: + current_drawdown = calculate_current_drawdown(trades_df, starting_balance) + except ValueError: pass profit_all_fiat = ( @@ -673,14 +684,19 @@ class RPC: "winrate": winrate, "expectancy": expectancy, "expectancy_ratio": expectancy_ratio, - "max_drawdown": drawdown.relative_account_drawdown, - "max_drawdown_abs": drawdown.drawdown_abs, - "max_drawdown_start": format_date(drawdown.high_date), - "max_drawdown_start_timestamp": dt_ts_def(drawdown.high_date), - "max_drawdown_end": format_date(drawdown.low_date), - "max_drawdown_end_timestamp": dt_ts_def(drawdown.low_date), - "drawdown_high": drawdown.high_value, - "drawdown_low": drawdown.low_value, + "max_drawdown": max_drawdown.relative_account_drawdown, + "max_drawdown_abs": max_drawdown.drawdown_abs, + "max_drawdown_start": format_date(max_drawdown.high_date), + "max_drawdown_start_timestamp": dt_ts_def(max_drawdown.high_date), + "max_drawdown_end": format_date(max_drawdown.low_date), + "max_drawdown_end_timestamp": dt_ts_def(max_drawdown.low_date), + "drawdown_high": max_drawdown.high_value, + "drawdown_low": max_drawdown.low_value, + "current_drawdown": current_drawdown.relative_account_drawdown, + "current_drawdown_abs": current_drawdown.drawdown_abs, + "current_drawdown_high": current_drawdown.high_value, + "current_drawdown_start": format_date(current_drawdown.high_date), + "current_drawdown_start_timestamp": dt_ts_def(current_drawdown.high_date), "trading_volume": trading_volume, "bot_start_timestamp": dt_ts_def(bot_start, 0), "bot_start_date": format_date(bot_start), diff --git a/freqtrade/rpc/telegram.py b/freqtrade/rpc/telegram.py index 7230a8681..3e90a0861 100644 --- a/freqtrade/rpc/telegram.py +++ b/freqtrade/rpc/telegram.py @@ -1085,6 +1085,10 @@ class Telegram(RPCHandler): f"({fmt_coin(stats['drawdown_high'], stake_cur)})`\n" f" to `{stats['max_drawdown_end']} " f"({fmt_coin(stats['drawdown_low'], stake_cur)})`\n" + f"*Current Drawdown:* `{stats['current_drawdown']:.2%} " + f"({fmt_coin(stats['current_drawdown_abs'], stake_cur)})`\n" + f" from `{stats['current_drawdown_start']} " + f"({fmt_coin(stats['current_drawdown_high'], stake_cur)})`\n" ) await self._send_msg( markdown_msg,