""" MultiMetricHyperOptLoss 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. 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. """ import numpy as np from pandas import DataFrame 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 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.0 # Coefficient to adjust profit factor impact PF_CONST = 1.0 # Coefficient to adjust winrate impact WINRATE_CONST = 1.2 class MultiMetricHyperOptLoss(IHyperOptLoss): @staticmethod def hyperopt_loss_function( results: DataFrame, trade_count: int, starting_balance: float, **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_ratio = calculate_expectancy(results) log_expectancy_ratio = np.log(min(10, 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=starting_balance, 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 )