From 9209d110843e8a3ed8f38ad697b6c31b11d22713 Mon Sep 17 00:00:00 2001 From: bearvar Date: Wed, 16 Oct 2024 21:52:42 +0500 Subject: [PATCH 1/9] Add new hyperopt loss function --- .../hyperopt_loss/hyperopt_loss_complex.py | 102 ++++++++++++++++++ 1 file changed, 102 insertions(+) create mode 100644 freqtrade/optimize/hyperopt_loss/hyperopt_loss_complex.py diff --git a/freqtrade/optimize/hyperopt_loss/hyperopt_loss_complex.py b/freqtrade/optimize/hyperopt_loss/hyperopt_loss_complex.py new file mode 100644 index 000000000..afdaf639c --- /dev/null +++ b/freqtrade/optimize/hyperopt_loss/hyperopt_loss_complex.py @@ -0,0 +1,102 @@ +""" +ComplexHyperOptLoss + +This module defines the alternative HyperOptLoss class based on: + - Profit + - Drawdown + - Profit Factor + - Expectancy Ratio + - Winrate + - Amount of trades + +Possible to change: + - `DRAWDOWN_MULT` to penalize drawdown objective for individual needs; + - `TARGET_TRADE_AMOUNT` to adjust amount of trades impact. + - `EXPECTANCY_CONST` to adjust expectancy ratio impact. + - `PF_CONST` to adjust profit factor impact. + - `WINRATE_CONST` to adjust winrate impact. +""" + +from datetime import datetime +from typing import Any, Dict +import numpy as np + +from pandas import DataFrame + +from freqtrade.constants import Config +from freqtrade.data.metrics import calculate_max_drawdown, calculate_expectancy +from freqtrade.optimize.hyperopt import IHyperOptLoss + +# smaller numbers penalize drawdowns more severely +DRAWDOWN_MULT = 0.055 +# A very large number to use as a replacement for infinity +LARGE_NUMBER = 1e6 +# Target trade amount, if higher that TARGET_TRADE_AMOUNT - no penalty +TARGET_TRADE_AMOUNT = 50 +# Coefficient to adjust impact of expectancy +EXPECTANCY_CONST = 2 +# Coefficient to adjust profit factor impact +PF_CONST = 1 +# Coefficient to adjust winrate impact +WINRATE_CONST = 1.2 + + +class ComplexHyperOptLoss(IHyperOptLoss): + @staticmethod + def hyperopt_loss_function( + *, + results: DataFrame, + trade_count: int, + min_date: datetime, + max_date: datetime, + config: Config, + processed: Dict[str, DataFrame], + backtest_stats: Dict[str, Any], + **kwargs, + ) -> float: + total_profit = results["profit_abs"].sum() + + # Calculate profit factor + winning_profit = results.loc[results["profit_abs"] > 0, "profit_abs"].sum() + losing_profit = results.loc[results["profit_abs"] < 0, "profit_abs"].sum() + profit_factor = winning_profit / (abs(losing_profit) + 1e-6) + log_profit_factor = np.log(profit_factor + PF_CONST) + + # Calculate expectancy + expectancy, expectancy_ratio = calculate_expectancy(results) + if expectancy_ratio > 10: + log_expectancy_ratio = np.log(1.01) + else: + log_expectancy_ratio = np.log(expectancy_ratio + EXPECTANCY_CONST) + + # Calculate winrate + winning_trades = results.loc[results["profit_abs"] > 0] + winrate = len(winning_trades) / len(results) + log_winrate_coef = np.log(WINRATE_CONST + winrate) + + # Calculate drawdown + try: + drawdown = calculate_max_drawdown( + results, starting_balance=config["dry_run_wallet"], value_col="profit_abs" + ) + relative_account_drawdown = drawdown.relative_account_drawdown + except ValueError: + relative_account_drawdown = 0 + + # Trade Count Penalty + trade_count_penalty = 1.0 # Default: no penalty + if trade_count < TARGET_TRADE_AMOUNT: + trade_count_penalty = 1 - (abs(trade_count - TARGET_TRADE_AMOUNT) / TARGET_TRADE_AMOUNT) + trade_count_penalty = max(trade_count_penalty, 0.1) + + profit_draw_function = total_profit - (relative_account_drawdown * total_profit) * ( + 1 - DRAWDOWN_MULT + ) + + return -1 * ( + profit_draw_function + * log_profit_factor + * log_expectancy_ratio + * log_winrate_coef + * trade_count_penalty + ) From 121bdbad847b9af29ac785f4f7fc6521a939413d Mon Sep 17 00:00:00 2001 From: Alex <107477559+bearvar@users.noreply.github.com> Date: Thu, 17 Oct 2024 21:42:34 +0500 Subject: [PATCH 2/9] Update and rename hyperopt_loss_complex.py to hyperopt_loss_multi_metric.py --- ...hyperopt_loss_complex.py => hyperopt_loss_multi_metric.py} | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) rename freqtrade/optimize/hyperopt_loss/{hyperopt_loss_complex.py => hyperopt_loss_multi_metric.py} (97%) diff --git a/freqtrade/optimize/hyperopt_loss/hyperopt_loss_complex.py b/freqtrade/optimize/hyperopt_loss/hyperopt_loss_multi_metric.py similarity index 97% rename from freqtrade/optimize/hyperopt_loss/hyperopt_loss_complex.py rename to freqtrade/optimize/hyperopt_loss/hyperopt_loss_multi_metric.py index afdaf639c..5d557104f 100644 --- a/freqtrade/optimize/hyperopt_loss/hyperopt_loss_complex.py +++ b/freqtrade/optimize/hyperopt_loss/hyperopt_loss_multi_metric.py @@ -1,5 +1,5 @@ """ -ComplexHyperOptLoss +MultiMetricHyperOptLoss This module defines the alternative HyperOptLoss class based on: - Profit @@ -41,7 +41,7 @@ PF_CONST = 1 WINRATE_CONST = 1.2 -class ComplexHyperOptLoss(IHyperOptLoss): +class MultiMetricHyperOptLoss(IHyperOptLoss): @staticmethod def hyperopt_loss_function( *, From 6eac798d91f3a6e0e65f260340d7b672f748331b Mon Sep 17 00:00:00 2001 From: bearvar Date: Thu, 17 Oct 2024 21:54:25 +0500 Subject: [PATCH 3/9] Rename ComplexHyperOptLoss to MultiMetricHyperOptLoss. Add it to test_loss_functions_better_profits. --- freqtrade/optimize/hyperopt_loss/hyperopt_loss_multi_metric.py | 2 +- tests/optimize/test_hyperoptloss.py | 1 + 2 files changed, 2 insertions(+), 1 deletion(-) diff --git a/freqtrade/optimize/hyperopt_loss/hyperopt_loss_multi_metric.py b/freqtrade/optimize/hyperopt_loss/hyperopt_loss_multi_metric.py index 5d557104f..4c8b3a2cf 100644 --- a/freqtrade/optimize/hyperopt_loss/hyperopt_loss_multi_metric.py +++ b/freqtrade/optimize/hyperopt_loss/hyperopt_loss_multi_metric.py @@ -99,4 +99,4 @@ class MultiMetricHyperOptLoss(IHyperOptLoss): * log_expectancy_ratio * log_winrate_coef * trade_count_penalty - ) + ) \ No newline at end of file diff --git a/tests/optimize/test_hyperoptloss.py b/tests/optimize/test_hyperoptloss.py index b78cdde30..53de37a0e 100644 --- a/tests/optimize/test_hyperoptloss.py +++ b/tests/optimize/test_hyperoptloss.py @@ -95,6 +95,7 @@ def test_loss_calculation_has_limited_profit(hyperopt_conf, hyperopt_results) -> "MaxDrawDownRelativeHyperOptLoss", "CalmarHyperOptLoss", "ProfitDrawDownHyperOptLoss", + "MultiMetricHyperOptLoss", ], ) def test_loss_functions_better_profits(default_conf, hyperopt_results, lossfunction) -> None: From 578604619a7f6483ce735e4be2857c04f08e930d Mon Sep 17 00:00:00 2001 From: bearvar Date: Thu, 17 Oct 2024 22:07:32 +0500 Subject: [PATCH 4/9] Edit PF_CONST to show it can be decimal. --- freqtrade/optimize/hyperopt_loss/hyperopt_loss_multi_metric.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/freqtrade/optimize/hyperopt_loss/hyperopt_loss_multi_metric.py b/freqtrade/optimize/hyperopt_loss/hyperopt_loss_multi_metric.py index 4c8b3a2cf..3c3c5ca96 100644 --- a/freqtrade/optimize/hyperopt_loss/hyperopt_loss_multi_metric.py +++ b/freqtrade/optimize/hyperopt_loss/hyperopt_loss_multi_metric.py @@ -36,7 +36,7 @@ TARGET_TRADE_AMOUNT = 50 # Coefficient to adjust impact of expectancy EXPECTANCY_CONST = 2 # Coefficient to adjust profit factor impact -PF_CONST = 1 +PF_CONST = 1.0 # Coefficient to adjust winrate impact WINRATE_CONST = 1.2 From 78bf40eaa9257c4abd1244e6c33599e55a635a1f Mon Sep 17 00:00:00 2001 From: bearvar Date: Thu, 17 Oct 2024 22:09:00 +0500 Subject: [PATCH 5/9] Edit EXPECTANCY_CONST to show it can be decimal. --- freqtrade/optimize/hyperopt_loss/hyperopt_loss_multi_metric.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/freqtrade/optimize/hyperopt_loss/hyperopt_loss_multi_metric.py b/freqtrade/optimize/hyperopt_loss/hyperopt_loss_multi_metric.py index 3c3c5ca96..110edc78f 100644 --- a/freqtrade/optimize/hyperopt_loss/hyperopt_loss_multi_metric.py +++ b/freqtrade/optimize/hyperopt_loss/hyperopt_loss_multi_metric.py @@ -34,7 +34,7 @@ LARGE_NUMBER = 1e6 # Target trade amount, if higher that TARGET_TRADE_AMOUNT - no penalty TARGET_TRADE_AMOUNT = 50 # Coefficient to adjust impact of expectancy -EXPECTANCY_CONST = 2 +EXPECTANCY_CONST = 2.0 # Coefficient to adjust profit factor impact PF_CONST = 1.0 # Coefficient to adjust winrate impact From 660af7ec70eb8fb81c99b56c77f58b2b49350f03 Mon Sep 17 00:00:00 2001 From: Matthias Date: Sat, 19 Oct 2024 13:02:27 +0200 Subject: [PATCH 6/9] chore: Fix test failure, sort imports --- .../optimize/hyperopt_loss/hyperopt_loss_multi_metric.py | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/freqtrade/optimize/hyperopt_loss/hyperopt_loss_multi_metric.py b/freqtrade/optimize/hyperopt_loss/hyperopt_loss_multi_metric.py index 110edc78f..d4a399711 100644 --- a/freqtrade/optimize/hyperopt_loss/hyperopt_loss_multi_metric.py +++ b/freqtrade/optimize/hyperopt_loss/hyperopt_loss_multi_metric.py @@ -19,14 +19,15 @@ Possible to change: from datetime import datetime from typing import Any, Dict -import numpy as np +import numpy as np from pandas import DataFrame from freqtrade.constants import Config -from freqtrade.data.metrics import calculate_max_drawdown, calculate_expectancy +from freqtrade.data.metrics import calculate_expectancy, calculate_max_drawdown from freqtrade.optimize.hyperopt import IHyperOptLoss + # smaller numbers penalize drawdowns more severely DRAWDOWN_MULT = 0.055 # A very large number to use as a replacement for infinity @@ -44,7 +45,6 @@ WINRATE_CONST = 1.2 class MultiMetricHyperOptLoss(IHyperOptLoss): @staticmethod def hyperopt_loss_function( - *, results: DataFrame, trade_count: int, min_date: datetime, @@ -99,4 +99,4 @@ class MultiMetricHyperOptLoss(IHyperOptLoss): * log_expectancy_ratio * log_winrate_coef * trade_count_penalty - ) \ No newline at end of file + ) From 1a85dc9b996394bffa276a137532b33014e5c169 Mon Sep 17 00:00:00 2001 From: Matthias Date: Sat, 19 Oct 2024 13:02:45 +0200 Subject: [PATCH 7/9] chore: Update to new code standard --- .../optimize/hyperopt_loss/hyperopt_loss_multi_metric.py | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/freqtrade/optimize/hyperopt_loss/hyperopt_loss_multi_metric.py b/freqtrade/optimize/hyperopt_loss/hyperopt_loss_multi_metric.py index d4a399711..3a11a938d 100644 --- a/freqtrade/optimize/hyperopt_loss/hyperopt_loss_multi_metric.py +++ b/freqtrade/optimize/hyperopt_loss/hyperopt_loss_multi_metric.py @@ -18,7 +18,7 @@ Possible to change: """ from datetime import datetime -from typing import Any, Dict +from typing import Any import numpy as np from pandas import DataFrame @@ -50,8 +50,8 @@ class MultiMetricHyperOptLoss(IHyperOptLoss): min_date: datetime, max_date: datetime, config: Config, - processed: Dict[str, DataFrame], - backtest_stats: Dict[str, Any], + processed: dict[str, DataFrame], + backtest_stats: dict[str, Any], **kwargs, ) -> float: total_profit = results["profit_abs"].sum() From e9d9ebf1565dd39a0f391959d3be000e321954c9 Mon Sep 17 00:00:00 2001 From: Matthias Date: Sat, 19 Oct 2024 13:05:59 +0200 Subject: [PATCH 8/9] docs: Update documentation for MultiMetricHyperOptLoss --- docs/hyperopt.md | 7 ++++--- .../hyperopt_loss/hyperopt_loss_multi_metric.py | 13 +++++++++++++ 2 files changed, 17 insertions(+), 3 deletions(-) diff --git a/docs/hyperopt.md b/docs/hyperopt.md index 43085029c..6788f681a 100644 --- a/docs/hyperopt.md +++ b/docs/hyperopt.md @@ -588,14 +588,15 @@ Currently, the following loss functions are builtin: * `ShortTradeDurHyperOptLoss` - (default legacy Freqtrade hyperoptimization loss function) - Mostly for short trade duration and avoiding losses. * `OnlyProfitHyperOptLoss` - takes only amount of profit into consideration. -* `SharpeHyperOptLoss` - optimizes Sharpe Ratio calculated on trade returns relative to standard deviation. -* `SharpeHyperOptLossDaily` - optimizes Sharpe Ratio calculated on **daily** trade returns relative to standard deviation. -* `SortinoHyperOptLoss` - optimizes Sortino Ratio calculated on trade returns relative to **downside** standard deviation. +* `SharpeHyperOptLoss` - Optimizes Sharpe Ratio calculated on trade returns relative to standard deviation. +* `SharpeHyperOptLossDaily` - Optimizes Sharpe Ratio calculated on **daily** trade returns relative to standard deviation. +* `SortinoHyperOptLoss` - Optimizes Sortino Ratio calculated on trade returns relative to **downside** standard deviation. * `SortinoHyperOptLossDaily` - optimizes Sortino Ratio calculated on **daily** trade returns relative to **downside** standard deviation. * `MaxDrawDownHyperOptLoss` - Optimizes Maximum absolute drawdown. * `MaxDrawDownRelativeHyperOptLoss` - Optimizes both maximum absolute drawdown while also adjusting for maximum relative drawdown. * `CalmarHyperOptLoss` - Optimizes Calmar Ratio calculated on trade returns relative to max drawdown. * `ProfitDrawDownHyperOptLoss` - Optimizes by max Profit & min Drawdown objective. `DRAWDOWN_MULT` variable within the hyperoptloss file can be adjusted to be stricter or more flexible on drawdown purposes. +* `MultiMetricHyperOptLoss` - Optimizes by several key metrics to achieve balanced performance. The primary focus is on maximizing Profit and minimizing Drawdown, while also considering additional metrics such as Profit Factor, Expectancy Ratio and Winrate. Moreover, it applies a penalty for epochs with a low number of trades, encouraging strategies with adequate trade frequency. Creation of a custom loss function is covered in the [Advanced Hyperopt](advanced-hyperopt.md) part of the documentation. diff --git a/freqtrade/optimize/hyperopt_loss/hyperopt_loss_multi_metric.py b/freqtrade/optimize/hyperopt_loss/hyperopt_loss_multi_metric.py index 3a11a938d..62f7e90d5 100644 --- a/freqtrade/optimize/hyperopt_loss/hyperopt_loss_multi_metric.py +++ b/freqtrade/optimize/hyperopt_loss/hyperopt_loss_multi_metric.py @@ -15,6 +15,19 @@ Possible to change: - `EXPECTANCY_CONST` to adjust expectancy ratio impact. - `PF_CONST` to adjust profit factor impact. - `WINRATE_CONST` to adjust winrate impact. + + +DRAWDOWN_MULT variable within the hyperoptloss file can be adjusted to be stricter or more + flexible on drawdown purposes. Smaller numbers penalize drawdowns more severely. +PF_CONST variable adjusts the impact of the Profit Factor on the optimization. +EXPECTANCY_CONST variable controls the influence of the Expectancy Ratio. +WINRATE_CONST variable can be adjusted to increase or decrease impact of winrate. + +PF_CONST, EXPECTANCY_CONST, WINRATE_CONST all operate in a similar manner: + a higher value means that the metric has a lesser impact on the objective, + while a lower value means that it has a greater impact. +TARGET_TRADE_AMOUNT variable sets the minimum number of trades required to avoid penalties. + If the trade amount falls below this threshold, the penalty is applied. """ from datetime import datetime From 0a91189e6a90064bf5f611141ad5c42e12d41732 Mon Sep 17 00:00:00 2001 From: Matthias Date: Sat, 19 Oct 2024 13:06:12 +0200 Subject: [PATCH 9/9] chore: remove unused kwargs --- .../optimize/hyperopt_loss/hyperopt_loss_multi_metric.py | 7 ------- 1 file changed, 7 deletions(-) diff --git a/freqtrade/optimize/hyperopt_loss/hyperopt_loss_multi_metric.py b/freqtrade/optimize/hyperopt_loss/hyperopt_loss_multi_metric.py index 62f7e90d5..de8d117d6 100644 --- a/freqtrade/optimize/hyperopt_loss/hyperopt_loss_multi_metric.py +++ b/freqtrade/optimize/hyperopt_loss/hyperopt_loss_multi_metric.py @@ -30,9 +30,6 @@ TARGET_TRADE_AMOUNT variable sets the minimum number of trades required to avoid If the trade amount falls below this threshold, the penalty is applied. """ -from datetime import datetime -from typing import Any - import numpy as np from pandas import DataFrame @@ -60,11 +57,7 @@ class MultiMetricHyperOptLoss(IHyperOptLoss): def hyperopt_loss_function( results: DataFrame, trade_count: int, - min_date: datetime, - max_date: datetime, config: Config, - processed: dict[str, DataFrame], - backtest_stats: dict[str, Any], **kwargs, ) -> float: total_profit = results["profit_abs"].sum()