Repository: freqtrade/freqtrade
Pull request: Hyperopt loss function using max drawdown
## Summary

Hyperopt loss function using max drawdown.
As mentioned [here](https://github.com/freqtrade/freqtrade/issues/5514#issuecomment-909055853), the function calculates a multiplier from drawdown, the lower drawdown is, the higher the multiplier.

closes https://github.com/freqtrade/freqtrade/issues/5514


Problem statement:
hyperopt loss function max draw down
Hi!

here a small addition to the loss functions already available.

```
from datetime import datetime
from freqtrade.data.btanalysis import calculate_max_drawdown
from freqtrade.optimize.hyperopt import IHyperOptLoss
from pandas import DataFrame

# optimize for max draw down and profit
# less max draw down more profit -> lower return value
class MaxDrawDownHyperOptLoss(IHyperOptLoss):
    @staticmethod
    def hyperopt_loss_function(results: DataFrame, trade_count: int,
                               min_date: datetime, max_date: datetime,
                               *args, **kwargs) -> float:

        max_drawdown = calculate_max_drawdown(results)
        max_drawdown_rev = 1 / max_drawdown[0]
        total_profit = results['profit_abs'].sum()
        ret = max_drawdown_rev * total_profit

        return -ret
```

Hints from issue discussion:
The correct / proper way to contribute a new loss function is as Pull request.

pasting finished code into an issue, you're basically dumping it "someplace" - which somehow translates to "throwing it there - do the work to incorporate it properly" ...

---

About the function itself - i fear that with compounding, profit will dominate this function.
you might need to be careful with the return-values too - as you're using the relative profits from drawdown, comparing it to the absolute profit (in stake currency).
Hi!The function calculates a multiplier from drawdown, the lower Drawdown is the higher the multiplier.Profit is just a factor, it does not matter if it is ratio or abs. The way here shown works fine. Some could include this kind of calculation also in the existing Sharpe/Sorting Loss functions.Why not a PR? I do not have a clean tree of freqtrade for this. The last time I tried I always messed up history.greetingsAm 31.08.2021 06:46 schrieb Matthias ***@***.***>:
The correct / proper way to contribute a new loss function is as Pull request.
pasting finished code into an issue, you're basically dumping it "someplace" - which somehow translates to "throwing it there - do the work to incorporate it properly" ...

About the function itself - i fear that with compounding, profit will dominate this function.
you might need to be careful with the return-values too - as you're using the relative profits from drawdown, comparing it to the absolute profit (in stake currency).

—You are receiving this because you authored the thread.Reply to this email directly, view it on GitHub, or unsubscribe.Triage notifications on the go with GitHub Mobile for iOS or Android.


Am 31.08.2021 06:46 schrieb Matthias ***@***.***>:
The correct / proper way to contribute a new loss function is as Pull request.
pasting finished code into an issue, you're basically dumping it "someplace" - which somehow translates to "throwing it there - do the work to incorporate it properly" ...

About the function itself - i fear that with compounding, profit will dominate this function.
you might need to be careful with the return-values too - as you're using the relative profits from drawdown, comparing it to the absolute profit (in stake currency).

—You are receiving this because you authored the thread.Reply to this email directly, view it on GitHub, or unsubscribe.Triage notifications on the go with GitHub Mobile for iOS or Android.


Am 31.08.2021 06:46 schrieb Matthias ***@***.***>:
The correct / proper way to contribute a new loss function is as Pull request.
pasting finished code into an issue, you're basically dumping it "someplace" - which somehow translates to "throwing it there - do the work to incorporate it properly" ...

About the function itself - i fear that with compounding, profit will dominate this function.
you might need to be careful with the return-values too - as you're using the relative profits from drawdown, comparing it to the absolute profit (in stake currency).

—You are receiving this because you authored the thread.Reply to this email directly, view it on GitHub, or unsubscribe.Triage notifications on the go with GitHub Mobile for iOS or Android.

[
{
***@***.***": "http://schema.org",
***@***.***": "EmailMessage",
"potentialAction": {
***@***.***": "ViewAction",
"target": "https://github.com/freqtrade/freqtrade/issues/5514#issuecomment-908895114",
"url": "https://github.com/freqtrade/freqtrade/issues/5514#issuecomment-908895114",
"name": "View Issue"
},
"description": "View this Issue on GitHub",
"publisher": {
***@***.***": "Organization",
"name": "GitHub",
"url": "https://github.com"
}
}
]

Commit message: Add Test and docs for MaxDrawDownHyperOptLoss

Patch to review:
diff --git a/docs/hyperopt.md b/docs/hyperopt.md
index 09d43939a87..45e0d444db4 100644
--- a/docs/hyperopt.md
+++ b/docs/hyperopt.md
@@ -60,7 +60,7 @@ optional arguments:
                         Specify what timerange of data to use.
   --data-format-ohlcv {json,jsongz,hdf5}
                         Storage format for downloaded candle (OHLCV) data.
-                        (default: `None`).
+                        (default: `json`).
   --max-open-trades INT
                         Override the value of the `max_open_trades`
                         configuration setting.
@@ -114,7 +114,8 @@ optional arguments:
                         Hyperopt-loss-functions are:
                         ShortTradeDurHyperOptLoss, OnlyProfitHyperOptLoss,
                         SharpeHyperOptLoss, SharpeHyperOptLossDaily,
-                        SortinoHyperOptLoss, SortinoHyperOptLossDaily
+                        SortinoHyperOptLoss, SortinoHyperOptLossDaily,
+                        MaxDrawDownHyperOptLoss
   --disable-param-export
                         Disable automatic hyperopt parameter export.
 
@@ -512,12 +513,13 @@ This class should be in its own file within the `user_data/hyperopts/` directory
 
 Currently, the following loss functions are builtin:
 
-* `ShortTradeDurHyperOptLoss` (default legacy Freqtrade hyperoptimization loss function) - Mostly for short trade duration and avoiding losses.
-* `OnlyProfitHyperOptLoss` (which 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)
-* `SortinoHyperOptLossDaily` (optimizes Sortino Ratio calculated on **daily** trade returns relative to **downside** standard deviation)
+* `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.
+* `SortinoHyperOptLossDaily` - optimizes Sortino Ratio calculated on **daily** trade returns relative to **downside** standard deviation.
+* `MaxDrawDownHyperOptLoss` - Optimizes Maximum drawdown.
 
 Creation of a custom loss function is covered in the [Advanced Hyperopt](advanced-hyperopt.md) part of the documentation.
 
diff --git a/freqtrade/constants.py b/freqtrade/constants.py
index fca319a0f1b..c6b8f0e6208 100644
--- a/freqtrade/constants.py
+++ b/freqtrade/constants.py
@@ -24,7 +24,8 @@
 ORDERTIF_POSSIBILITIES = ['gtc', 'fok', 'ioc']
 HYPEROPT_LOSS_BUILTIN = ['ShortTradeDurHyperOptLoss', 'OnlyProfitHyperOptLoss',
                          'SharpeHyperOptLoss', 'SharpeHyperOptLossDaily',
-                         'SortinoHyperOptLoss', 'SortinoHyperOptLossDaily']
+                         'SortinoHyperOptLoss', 'SortinoHyperOptLossDaily',
+                         'MaxDrawDownHyperOptLoss']
 AVAILABLE_PAIRLISTS = ['StaticPairList', 'VolumePairList',
                        'AgeFilter', 'OffsetFilter', 'PerformanceFilter',
                        'PrecisionFilter', 'PriceFilter', 'RangeStabilityFilter',
diff --git a/freqtrade/optimize/hyperopt_loss_max_drawdown.py b/freqtrade/optimize/hyperopt_loss_max_drawdown.py
new file mode 100644
index 00000000000..6777fb2e86f
--- /dev/null
+++ b/freqtrade/optimize/hyperopt_loss_max_drawdown.py
@@ -0,0 +1,43 @@
+"""
+MaxDrawDownHyperOptLoss
+
+This module defines the alternative HyperOptLoss class which can be used for
+Hyperoptimization.
+"""
+from datetime import datetime
+
+from pandas import DataFrame
+
+from freqtrade.data.btanalysis import calculate_max_drawdown
+from freqtrade.optimize.hyperopt import IHyperOptLoss
+
+
+class MaxDrawDownHyperOptLoss(IHyperOptLoss):
+
+    """
+    Defines the loss function for hyperopt.
+
+    This implementation optimizes for max draw down and profit
+    Less max drawdown more profit -> Lower return value
+    """
+
+    @staticmethod
+    def hyperopt_loss_function(results: DataFrame, trade_count: int,
+                               min_date: datetime, max_date: datetime,
+                               *args, **kwargs) -> float:
+
+        """
+        Objective function.
+
+        Uses profit ratio weighted max_drawdown when drawdown is available.
+        Otherwise directly optimizes profit ratio.
+        """
+        total_profit = results['profit_abs'].sum()
+        try:
+            max_drawdown = calculate_max_drawdown(results, value_col='profit_abs')
+        except ValueError:
+            # No losing trade, therefore no drawdown.
+            return -total_profit
+        max_drawdown_rev = 1 / max_drawdown[0]
+        ret = max_drawdown_rev * total_profit
+        return -ret
diff --git a/tests/optimize/test_hyperoptloss.py b/tests/optimize/test_hyperoptloss.py
index 923e3fc32bc..a39190934be 100644
--- a/tests/optimize/test_hyperoptloss.py
+++ b/tests/optimize/test_hyperoptloss.py
@@ -84,13 +84,14 @@ def test_loss_calculation_has_limited_profit(hyperopt_conf, hyperopt_results) ->
     "SortinoHyperOptLossDaily",
     "SharpeHyperOptLoss",
     "SharpeHyperOptLossDaily",
+    "MaxDrawDownHyperOptLoss",
 ])
 def test_loss_functions_better_profits(default_conf, hyperopt_results, lossfunction) -> None:
     results_over = hyperopt_results.copy()
-    results_over['profit_abs'] = hyperopt_results['profit_abs'] * 2
+    results_over['profit_abs'] = hyperopt_results['profit_abs'] * 2 + 0.2
     results_over['profit_ratio'] = hyperopt_results['profit_ratio'] * 2
     results_under = hyperopt_results.copy()
-    results_under['profit_abs'] = hyperopt_results['profit_abs'] / 2
+    results_under['profit_abs'] = hyperopt_results['profit_abs'] / 2 - 0.2
     results_under['profit_ratio'] = hyperopt_results['profit_ratio'] / 2
 
     default_conf.update({'hyperopt_loss': lossfunction})


Code review comments:
diff_hunk: @@ -0,0 +1,43 @@
+"""
+MaxDrawDownHyperOptLoss
+
+This module defines the alternative HyperOptLoss class which can be used for
+Hyperoptimization.
+"""
+from datetime import datetime
+
+from pandas import DataFrame
+
+from freqtrade.data.btanalysis import calculate_max_drawdown
+from freqtrade.optimize.hyperopt import IHyperOptLoss
+
+
+class MaxDrawDownHyperOptLoss(IHyperOptLoss):
+
+    """
+    Defines the loss function for hyperopt.
+
+    This implementation optimizes for max draw down and profit
+    Less max drawdown more profit -> Lower return value
+    """
+
+    @staticmethod
+    def hyperopt_loss_function(results: DataFrame, trade_count: int,
+                               min_date: datetime, max_date: datetime,
+                               *args, **kwargs) -> float:
+
+        """
+        Objective function.
+
+        Uses profit ratio weighted max_drawdown when drawdown is available.
+        Otherwise directly optimizes profit ratio.
+        """
+        total_profit = results['profit_abs'].sum()
+        try:
+            max_drawdown = calculate_max_drawdown(results, value_col='profit_abs')
+        except ValueError:
+            # No losing trade, therefore no drawdown.
+            return -total_profit
+        max_drawdown_rev = 1 / max_drawdown[0]
+        ret = max_drawdown_rev * total_profit
+        return -ret
path: freqtrade/optimize/hyperopt_loss_max_drawdown.py
text: @user1:
i think these 3 lines can be simplified to `return -total_profit / max_drawdown[0]`