From e6dd932436f6bc98fcf4f0f245b34cb7bb1ced8a Mon Sep 17 00:00:00 2001 From: mrpabloyeah Date: Tue, 8 Jul 2025 13:00:48 +0200 Subject: [PATCH] Expand calculate_max_drawdown() to return the current drawdown data and use it instead of calculate_current_drawdown() --- freqtrade/data/metrics.py | 122 +++++++++++++++++--------------------- freqtrade/rpc/rpc.py | 24 ++------ 2 files changed, 60 insertions(+), 86 deletions(-) diff --git a/freqtrade/data/metrics.py b/freqtrade/data/metrics.py index d7b3e0162..c1c37358f 100644 --- a/freqtrade/data/metrics.py +++ b/freqtrade/data/metrics.py @@ -174,12 +174,18 @@ def calculate_underwater( @dataclass() class DrawDownResult: - drawdown_abs: float = 0.0 + # Max drawdown fields high_date: pd.Timestamp = None low_date: pd.Timestamp = None high_value: float = 0.0 low_value: float = 0.0 + drawdown_abs: float = 0.0 relative_account_drawdown: float = 0.0 + # Current drawdown fields + current_high_date: pd.Timestamp = None + current_high_value: float = 0.0 + current_drawdown_abs: float = 0.0 + current_relative_account_drawdown: float = 0.0 def calculate_max_drawdown( @@ -191,88 +197,68 @@ def calculate_max_drawdown( relative: bool = False, ) -> DrawDownResult: """ - Calculate max drawdown and the corresponding close dates - :param trades: DataFrame containing trades (requires columns close_date and profit_ratio) + Calculate max drawdown and current drawdown with corresponding dates + :param trades: DataFrame containing trades (requires columns close_date and profit_abs) :param date_col: Column in DataFrame to use for dates (defaults to 'close_date') :param value_col: Column in DataFrame to use for values (defaults to 'profit_abs') :param starting_balance: Portfolio starting balance - properly calculate relative drawdown. + :param relative: If True, use relative drawdown for max calculation instead of absolute :return: DrawDownResult object with absolute max drawdown, high and low time and high and low value, - and the relative account drawdown + relative account drawdown, and current drawdown information. :raise: ValueError if trade-dataframe was found empty. """ - if len(trades) == 0: - raise ValueError("Trade dataframe empty.") - profit_results = trades.sort_values(date_col).reset_index(drop=True) - max_drawdown_df = _calc_drawdown_series( - profit_results, date_col=date_col, value_col=value_col, starting_balance=starting_balance - ) - - idxmin = ( - max_drawdown_df["drawdown_relative"].idxmax() - if relative - else max_drawdown_df["drawdown"].idxmin() - ) - - high_idx = max_drawdown_df.iloc[: idxmin + 1]["high_value"].idxmax() - high_date = profit_results.loc[high_idx, date_col] - low_date = profit_results.loc[idxmin, date_col] - high_val = max_drawdown_df.loc[high_idx, "cumulative"] - low_val = max_drawdown_df.loc[idxmin, "cumulative"] - max_drawdown_rel = max_drawdown_df.loc[idxmin, "drawdown_relative"] - - return DrawDownResult( - drawdown_abs=abs(max_drawdown_df.loc[idxmin, "drawdown"]), - high_date=high_date, - low_date=low_date, - high_value=high_val, - low_value=low_val, - relative_account_drawdown=max_drawdown_rel, - ) - - -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) + profit_results = trades.sort_values(date_col).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, + # Get drawdown data + max_drawdown_df = _calc_drawdown_series( + profit_results, date_col=date_col, value_col=value_col, starting_balance=starting_balance ) + # Calculate maximum drawdown + idxmin = ( + max_drawdown_df["drawdown_relative"].idxmax() + if relative + else max_drawdown_df["drawdown"].idxmin() + ) + high_idx = max_drawdown_df.iloc[: idxmin + 1]["high_value"].idxmax() + high_date = profit_results.loc[high_idx, date_col] + low_date = profit_results.loc[idxmin, date_col] + high_val = max_drawdown_df.loc[high_idx, "cumulative"] + low_val = max_drawdown_df.loc[idxmin, "cumulative"] + max_drawdown_abs = abs(max_drawdown_df.loc[idxmin, "drawdown"]) + max_drawdown_rel = max_drawdown_df.loc[idxmin, "drawdown_relative"] + + # Calculate current drawdown + current_high_idx = max_drawdown_df["high_value"].iloc[:-1].idxmax() + current_high_date = profit_results.loc[current_high_idx, date_col] + current_high_value = max_drawdown_df.iloc[-1]["high_value"] + current_cumulative = max_drawdown_df.iloc[-1]["cumulative"] + current_drawdown_abs = current_high_value - current_cumulative + current_drawdown_relative = max_drawdown_df.iloc[-1]["drawdown_relative"] + + result = DrawDownResult( + # Max drawdown + high_date=high_date, + low_date=low_date, + high_value=high_val, + low_value=low_val, + drawdown_abs=max_drawdown_abs, + relative_account_drawdown=max_drawdown_rel, + # Current drawdown + current_high_date=current_high_date, + current_high_value=current_high_value, + current_drawdown_abs=current_drawdown_abs, + current_relative_account_drawdown=current_drawdown_relative, + ) + + return result + def calculate_csum(trades: pd.DataFrame, starting_balance: float = 0) -> tuple[float, float]: """ diff --git a/freqtrade/rpc/rpc.py b/freqtrade/rpc/rpc.py index afb03e630..838f1368c 100644 --- a/freqtrade/rpc/rpc.py +++ b/freqtrade/rpc/rpc.py @@ -19,12 +19,7 @@ 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_current_drawdown, - calculate_expectancy, - calculate_max_drawdown, -) +from freqtrade.data.metrics import DrawDownResult, calculate_expectancy, calculate_max_drawdown from freqtrade.enums import ( CandleType, ExitCheckTuple, @@ -616,9 +611,7 @@ class RPC: ) expectancy, expectancy_ratio = calculate_expectancy(trades_df) - max_drawdown = DrawDownResult() - current_drawdown = DrawDownResult() if len(trades_df) > 0: try: @@ -631,11 +624,6 @@ class RPC: except ValueError: pass - try: - current_drawdown = calculate_current_drawdown(trades_df, starting_balance) - except ValueError: - pass - profit_all_fiat = ( self._fiat_converter.convert_amount( profit_all_coin_sum, stake_currency, fiat_display_currency @@ -692,11 +680,11 @@ class RPC: "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), + "current_drawdown": max_drawdown.current_relative_account_drawdown, + "current_drawdown_abs": max_drawdown.current_drawdown_abs, + "current_drawdown_high": max_drawdown.current_high_value, + "current_drawdown_start": format_date(max_drawdown.current_high_date), + "current_drawdown_start_timestamp": dt_ts_def(max_drawdown.current_high_date), "trading_volume": trading_volume, "bot_start_timestamp": dt_ts_def(bot_start, 0), "bot_start_date": format_date(bot_start),