Expand calculate_max_drawdown() to return the current drawdown data and use it instead of calculate_current_drawdown()

This commit is contained in:
mrpabloyeah
2025-07-08 13:00:48 +02:00
parent 2ebc5374f4
commit e6dd932436
2 changed files with 60 additions and 86 deletions

View File

@@ -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]:
"""

View File

@@ -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),