diff --git a/freqtrade/data/metrics.py b/freqtrade/data/metrics.py index 6a1ad2766..7e0b279f4 100644 --- a/freqtrade/data/metrics.py +++ b/freqtrade/data/metrics.py @@ -174,12 +174,18 @@ def calculate_underwater( @dataclass() class DrawDownResult: + # Max drawdown fields drawdown_abs: float = 0.0 high_date: pd.Timestamp = None low_date: pd.Timestamp = None high_value: float = 0.0 low_value: 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,29 +197,31 @@ 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 ) + # 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] @@ -221,13 +229,27 @@ def calculate_max_drawdown( low_val = max_drawdown_df.loc[idxmin, "cumulative"] 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"] + return DrawDownResult( + # Max drawdown 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, + # 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, ) diff --git a/freqtrade/rpc/api_server/api_schemas.py b/freqtrade/rpc/api_server/api_schemas.py index bb5dfafa7..5f89b4a4f 100644 --- a/freqtrade/rpc/api_server/api_schemas.py +++ b/freqtrade/rpc/api_server/api_schemas.py @@ -163,6 +163,11 @@ class Profit(BaseModel): max_drawdown_start_timestamp: int max_drawdown_end: str max_drawdown_end_timestamp: int + current_drawdown: float + current_drawdown_abs: float + current_drawdown_high: float + current_drawdown_start: str + current_drawdown_start_timestamp: int trading_volume: float | None = None bot_start_timestamp: int bot_start_date: str diff --git a/freqtrade/rpc/rpc.py b/freqtrade/rpc/rpc.py index 00a280d34..5d8ffc141 100644 --- a/freqtrade/rpc/rpc.py +++ b/freqtrade/rpc/rpc.py @@ -681,6 +681,11 @@ class RPC: "max_drawdown_end_timestamp": dt_ts_def(drawdown.low_date), "drawdown_high": drawdown.high_value, "drawdown_low": drawdown.low_value, + "current_drawdown": drawdown.current_relative_account_drawdown, + "current_drawdown_abs": drawdown.current_drawdown_abs, + "current_drawdown_high": drawdown.current_high_value, + "current_drawdown_start": format_date(drawdown.current_high_date), + "current_drawdown_start_timestamp": dt_ts_def(drawdown.current_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 d864fa7a4..7b3967193 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, diff --git a/tests/rpc/test_rpc_apiserver.py b/tests/rpc/test_rpc_apiserver.py index 573130a4a..120b4865b 100644 --- a/tests/rpc/test_rpc_apiserver.py +++ b/tests/rpc/test_rpc_apiserver.py @@ -1332,6 +1332,11 @@ def test_api_profit(botclient, mocker, ticker, fee, markets, is_short, expected) "max_drawdown_start_timestamp": ANY, "max_drawdown_end": ANY, "max_drawdown_end_timestamp": ANY, + "current_drawdown": ANY, + "current_drawdown_abs": ANY, + "current_drawdown_high": ANY, + "current_drawdown_start": ANY, + "current_drawdown_start_timestamp": ANY, "trading_volume": expected["trading_volume"], "bot_start_timestamp": 0, "bot_start_date": "",