diff --git a/src/Managing.Domain/Shared/Helpers/BacktestScore.cs b/src/Managing.Domain/Shared/Helpers/BacktestScore.cs index 6dc3f2d..7c72fcc 100644 --- a/src/Managing.Domain/Shared/Helpers/BacktestScore.cs +++ b/src/Managing.Domain/Shared/Helpers/BacktestScore.cs @@ -5,28 +5,25 @@ public class BacktestScorer // Updated weights without ProfitEfficiency private static readonly Dictionary Weights = new Dictionary { - { "SharpeRatio", 0.22 }, // Increased weight - { "GrowthPercentage", 0.22 }, // Increased weight + { "GrowthPercentage", 0.30 }, // Increased weight for profitability + { "SharpeRatio", 0.18 }, { "MaxDrawdownPc", 0.15 }, - { "WinRate", 0.15 }, // Increased weight - { "HodlComparison", 0.15 }, // Increased weight - { "TradeCount", 0.06 }, - { "RecoveryTime", 0.04 }, - { "RiskAdjustedGrowth", 0.01 } + { "HodlComparison", 0.15 }, + { "WinRate", 0.10 }, + { "ProfitabilityBonus", 0.07 }, // New direct profitability component + { "TradeCount", 0.03 }, + { "RecoveryTime", 0.02 } }; + public static double CalculateTotalScore(BacktestScoringParams p) { try { - // Detect inactive strategies - if (IsInactiveStrategy(p)) - { - return Math.Min(CalculateBaseScore(p) * 0.3, 40); - } + var baseScore = CalculateBaseScore(p); + var finalScore = ApplyProfitabilityRules(baseScore, p); - var score = CalculateBaseScore(p); - return double.Clamp(score, 0, 100); + return double.Clamp(finalScore, 0, 100); } catch { @@ -38,19 +35,85 @@ public class BacktestScorer { var componentScores = new Dictionary { - { "SharpeRatio", CalculateSharpeScore(p.SharpeRatio) }, { "GrowthPercentage", CalculateGrowthScore(p.GrowthPercentage) }, + { "SharpeRatio", CalculateSharpeScore(p.SharpeRatio) }, { "MaxDrawdownPc", CalculateDrawdownScore(p.MaxDrawdownPc) }, - { "WinRate", CalculateWinRateScore(p.WinRate, p.TradeCount) }, { "HodlComparison", CalculateHodlComparisonScore(p.GrowthPercentage, p.HodlPercentage) }, + { "WinRate", CalculateWinRateScore(p.WinRate, p.TradeCount) }, + { "ProfitabilityBonus", CalculateProfitabilityBonus(p.GrowthPercentage) }, { "TradeCount", CalculateTradeCountScore(p.TradeCount) }, - { "RecoveryTime", CalculateRecoveryScore(p.MaxDrawdownRecoveryTime) }, - { "RiskAdjustedGrowth", CalculateRiskAdjustedGrowthScore(p.GrowthPercentage, p.MaxDrawdownPc) } + { "RecoveryTime", CalculateRecoveryScore(p.MaxDrawdownRecoveryTime) } }; return componentScores.Sum(kvp => kvp.Value * Weights[kvp.Key]); } + private static double ApplyProfitabilityRules(double baseScore, BacktestScoringParams p) + { + // 1. Negative PnL Penalty (Core Rule) + if (p.GrowthPercentage < 0) + { + baseScore = Math.Min(baseScore, 70) * GetNegativePnLMultiplier(p.GrowthPercentage); + } + + // 2. Absolute PnL Validation (Additional Recommendation) + if (p.TotalPnL <= 0) + { + baseScore = Math.Min(baseScore, 50); + } + + // 3. Win Rate Validation (Additional Recommendation) + if (p.WinRate < 0.3 && p.TradeCount > 10) + { + baseScore = Math.Min(baseScore, 60); + } + + // 4. Minimum Profit Threshold (Additional Recommendation) + if (p.GrowthPercentage < 2 && p.TradeCount > 5) + { + baseScore = Math.Min(baseScore, 80); + } + + return baseScore; + } + + private static double CalculateGrowthScore(double growthPercentage) + { + // More aggressive penalty for negative growth + if (growthPercentage < 0) + { + return Math.Max(0, 40 + (growthPercentage * 2)); // -10% → 20, -20% → 0 + } + + // Require minimum 5% growth for full score + return growthPercentage switch + { + < 5 => growthPercentage * 15, // 2% → 30, 4% → 60 + _ => 100 + }; + } + + // Existing multiplier calculation + private static double GetNegativePnLMultiplier(double growthPercentage) + { + return growthPercentage switch + { + > -5 => 0.8, + > -10 => 0.6, + > -20 => 0.4, + _ => 0.2 + }; + } + + private static double CalculateProfitabilityBonus(double growthPercentage) + { + return growthPercentage switch + { + > 0 => 100 * (1 - 1 / (1 + growthPercentage / 50)), // Diminishing returns + _ => 0 + }; + } + private static bool IsInactiveStrategy(BacktestScoringParams p) { // Detect strategies with no economic value @@ -69,14 +132,6 @@ public class BacktestScorer }; } - private static double CalculateGrowthScore(double growthPercentage) - { - if (growthPercentage < 0) - return Math.Max(0, 50 + (growthPercentage * 2)); // -5% → 40, -25% → 0 - - return Math.Min(100, (Math.Log(1 + growthPercentage) / Math.Log(1 + 1000)) * 100); - } - private static double CalculateDrawdownScore(double maxDrawdownPc) { return maxDrawdownPc switch