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
+54 -68
View File
@@ -174,12 +174,18 @@ def calculate_underwater(
@dataclass() @dataclass()
class DrawDownResult: class DrawDownResult:
drawdown_abs: float = 0.0 # Max drawdown fields
high_date: pd.Timestamp = None high_date: pd.Timestamp = None
low_date: pd.Timestamp = None low_date: pd.Timestamp = None
high_value: float = 0.0 high_value: float = 0.0
low_value: float = 0.0 low_value: float = 0.0
drawdown_abs: float = 0.0
relative_account_drawdown: 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( def calculate_max_drawdown(
@@ -191,88 +197,68 @@ def calculate_max_drawdown(
relative: bool = False, relative: bool = False,
) -> DrawDownResult: ) -> DrawDownResult:
""" """
Calculate max drawdown and the corresponding close dates Calculate max drawdown and current drawdown with corresponding dates
:param trades: DataFrame containing trades (requires columns close_date and profit_ratio) :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 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 value_col: Column in DataFrame to use for values (defaults to 'profit_abs')
:param starting_balance: Portfolio starting balance - properly calculate relative drawdown. :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 :return: DrawDownResult object
with absolute max drawdown, high and low time and high and low value, 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. :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: if len(trades) == 0:
raise ValueError("Trade dataframe empty.") raise ValueError("Trade dataframe empty.")
# Sort trades by close date # 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 # Get drawdown data
cum_profit = sorted_df["profit_abs"].cumsum() max_drawdown_df = _calc_drawdown_series(
profit_results, date_col=date_col, value_col=value_col, starting_balance=starting_balance
# 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,
) )
# 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]: def calculate_csum(trades: pd.DataFrame, starting_balance: float = 0) -> tuple[float, float]:
""" """
+6 -18
View File
@@ -19,12 +19,7 @@ from freqtrade import __version__
from freqtrade.configuration.timerange import TimeRange from freqtrade.configuration.timerange import TimeRange
from freqtrade.constants import CANCEL_REASON, DEFAULT_DATAFRAME_COLUMNS, Config from freqtrade.constants import CANCEL_REASON, DEFAULT_DATAFRAME_COLUMNS, Config
from freqtrade.data.history import load_data from freqtrade.data.history import load_data
from freqtrade.data.metrics import ( from freqtrade.data.metrics import DrawDownResult, calculate_expectancy, calculate_max_drawdown
DrawDownResult,
calculate_current_drawdown,
calculate_expectancy,
calculate_max_drawdown,
)
from freqtrade.enums import ( from freqtrade.enums import (
CandleType, CandleType,
ExitCheckTuple, ExitCheckTuple,
@@ -616,9 +611,7 @@ class RPC:
) )
expectancy, expectancy_ratio = calculate_expectancy(trades_df) expectancy, expectancy_ratio = calculate_expectancy(trades_df)
max_drawdown = DrawDownResult() max_drawdown = DrawDownResult()
current_drawdown = DrawDownResult()
if len(trades_df) > 0: if len(trades_df) > 0:
try: try:
@@ -631,11 +624,6 @@ class RPC:
except ValueError: except ValueError:
pass pass
try:
current_drawdown = calculate_current_drawdown(trades_df, starting_balance)
except ValueError:
pass
profit_all_fiat = ( profit_all_fiat = (
self._fiat_converter.convert_amount( self._fiat_converter.convert_amount(
profit_all_coin_sum, stake_currency, fiat_display_currency 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), "max_drawdown_end_timestamp": dt_ts_def(max_drawdown.low_date),
"drawdown_high": max_drawdown.high_value, "drawdown_high": max_drawdown.high_value,
"drawdown_low": max_drawdown.low_value, "drawdown_low": max_drawdown.low_value,
"current_drawdown": current_drawdown.relative_account_drawdown, "current_drawdown": max_drawdown.current_relative_account_drawdown,
"current_drawdown_abs": current_drawdown.drawdown_abs, "current_drawdown_abs": max_drawdown.current_drawdown_abs,
"current_drawdown_high": current_drawdown.high_value, "current_drawdown_high": max_drawdown.current_high_value,
"current_drawdown_start": format_date(current_drawdown.high_date), "current_drawdown_start": format_date(max_drawdown.current_high_date),
"current_drawdown_start_timestamp": dt_ts_def(current_drawdown.high_date), "current_drawdown_start_timestamp": dt_ts_def(max_drawdown.current_high_date),
"trading_volume": trading_volume, "trading_volume": trading_volume,
"bot_start_timestamp": dt_ts_def(bot_start, 0), "bot_start_timestamp": dt_ts_def(bot_start, 0),
"bot_start_date": format_date(bot_start), "bot_start_date": format_date(bot_start),