Update scoring
This commit is contained in:
@@ -170,7 +170,8 @@ public class MessengerService : IMessengerService
|
||||
}
|
||||
}
|
||||
|
||||
public async Task SendGeneticAlgorithmNotification(GeneticRequest request, double bestFitness, object? bestChromosome)
|
||||
public async Task SendGeneticAlgorithmNotification(GeneticRequest request, double bestFitness,
|
||||
object? bestChromosome)
|
||||
{
|
||||
try
|
||||
{
|
||||
@@ -263,7 +264,7 @@ public class MessengerService : IMessengerService
|
||||
$"📊 Total Trades: {tradeCount}\n" +
|
||||
$"💰 Final PnL: ${finalPnl:F2}\n" +
|
||||
$"📈 Growth: {growthPercentage:F1}%\n" +
|
||||
$"📉 Max Drawdown: ${maxDrawdown:C}\n" +
|
||||
$"📉 Max Drawdown: ${maxDrawdown:N}\n" +
|
||||
$"📊 Sharpe Ratio: {sharpeRatio:F2}\n\n" +
|
||||
$"🆔 Backtest ID: {backtest.Id}";
|
||||
|
||||
@@ -272,11 +273,11 @@ public class MessengerService : IMessengerService
|
||||
|
||||
private string BuildGeneticAlgorithmMessage(GeneticRequest request, double bestFitness, object? bestChromosome)
|
||||
{
|
||||
var duration = request.CompletedAt.HasValue
|
||||
? request.CompletedAt.Value - request.CreatedAt
|
||||
var duration = request.CompletedAt.HasValue
|
||||
? request.CompletedAt.Value - request.CreatedAt
|
||||
: TimeSpan.Zero;
|
||||
|
||||
var indicators = request.EligibleIndicators.Any()
|
||||
var indicators = request.EligibleIndicators.Any()
|
||||
? string.Join(", ", request.EligibleIndicators.Select(i => i.ToString()))
|
||||
: "N/A";
|
||||
|
||||
|
||||
@@ -98,16 +98,16 @@ public class BacktestScorer
|
||||
{
|
||||
return component switch
|
||||
{
|
||||
"GrowthPercentage" => $"Growth of {p.GrowthPercentage:F2}% (target: 10% for full score)",
|
||||
"SharpeRatio" => $"Sharpe ratio of {p.SharpeRatio:F2} (target: 4.0 for full score)",
|
||||
"HodlComparison" => $"Strategy vs HODL: {p.GrowthPercentage:F2}% vs {p.HodlPercentage:F2}% (difference: {p.GrowthPercentage - p.HodlPercentage:F2}%)",
|
||||
"WinRate" => $"Win rate of {p.WinRate:F2} with {p.TradeCount} trades", // Show as decimal
|
||||
"ProfitabilityBonus" => $"Bonus for positive growth of {p.GrowthPercentage:F2}%",
|
||||
"TradeCount" => $"{p.TradeCount} trades executed (minimum 5, optimal 50+)",
|
||||
"RecoveryTime" => $"Recovery time: {p.MaxDrawdownRecoveryTime.TotalDays:F1} days",
|
||||
"TestDuration" => $"Test duration: {(p.EndDate - p.StartDate).TotalDays:F1} days",
|
||||
"FeesImpact" => $"Fees: ${p.Fees:F2} ({(p.TotalPnL > 0 ? p.Fees / p.TotalPnL * 100 : 0):F2}% of PnL)",
|
||||
"RiskAdjustedReturn" => $"PnL/TradingBalance Drawdown ratio: {p.TotalPnL / (double)p.MaxDrawdown:F2}:1 (MaxDD: ${p.MaxDrawdown:F2} vs PnL: ${p.TotalPnL:F2})",
|
||||
"GrowthPercentage" => $"Growth of {p.GrowthPercentage:F2}% (target: 20% for full score, 5% for 20 points, 10% for 50 points)",
|
||||
"SharpeRatio" => $"Sharpe ratio of {p.SharpeRatio * 100:F2}, target: 4.0 for full score",
|
||||
"HodlComparison" => $"Strategy vs HODL: {p.GrowthPercentage:F2}% vs {p.HodlPercentage:F2}% (difference: {p.GrowthPercentage - p.HodlPercentage:F2}%, target: +5% for full score)",
|
||||
"WinRate" => $"Win rate of {p.WinRate:F2}% with {p.TradeCount} trades (significance factor: {Math.Min(1, (p.TradeCount - 5) / 50.0):F2})",
|
||||
"ProfitabilityBonus" => $"Bonus for positive growth of {p.GrowthPercentage:F2}% (max 50 points)",
|
||||
"TradeCount" => $"{p.TradeCount} trades executed (5-10: 0-50 points, 10-50: 50-100 points, 50+: 100 points)",
|
||||
"RecoveryTime" => $"Recovery time: {p.MaxDrawdownRecoveryTime.TotalDays:F1} days (timeframe-adjusted max: {GetMaxRecoveryDays(p.Timeframe):F1} days)",
|
||||
"TestDuration" => $"Test duration: {(p.EndDate - p.StartDate).TotalDays:F1} days (min: {GetMinTestDays(p.Timeframe):F1}, optimal: {GetMinTestDays(p.Timeframe) * 3:F1} days)",
|
||||
"FeesImpact" => $"Fees: ${p.Fees:F2} ({(p.TotalPnL > 0 ? p.Fees / p.TotalPnL * 100 : 0):F2}% of PnL, target: below 10% for full score)",
|
||||
"RiskAdjustedReturn" => $"Risk/Reward: {p.TotalPnL / (double)p.MaxDrawdown:F2}:1 (PnL: ${p.TotalPnL:F2}, MaxDD: ${p.MaxDrawdown:F2}, target: >3:1 for full score)",
|
||||
_ => $"Component score: {score:F1}"
|
||||
};
|
||||
}
|
||||
@@ -126,12 +126,12 @@ public class BacktestScorer
|
||||
}
|
||||
|
||||
// 3. Win Rate Validation (Dynamic)
|
||||
if (p.WinRate < 0.3 && p.TradeCount > 10)
|
||||
if (p.WinRate < 30 && p.TradeCount > 10) // winRate is percentage, so 30 = 30%
|
||||
{
|
||||
var winRatePenalty = (0.3 - p.WinRate) * 0.5; // 50% penalty per 10% below 30%
|
||||
var winRatePenalty = (30 - p.WinRate) * 0.5; // 50% penalty per 10% below 30%
|
||||
var newMultiplier = Math.Max(0.2, 1 - winRatePenalty);
|
||||
penaltyMultiplier *= newMultiplier;
|
||||
result.AddPenaltyCheck("Low Win Rate", newMultiplier, $"Win rate of {p.WinRate * 100:F1}% below 30% threshold applied {winRatePenalty:F1}% penalty");
|
||||
result.AddPenaltyCheck("Low Win Rate", newMultiplier, $"Win rate of {p.WinRate:F2}% below 30% threshold applied {winRatePenalty:F1}% penalty");
|
||||
}
|
||||
|
||||
// 4. Minimum Profit Threshold (Dynamic)
|
||||
@@ -246,8 +246,9 @@ public class BacktestScorer
|
||||
|
||||
private static double CalculateWinRateScore(double winRate, int tradeCount, BacktestScoringResult result)
|
||||
{
|
||||
// Use winRate as a decimal (e.g., 0.55 for 55%)
|
||||
var baseScore = winRate;
|
||||
// winRate is passed as percentage (e.g., 23.45 for 23.45%), convert to decimal for scoring
|
||||
var winRateDecimal = winRate / 100.0;
|
||||
var baseScore = winRateDecimal;
|
||||
|
||||
// Significance factor - more aggressive
|
||||
var significanceFactor = Math.Min(1, (tradeCount - 5) / 50.0); // Start at 5 trades, full significance at 55 trades
|
||||
@@ -379,4 +380,33 @@ public class BacktestScorer
|
||||
_ => Math.Max(0, riskRewardRatio * 40) // 0-0.5:1: 0-20 points
|
||||
};
|
||||
}
|
||||
|
||||
// Helper methods for message generation
|
||||
private static double GetMaxRecoveryDays(Timeframe timeframe)
|
||||
{
|
||||
return timeframe switch
|
||||
{
|
||||
Timeframe.FiveMinutes => 3.0,
|
||||
Timeframe.FifteenMinutes => 5.0,
|
||||
Timeframe.ThirtyMinutes => 10.0,
|
||||
Timeframe.OneHour => 15.0,
|
||||
Timeframe.FourHour => 30.0,
|
||||
Timeframe.OneDay => 90.0,
|
||||
_ => 30.0
|
||||
};
|
||||
}
|
||||
|
||||
private static double GetMinTestDays(Timeframe timeframe)
|
||||
{
|
||||
return timeframe switch
|
||||
{
|
||||
Timeframe.FiveMinutes => 14.0,
|
||||
Timeframe.FifteenMinutes => 28.0,
|
||||
Timeframe.ThirtyMinutes => 56.0,
|
||||
Timeframe.OneHour => 84.0,
|
||||
Timeframe.FourHour => 120.0,
|
||||
Timeframe.OneDay => 90.0,
|
||||
_ => 21.0
|
||||
};
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user