From 916ef43f7f4666ea11c5863e5e77b539f178cb12 Mon Sep 17 00:00:00 2001 From: mrpabloyeah Date: Tue, 25 Mar 2025 19:53:40 +0100 Subject: [PATCH 1/6] Calculate and save all metrics per pair --- .../optimize_reports/optimize_reports.py | 60 ++++++++++++++++--- 1 file changed, 53 insertions(+), 7 deletions(-) diff --git a/freqtrade/optimize/optimize_reports/optimize_reports.py b/freqtrade/optimize/optimize_reports/optimize_reports.py index c0188673a..c678cbee7 100644 --- a/freqtrade/optimize/optimize_reports/optimize_reports.py +++ b/freqtrade/optimize/optimize_reports/optimize_reports.py @@ -70,7 +70,11 @@ def generate_rejected_signals( def _generate_result_line( - result: DataFrame, starting_balance: float, first_column: str | list[str] + result: DataFrame, + min_date: datetime, + max_date: datetime, + starting_balance: float, + first_column: str | list[str] ) -> dict: """ Generate one result dict, with "first_column" as key. @@ -78,6 +82,18 @@ def _generate_result_line( profit_sum = result["profit_ratio"].sum() # (end-capital - starting capital) / starting capital profit_total = result["profit_abs"].sum() / starting_balance + backtest_days = (max_date - min_date).days or 1 + final_balance = starting_balance + result["profit_abs"].sum() + expectancy, expectancy_ratio = calculate_expectancy(result) + winning_profit = result.loc[result["profit_abs"] > 0, "profit_abs"].sum() + losing_profit = result.loc[result["profit_abs"] < 0, "profit_abs"].sum() + profit_factor = winning_profit / abs(losing_profit) if losing_profit else 0.0 + try: + drawdown = calculate_max_drawdown( + result, value_col="profit_abs", starting_balance=starting_balance + ) + except: + drawdown = None return { "key": first_column, @@ -106,6 +122,16 @@ def _generate_result_line( "draws": len(result[result["profit_abs"] == 0]), "losses": len(result[result["profit_abs"] < 0]), "winrate": len(result[result["profit_abs"] > 0]) / len(result) if len(result) else 0.0, + "cagr": calculate_cagr(backtest_days, starting_balance, final_balance), + "expectancy": expectancy, + "expectancy_ratio": expectancy_ratio, + "sortino": calculate_sortino(result, min_date, max_date, starting_balance), + "sharpe": calculate_sharpe(result, min_date, max_date, starting_balance), + "calmar": calculate_calmar(result, min_date, max_date, starting_balance), + "sqn": calculate_sqn(result, starting_balance), + "profit_factor": profit_factor, + "max_drawdown_account": drawdown.relative_account_drawdown if drawdown else 0.0, + "max_drawdown_abs": drawdown.drawdown_abs if drawdown else 0.0, } @@ -121,6 +147,8 @@ def generate_pair_metrics( # stake_currency: str, starting_balance: float, results: DataFrame, + min_date: datetime, + max_date: datetime, skip_nan: bool = False, ) -> list[dict]: """ @@ -140,13 +168,13 @@ def generate_pair_metrics( # if skip_nan and result["profit_abs"].isnull().all(): continue - tabular_data.append(_generate_result_line(result, starting_balance, pair)) + tabular_data.append(_generate_result_line(result, min_date, max_date, starting_balance, pair)) # Sort by total profit %: tabular_data = sorted(tabular_data, key=lambda k: k["profit_total_abs"], reverse=True) # Append Total - tabular_data.append(_generate_result_line(results, starting_balance, "TOTAL")) + tabular_data.append(_generate_result_line(results, min_date, max_date, starting_balance, "TOTAL")) return tabular_data @@ -154,6 +182,8 @@ def generate_tag_metrics( tag_type: Literal["enter_tag", "exit_reason"] | list[Literal["enter_tag", "exit_reason"]], starting_balance: float, results: DataFrame, + min_date: datetime, + max_date: datetime, skip_nan: bool = False, ) -> list[dict]: """ @@ -173,13 +203,13 @@ def generate_tag_metrics( if skip_nan and group["profit_abs"].isnull().all(): continue - tabular_data.append(_generate_result_line(group, starting_balance, tags)) + tabular_data.append(_generate_result_line(group, min_date, max_date, starting_balance, tags)) # Sort by total profit %: tabular_data = sorted(tabular_data, key=lambda k: k["profit_total_abs"], reverse=True) # Append Total - tabular_data.append(_generate_result_line(results, starting_balance, "TOTAL")) + tabular_data.append(_generate_result_line(results, min_date, max_date, starting_balance, "TOTAL")) return tabular_data else: return [] @@ -395,19 +425,33 @@ def generate_strategy_stats( stake_currency=stake_currency, starting_balance=start_balance, results=results, + min_date=min_date, + max_date=max_date, skip_nan=False, ) enter_tag_stats = generate_tag_metrics( - "enter_tag", starting_balance=start_balance, results=results, skip_nan=False + "enter_tag", + starting_balance=start_balance, + results=results, + min_date=min_date, + max_date=max_date, + skip_nan=False ) exit_reason_stats = generate_tag_metrics( - "exit_reason", starting_balance=start_balance, results=results, skip_nan=False + "exit_reason", + starting_balance=start_balance, + results=results, + min_date=min_date, + max_date=max_date, + skip_nan=False ) mix_tag_stats = generate_tag_metrics( ["enter_tag", "exit_reason"], starting_balance=start_balance, results=results, + min_date=min_date, + max_date=max_date, skip_nan=False, ) left_open_results = generate_pair_metrics( @@ -415,6 +459,8 @@ def generate_strategy_stats( stake_currency=stake_currency, starting_balance=start_balance, results=results.loc[results["exit_reason"] == "force_exit"], + min_date=min_date, + max_date=max_date, skip_nan=True, ) From be40e828a973910744939ef2640f3c2e5afec9e1 Mon Sep 17 00:00:00 2001 From: mrpabloyeah Date: Tue, 25 Mar 2025 20:16:57 +0100 Subject: [PATCH 2/6] Calculate and save all metrics per pair (fix) --- .../optimize_reports/optimize_reports.py | 23 +++++++++++++------ 1 file changed, 16 insertions(+), 7 deletions(-) diff --git a/freqtrade/optimize/optimize_reports/optimize_reports.py b/freqtrade/optimize/optimize_reports/optimize_reports.py index c678cbee7..169557b12 100644 --- a/freqtrade/optimize/optimize_reports/optimize_reports.py +++ b/freqtrade/optimize/optimize_reports/optimize_reports.py @@ -74,7 +74,7 @@ def _generate_result_line( min_date: datetime, max_date: datetime, starting_balance: float, - first_column: str | list[str] + first_column: str | list[str], ) -> dict: """ Generate one result dict, with "first_column" as key. @@ -168,13 +168,18 @@ def generate_pair_metrics( # if skip_nan and result["profit_abs"].isnull().all(): continue - tabular_data.append(_generate_result_line(result, min_date, max_date, starting_balance, pair)) + tabular_data.append( + _generate_result_line(result, min_date, max_date, starting_balance, pair) + ) # Sort by total profit %: tabular_data = sorted(tabular_data, key=lambda k: k["profit_total_abs"], reverse=True) # Append Total - tabular_data.append(_generate_result_line(results, min_date, max_date, starting_balance, "TOTAL")) + tabular_data.append( + _generate_result_line(results, min_date, max_date, starting_balance, "TOTAL") + ) + return tabular_data @@ -203,13 +208,17 @@ def generate_tag_metrics( if skip_nan and group["profit_abs"].isnull().all(): continue - tabular_data.append(_generate_result_line(group, min_date, max_date, starting_balance, tags)) + tabular_data.append( + _generate_result_line(group, min_date, max_date, starting_balance, tags) + ) # Sort by total profit %: tabular_data = sorted(tabular_data, key=lambda k: k["profit_total_abs"], reverse=True) # Append Total - tabular_data.append(_generate_result_line(results, min_date, max_date, starting_balance, "TOTAL")) + tabular_data.append( + _generate_result_line(results, min_date, max_date, starting_balance, "TOTAL") + ) return tabular_data else: return [] @@ -436,7 +445,7 @@ def generate_strategy_stats( results=results, min_date=min_date, max_date=max_date, - skip_nan=False + skip_nan=False, ) exit_reason_stats = generate_tag_metrics( "exit_reason", @@ -444,7 +453,7 @@ def generate_strategy_stats( results=results, min_date=min_date, max_date=max_date, - skip_nan=False + skip_nan=False, ) mix_tag_stats = generate_tag_metrics( ["enter_tag", "exit_reason"], From 20325a2b5e634029c1cff0e8e0aa9ea62d8fbc60 Mon Sep 17 00:00:00 2001 From: mrpabloyeah Date: Tue, 25 Mar 2025 20:24:29 +0100 Subject: [PATCH 3/6] Calculate and save all metrics per pair (fix 2) --- freqtrade/optimize/optimize_reports/optimize_reports.py | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/freqtrade/optimize/optimize_reports/optimize_reports.py b/freqtrade/optimize/optimize_reports/optimize_reports.py index 169557b12..e8ac820e7 100644 --- a/freqtrade/optimize/optimize_reports/optimize_reports.py +++ b/freqtrade/optimize/optimize_reports/optimize_reports.py @@ -88,11 +88,13 @@ def _generate_result_line( winning_profit = result.loc[result["profit_abs"] > 0, "profit_abs"].sum() losing_profit = result.loc[result["profit_abs"] < 0, "profit_abs"].sum() profit_factor = winning_profit / abs(losing_profit) if losing_profit else 0.0 + try: drawdown = calculate_max_drawdown( result, value_col="profit_abs", starting_balance=starting_balance ) - except: + + except ValueError: drawdown = None return { From 00237d68e4ca5c259c4c16f4f8ac03315a8c00a1 Mon Sep 17 00:00:00 2001 From: mrpabloyeah Date: Tue, 25 Mar 2025 20:53:09 +0100 Subject: [PATCH 4/6] Calculate and save all metrics per pair (fix 3) --- tests/optimize/test_optimize_reports.py | 14 ++++++++++++-- 1 file changed, 12 insertions(+), 2 deletions(-) diff --git a/tests/optimize/test_optimize_reports.py b/tests/optimize/test_optimize_reports.py index d0c970b33..a2f55920d 100644 --- a/tests/optimize/test_optimize_reports.py +++ b/tests/optimize/test_optimize_reports.py @@ -71,7 +71,12 @@ def test_text_table_bt_results(capsys): ) pair_results = generate_pair_metrics( - ["ETH/BTC"], stake_currency="BTC", starting_balance=4, results=results + ["ETH/BTC"], + stake_currency="BTC", + starting_balance=4, + results=results, + min_date=dt_from_ts(1510688220), + max_date=dt_from_ts(1510700340), ) text_table_bt_results(pair_results, stake_currency="BTC", title="title") text = capsys.readouterr().out @@ -388,7 +393,12 @@ def test_generate_pair_metrics(): ) pair_results = generate_pair_metrics( - ["ETH/BTC"], stake_currency="BTC", starting_balance=2, results=results + ["ETH/BTC"], + stake_currency="BTC", + starting_balance=2, + results=results, + min_date=dt_from_ts(1510688220), + max_date=dt_from_ts(1510700340), ) assert isinstance(pair_results, list) assert len(pair_results) == 2 From 4f2681500a4753a565aa26173971d7e9f8c5c079 Mon Sep 17 00:00:00 2001 From: mrpabloyeah Date: Tue, 25 Mar 2025 21:08:38 +0100 Subject: [PATCH 5/6] Calculate and save all metrics per pair (fix 4) --- tests/optimize/test_optimize_reports.py | 14 ++++++++++++-- 1 file changed, 12 insertions(+), 2 deletions(-) diff --git a/tests/optimize/test_optimize_reports.py b/tests/optimize/test_optimize_reports.py index a2f55920d..aa786c269 100644 --- a/tests/optimize/test_optimize_reports.py +++ b/tests/optimize/test_optimize_reports.py @@ -491,7 +491,12 @@ def test_text_table_exit_reason(capsys): ) exit_reason_stats = generate_tag_metrics( - "exit_reason", starting_balance=22, results=results, skip_nan=False + "exit_reason", + starting_balance=22, + results=results, + min_date=dt_from_ts(1510688220), + max_date=dt_from_ts(1510700340), + skip_nan=False, ) text_table_tags("exit_tag", exit_reason_stats, "BTC") text = capsys.readouterr().out @@ -529,7 +534,12 @@ def test_generate_sell_reason_stats(): ) exit_reason_stats = generate_tag_metrics( - "exit_reason", starting_balance=22, results=results, skip_nan=False + "exit_reason", + starting_balance=22, + results=results, + min_date=dt_from_ts(1510688220), + max_date=dt_from_ts(1510700340), + skip_nan=False, ) roi_result = exit_reason_stats[0] assert roi_result["key"] == "roi" From be5d158761f1d7fa289289f267d4d58745402b38 Mon Sep 17 00:00:00 2001 From: mrpabloyeah Date: Tue, 25 Mar 2025 22:03:22 +0100 Subject: [PATCH 6/6] Calculate and save all metrics per pair (fix 5) --- tests/optimize/test_optimize_reports.py | 19 +++++++++++++++++++ 1 file changed, 19 insertions(+) diff --git a/tests/optimize/test_optimize_reports.py b/tests/optimize/test_optimize_reports.py index aa786c269..ac1cfb320 100644 --- a/tests/optimize/test_optimize_reports.py +++ b/tests/optimize/test_optimize_reports.py @@ -67,6 +67,11 @@ def test_text_table_bt_results(capsys): "profit_ratio": [0.1, 0.2, -0.05], "profit_abs": [0.2, 0.4, -0.1], "trade_duration": [10, 30, 20], + "close_date": [ + dt_utc(2017, 11, 14, 21, 35, 00), + dt_utc(2017, 11, 14, 22, 10, 00), + dt_utc(2017, 11, 14, 22, 43, 00), + ], } ) @@ -386,6 +391,10 @@ def test_generate_pair_metrics(): "profit_ratio": [0.1, 0.2], "profit_abs": [0.2, 0.4], "trade_duration": [10, 30], + "close_date": [ + dt_utc(2017, 11, 14, 21, 35, 00), + dt_utc(2017, 11, 14, 22, 10, 00), + ], "wins": [2, 0], "draws": [0, 0], "losses": [0, 0], @@ -483,6 +492,11 @@ def test_text_table_exit_reason(capsys): "profit_ratio": [0.1, 0.2, -0.1], "profit_abs": [0.2, 0.4, -0.2], "trade_duration": [10, 30, 10], + "close_date": [ + dt_utc(2017, 11, 14, 21, 35, 00), + dt_utc(2017, 11, 14, 22, 10, 00), + dt_utc(2017, 11, 14, 22, 43, 00), + ], "wins": [2, 0, 0], "draws": [0, 0, 0], "losses": [0, 0, 1], @@ -526,6 +540,11 @@ def test_generate_sell_reason_stats(): "profit_ratio": [0.1, 0.2, -0.1], "profit_abs": [0.2, 0.4, -0.2], "trade_duration": [10, 30, 10], + "close_date": [ + dt_utc(2017, 11, 14, 21, 35, 00), + dt_utc(2017, 11, 14, 22, 10, 00), + dt_utc(2017, 11, 14, 22, 43, 00), + ], "wins": [2, 0, 0], "draws": [0, 0, 0], "losses": [0, 0, 1],