From 47451dd9895b38d61aeb35ac60b003ec308c1fa1 Mon Sep 17 00:00:00 2001 From: mrpabloyeah Date: Thu, 13 Nov 2025 20:14:39 +0100 Subject: [PATCH 1/4] Fix high_value calculation in calculate_max_drawdown() --- freqtrade/data/metrics.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/freqtrade/data/metrics.py b/freqtrade/data/metrics.py index 7e0b279f4..557d0ddcc 100644 --- a/freqtrade/data/metrics.py +++ b/freqtrade/data/metrics.py @@ -225,7 +225,7 @@ def calculate_max_drawdown( 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"] + high_val = max_drawdown_df.loc[high_idx, "high_value"] low_val = max_drawdown_df.loc[idxmin, "cumulative"] max_drawdown_rel = max_drawdown_df.loc[idxmin, "drawdown_relative"] From 1a506dc4b441133d38625edada2a0329cd1314c2 Mon Sep 17 00:00:00 2001 From: Matthias Date: Fri, 14 Nov 2025 19:41:52 +0100 Subject: [PATCH 2/4] test: add tests for high/low logic --- tests/data/test_btanalysis.py | 2 ++ 1 file changed, 2 insertions(+) diff --git a/tests/data/test_btanalysis.py b/tests/data/test_btanalysis.py index e7c914040..41b42d175 100644 --- a/tests/data/test_btanalysis.py +++ b/tests/data/test_btanalysis.py @@ -581,6 +581,8 @@ def test_calculate_max_drawdown2(): # No winning trade ... drawdown = calculate_max_drawdown(df1, date_col="open_date", value_col="profit") assert drawdown.drawdown_abs == 0.055545 + assert drawdown.high_value == 0.0 + assert drawdown.current_high_value == 0.0 @pytest.mark.parametrize( From 70ec376657b14d1846488a32136caf78f6f9d370 Mon Sep 17 00:00:00 2001 From: Matthias Date: Fri, 14 Nov 2025 19:53:22 +0100 Subject: [PATCH 3/4] test: further expand drawdown test --- tests/data/test_btanalysis.py | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/tests/data/test_btanalysis.py b/tests/data/test_btanalysis.py index 41b42d175..a09db5898 100644 --- a/tests/data/test_btanalysis.py +++ b/tests/data/test_btanalysis.py @@ -575,6 +575,9 @@ def test_calculate_max_drawdown2(): # No losing trade ... drawdown = calculate_max_drawdown(df, date_col="open_date", value_col="profit") assert drawdown.drawdown_abs == 0.0 + assert drawdown.low_value == 0.0 + assert drawdown.current_high_value >= 0.0 + assert drawdown.current_drawdown_abs == 0.0 df1 = DataFrame(zip(values[:5], dates[:5], strict=False), columns=["profit", "open_date"]) df1.loc[:, "profit"] = df1["profit"] * -1 @@ -583,6 +586,7 @@ def test_calculate_max_drawdown2(): assert drawdown.drawdown_abs == 0.055545 assert drawdown.high_value == 0.0 assert drawdown.current_high_value == 0.0 + assert drawdown.current_drawdown_abs == 0.055545 @pytest.mark.parametrize( From 38ff75553353688d68d6182fda880cb25ea69e86 Mon Sep 17 00:00:00 2001 From: Matthias Date: Sat, 15 Nov 2025 09:52:24 +0100 Subject: [PATCH 4/4] fix: start drawdown series with a 0 row This will account for edge-cases with no winning / losing trades --- freqtrade/data/metrics.py | 27 +++++++++++++++++++++------ 1 file changed, 21 insertions(+), 6 deletions(-) diff --git a/freqtrade/data/metrics.py b/freqtrade/data/metrics.py index 557d0ddcc..a4b6898e2 100644 --- a/freqtrade/data/metrics.py +++ b/freqtrade/data/metrics.py @@ -143,6 +143,20 @@ def _calc_drawdown_series( max_drawdown_df["drawdown_relative"] = ( max_drawdown_df["high_value"] - max_drawdown_df["cumulative"] ) / max_drawdown_df["high_value"] + + # Add zero row at start to account for edge-cases with no winning / losing trades - so high/low + # will be 0.0 in such cases. + zero_row = pd.DataFrame( + { + "cumulative": [0.0], + "high_value": [0.0], + "drawdown": [0.0], + "drawdown_relative": [0.0], + "date": [profit_results.loc[0, date_col]], + } + ) + + max_drawdown_df = pd.concat([zero_row, max_drawdown_df], ignore_index=True) return max_drawdown_df @@ -215,6 +229,7 @@ def calculate_max_drawdown( max_drawdown_df = _calc_drawdown_series( profit_results, date_col=date_col, value_col=value_col, starting_balance=starting_balance ) + # max_drawdown_df has an extra zero row at the start # Calculate maximum drawdown idxmin = ( @@ -223,15 +238,15 @@ def calculate_max_drawdown( 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, "high_value"] - low_val = max_drawdown_df.loc[idxmin, "cumulative"] - max_drawdown_rel = max_drawdown_df.loc[idxmin, "drawdown_relative"] + high_date = profit_results.at[max(high_idx - 1, 0), date_col] + low_date = profit_results.at[max(idxmin - 1, 0), date_col] + high_val = max_drawdown_df.at[high_idx, "cumulative"] + low_val = max_drawdown_df.at[idxmin, "cumulative"] + max_drawdown_rel = max_drawdown_df.at[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_date = profit_results.at[max(current_high_idx - 1, 0), 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