# pragma pylint: disable=missing-docstring, invalid-name, pointless-string-statement
"""
Hyperopt loss function for SimpleStrategy.
Optimizes for:
- Total profit (primary)
- Win rate (secondary)
- Trade count (minimum 20 for significance)
- Drawdown control (penalty for >25% drawdown)
"""
from freqtrade.optimize.hyperopt import IHyperOptLoss
from datetime import datetime
from pandas import DataFrame
from typing import Any
class SimpleStrategyHyperopt(IHyperOptLoss):
@staticmethod
def hyperopt_loss_function(
*,
results: DataFrame,
trade_count: int,
min_date: datetime,
max_date: datetime,
config: dict[str, Any],
processed: dict[str, DataFrame],
backtest_stats: dict[str, Any],
starting_balance: float,
**kwargs,
) -> float:
"""
Custom loss function — LOWER is better (we minimize this).
Scoring priorities:
1. Total profit (most important)
2. Win rate bonus
3. Trade count bonus (at least 20 trades)
4. Drawdown penalty
"""
if trade_count < 10:
return 1e10 # Massive penalty for too few trades
total_profit = backtest_stats['profit_total']
wins = backtest_stats.get('wins', 0)
# Win rate
win_rate = wins / trade_count if trade_count > 0 else 0
# Base score: negative profit (we minimize, so lower = better = more profit)
score = -total_profit
# Bonus for good win rate (>50% is good)
if win_rate > 0.60:
score -= 5
elif win_rate > 0.50:
score -= 2
# Bonus for more trades (more data = more confidence)
if trade_count > 50:
score -= 2
elif trade_count > 20:
score -= 1
# Drawdown penalty
max_drawdown = backtest_stats.get('max_drawdown', 0)
if max_drawdown and max_drawdown > 0.25:
score += (max_drawdown - 0.25) * 50
# Duration factor: prefer strategies that make money faster
days = (max_date - min_date).days
if days > 0:
daily_rate = total_profit / days
score -= daily_rate * 10 # Bonus for faster profits
return score