mirror of
https://github.com/freqtrade/freqtrade.git
synced 2025-11-29 08:33:07 +00:00
Merge pull request #11618 from mrpabloyeah/hyperopt_loss_max_drawdown_per_pair
Add new loss function based on profit/drawdown ratio per pair
This commit is contained in:
@@ -79,6 +79,7 @@ options:
|
||||
SortinoHyperOptLoss, SortinoHyperOptLossDaily,
|
||||
CalmarHyperOptLoss, MaxDrawDownHyperOptLoss,
|
||||
MaxDrawDownRelativeHyperOptLoss,
|
||||
MaxDrawDownPerPairHyperOptLoss,
|
||||
ProfitDrawDownHyperOptLoss, MultiMetricHyperOptLoss
|
||||
--disable-param-export
|
||||
Disable automatic hyperopt parameter export.
|
||||
|
||||
@@ -471,6 +471,7 @@ Currently, the following loss functions are builtin:
|
||||
* `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.
|
||||
* `MaxDrawDownPerPairHyperOptLoss` - Calculates the profit/drawdown ratio per pair and returns the worst result as objective, forcing hyperopt to optimize the parameters for all pairs in the pairlist. This way, we prevent one or more pairs with good results from inflating the metrics, while the pairs with poor results are not represented and therefore not optimized.
|
||||
* `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.
|
||||
|
||||
@@ -37,6 +37,7 @@ HYPEROPT_LOSS_BUILTIN = [
|
||||
"CalmarHyperOptLoss",
|
||||
"MaxDrawDownHyperOptLoss",
|
||||
"MaxDrawDownRelativeHyperOptLoss",
|
||||
"MaxDrawDownPerPairHyperOptLoss",
|
||||
"ProfitDrawDownHyperOptLoss",
|
||||
"MultiMetricHyperOptLoss",
|
||||
]
|
||||
|
||||
@@ -0,0 +1,59 @@
|
||||
"""
|
||||
MaxDrawDownPerPairHyperOptLoss
|
||||
|
||||
This module defines the alternative HyperOptLoss class which can be used for
|
||||
Hyperoptimization.
|
||||
"""
|
||||
|
||||
from typing import Any
|
||||
|
||||
from freqtrade.optimize.hyperopt import IHyperOptLoss
|
||||
|
||||
|
||||
class MaxDrawDownPerPairHyperOptLoss(IHyperOptLoss):
|
||||
"""
|
||||
Defines the loss function for hyperopt.
|
||||
|
||||
This implementation calculates the profit/drawdown ratio per pair and
|
||||
returns the worst result as objective, forcing hyperopt to optimize
|
||||
the parameters for all pairs in the pairlist.
|
||||
|
||||
This way, we prevent one or more pairs with good results from inflating
|
||||
the metrics, while the rest of the pairs with poor results are not
|
||||
represented and therefore not optimized.
|
||||
"""
|
||||
|
||||
@staticmethod
|
||||
def hyperopt_loss_function(backtest_stats: dict[str, Any], *args, **kwargs) -> float:
|
||||
"""
|
||||
Objective function, returns smaller number for better results.
|
||||
"""
|
||||
|
||||
##############################################
|
||||
# Configurable parameters
|
||||
##############################################
|
||||
# Minimum acceptable profit/drawdown per pair
|
||||
min_acceptable_profit_dd = 1.0
|
||||
# Penalty when acceptable minimum are not met
|
||||
penalty = 20
|
||||
##############################################
|
||||
|
||||
score_per_pair = []
|
||||
for p in backtest_stats["results_per_pair"]:
|
||||
if p["key"] != "TOTAL":
|
||||
profit = p.get("profit_total_abs", 0)
|
||||
drawdown = p.get("max_drawdown_abs", 0)
|
||||
|
||||
if drawdown != 0 and profit != 0:
|
||||
profit_dd = profit / drawdown
|
||||
else:
|
||||
profit_dd = profit
|
||||
|
||||
if profit_dd < min_acceptable_profit_dd:
|
||||
score = profit_dd - penalty
|
||||
else:
|
||||
score = profit_dd
|
||||
|
||||
score_per_pair.append(score)
|
||||
|
||||
return -min(score_per_pair)
|
||||
@@ -153,6 +153,7 @@ def test_loss_calculation_has_limited_profit(hyperopt_conf, hyperopt_results) ->
|
||||
"SharpeHyperOptLossDaily",
|
||||
"MaxDrawDownHyperOptLoss",
|
||||
"MaxDrawDownRelativeHyperOptLoss",
|
||||
"MaxDrawDownPerPairHyperOptLoss",
|
||||
"CalmarHyperOptLoss",
|
||||
"ProfitDrawDownHyperOptLoss",
|
||||
"MultiMetricHyperOptLoss",
|
||||
@@ -165,6 +166,34 @@ def test_loss_functions_better_profits(default_conf, hyperopt_results, lossfunct
|
||||
results_under = hyperopt_results.copy()
|
||||
results_under["profit_abs"] = hyperopt_results["profit_abs"] / 2 - 0.2
|
||||
results_under["profit_ratio"] = hyperopt_results["profit_ratio"] / 2
|
||||
pair_results = [
|
||||
{
|
||||
"key": "ETH/USDT",
|
||||
"max_drawdown_abs": 50.0,
|
||||
"profit_total_abs": 100.0,
|
||||
},
|
||||
{
|
||||
"key": "BTC/USDT",
|
||||
"max_drawdown_abs": 50.0,
|
||||
"profit_total_abs": 100.0,
|
||||
},
|
||||
]
|
||||
pair_results_over = [
|
||||
{
|
||||
**p,
|
||||
"max_drawdown_abs": p["max_drawdown_abs"] * 0.5,
|
||||
"profit_total_abs": p["profit_total_abs"] * 2,
|
||||
}
|
||||
for p in pair_results
|
||||
]
|
||||
pair_results_under = [
|
||||
{
|
||||
**p,
|
||||
"max_drawdown_abs": p["max_drawdown_abs"] * 2,
|
||||
"profit_total_abs": p["profit_total_abs"] * 0.5,
|
||||
}
|
||||
for p in pair_results
|
||||
]
|
||||
|
||||
default_conf.update({"hyperopt_loss": lossfunction})
|
||||
hl = HyperOptLossResolver.load_hyperoptloss(default_conf)
|
||||
@@ -175,7 +204,10 @@ def test_loss_functions_better_profits(default_conf, hyperopt_results, lossfunct
|
||||
max_date=datetime(2019, 5, 1),
|
||||
config=default_conf,
|
||||
processed=None,
|
||||
backtest_stats={"profit_total": hyperopt_results["profit_abs"].sum()},
|
||||
backtest_stats={
|
||||
"profit_total": hyperopt_results["profit_abs"].sum(),
|
||||
"results_per_pair": pair_results,
|
||||
},
|
||||
starting_balance=default_conf["dry_run_wallet"],
|
||||
)
|
||||
over = hl.hyperopt_loss_function(
|
||||
@@ -185,7 +217,10 @@ def test_loss_functions_better_profits(default_conf, hyperopt_results, lossfunct
|
||||
max_date=datetime(2019, 5, 1),
|
||||
config=default_conf,
|
||||
processed=None,
|
||||
backtest_stats={"profit_total": results_over["profit_abs"].sum()},
|
||||
backtest_stats={
|
||||
"profit_total": results_over["profit_abs"].sum(),
|
||||
"results_per_pair": pair_results_over,
|
||||
},
|
||||
starting_balance=default_conf["dry_run_wallet"],
|
||||
)
|
||||
under = hl.hyperopt_loss_function(
|
||||
@@ -195,7 +230,10 @@ def test_loss_functions_better_profits(default_conf, hyperopt_results, lossfunct
|
||||
max_date=datetime(2019, 5, 1),
|
||||
config=default_conf,
|
||||
processed=None,
|
||||
backtest_stats={"profit_total": results_under["profit_abs"].sum()},
|
||||
backtest_stats={
|
||||
"profit_total": results_under["profit_abs"].sum(),
|
||||
"results_per_pair": pair_results_under,
|
||||
},
|
||||
starting_balance=default_conf["dry_run_wallet"],
|
||||
)
|
||||
assert over < correct
|
||||
|
||||
Reference in New Issue
Block a user