Add scoring message
This commit is contained in:
@@ -16,4 +16,5 @@ public class LightBacktestResponse
|
||||
public decimal Fees { get; set; }
|
||||
public double? SharpeRatio { get; set; }
|
||||
public double Score { get; set; }
|
||||
public string ScoreMessage { get; set; } = string.Empty;
|
||||
}
|
||||
@@ -317,7 +317,7 @@ namespace Managing.Application.Backtesting
|
||||
timeframe: config.Timeframe
|
||||
);
|
||||
|
||||
var score = BacktestScorer.CalculateTotalScore(scoringParams);
|
||||
var scoringResult = BacktestScorer.CalculateDetailedScore(scoringParams);
|
||||
|
||||
// Create backtest result with conditional candles and indicators values
|
||||
var result = new Backtest(config, bot.Positions, bot.Signals.ToList(),
|
||||
@@ -334,7 +334,8 @@ namespace Managing.Application.Backtesting
|
||||
IndicatorsValues = withCandles
|
||||
? AggregateValues(indicatorsValues, bot.IndicatorsValues)
|
||||
: new Dictionary<IndicatorType, IndicatorsResultBase>(),
|
||||
Score = score,
|
||||
Score = scoringResult.Score,
|
||||
ScoreMessage = scoringResult.SummaryMessage,
|
||||
Id = Guid.NewGuid().ToString(),
|
||||
RequestId = requestId,
|
||||
Metadata = metadata,
|
||||
|
||||
@@ -60,6 +60,7 @@ public class Backtest
|
||||
[Required] public double Score { get; set; }
|
||||
public string RequestId { get; set; }
|
||||
public object? Metadata { get; set; }
|
||||
public string ScoreMessage { get; set; } = string.Empty;
|
||||
|
||||
/// <summary>
|
||||
/// Creates a new TradingBotConfig based on this backtest's configuration for starting a live bot.
|
||||
|
||||
114
src/Managing.Domain/Backtests/BacktestScoringResult.cs
Normal file
114
src/Managing.Domain/Backtests/BacktestScoringResult.cs
Normal file
@@ -0,0 +1,114 @@
|
||||
using System.Text;
|
||||
|
||||
namespace Managing.Domain.Backtests;
|
||||
|
||||
public class BacktestScoringResult
|
||||
{
|
||||
public double Score { get; set; }
|
||||
public List<ScoringCheck> Checks { get; set; } = new();
|
||||
public string SummaryMessage { get; set; } = string.Empty;
|
||||
|
||||
public BacktestScoringResult(double score)
|
||||
{
|
||||
Score = score;
|
||||
}
|
||||
|
||||
public void AddCheck(string component, double score, double weight, string message, bool passed = true)
|
||||
{
|
||||
Checks.Add(new ScoringCheck
|
||||
{
|
||||
Component = component,
|
||||
Score = score,
|
||||
Weight = weight,
|
||||
Message = message,
|
||||
Passed = passed
|
||||
});
|
||||
}
|
||||
|
||||
public void AddEarlyExitCheck(string reason, string message)
|
||||
{
|
||||
Checks.Add(new ScoringCheck
|
||||
{
|
||||
Component = "Early Exit",
|
||||
Score = 0,
|
||||
Weight = 0,
|
||||
Message = message,
|
||||
Passed = false,
|
||||
IsEarlyExit = true
|
||||
});
|
||||
}
|
||||
|
||||
public void AddPenaltyCheck(string component, double penaltyMultiplier, string message)
|
||||
{
|
||||
Checks.Add(new ScoringCheck
|
||||
{
|
||||
Component = component,
|
||||
Score = 0,
|
||||
Weight = 0,
|
||||
Message = message,
|
||||
Passed = penaltyMultiplier >= 1.0,
|
||||
IsPenalty = true,
|
||||
PenaltyMultiplier = penaltyMultiplier
|
||||
});
|
||||
}
|
||||
|
||||
public string GenerateSummaryMessage()
|
||||
{
|
||||
if (Score == 0)
|
||||
{
|
||||
var earlyExit = Checks.FirstOrDefault(c => c.IsEarlyExit);
|
||||
if (earlyExit != null)
|
||||
{
|
||||
return $"Score: 0 - {earlyExit.Message}";
|
||||
}
|
||||
}
|
||||
|
||||
var passedChecks = Checks.Where(c => c.Passed && !c.IsEarlyExit && !c.IsPenalty).ToList();
|
||||
var failedChecks = Checks.Where(c => !c.Passed && !c.IsEarlyExit && !c.IsPenalty).ToList();
|
||||
var penalties = Checks.Where(c => c.IsPenalty).ToList();
|
||||
|
||||
var summary = new StringBuilder();
|
||||
summary.AppendLine($"Final Score: {Score:F1}/100");
|
||||
|
||||
if (passedChecks.Any())
|
||||
{
|
||||
summary.AppendLine($"✅ Passed Checks ({passedChecks.Count}):");
|
||||
foreach (var check in passedChecks.OrderByDescending(c => c.Score * c.Weight))
|
||||
{
|
||||
summary.AppendLine($" • {check.Component}: {check.Score:F1} points ({check.Message})");
|
||||
}
|
||||
}
|
||||
|
||||
if (failedChecks.Any())
|
||||
{
|
||||
summary.AppendLine($"❌ Failed Checks ({failedChecks.Count}):");
|
||||
foreach (var check in failedChecks)
|
||||
{
|
||||
summary.AppendLine($" • {check.Component}: {check.Message}");
|
||||
}
|
||||
}
|
||||
|
||||
if (penalties.Any())
|
||||
{
|
||||
summary.AppendLine($"⚠️ Applied Penalties ({penalties.Count}):");
|
||||
foreach (var penalty in penalties)
|
||||
{
|
||||
summary.AppendLine($" • {penalty.Component}: {penalty.Message}");
|
||||
}
|
||||
}
|
||||
|
||||
return summary.ToString().TrimEnd();
|
||||
}
|
||||
}
|
||||
|
||||
public class ScoringCheck
|
||||
{
|
||||
public string Component { get; set; } = string.Empty;
|
||||
public double Score { get; set; }
|
||||
public double Weight { get; set; }
|
||||
public string Message { get; set; } = string.Empty;
|
||||
public bool Passed { get; set; }
|
||||
public bool IsEarlyExit { get; set; }
|
||||
public bool IsPenalty { get; set; }
|
||||
public double PenaltyMultiplier { get; set; }
|
||||
}
|
||||
@@ -16,4 +16,5 @@ public class LightBacktest
|
||||
public decimal Fees { get; set; }
|
||||
public double? SharpeRatio { get; set; }
|
||||
public double Score { get; set; }
|
||||
public string ScoreMessage { get; set; } = string.Empty;
|
||||
}
|
||||
@@ -21,58 +21,101 @@ public class BacktestScorer
|
||||
|
||||
public static double CalculateTotalScore(BacktestScoringParams p)
|
||||
{
|
||||
var result = CalculateDetailedScore(p);
|
||||
return result.Score;
|
||||
}
|
||||
|
||||
public static BacktestScoringResult CalculateDetailedScore(BacktestScoringParams p)
|
||||
{
|
||||
var result = new BacktestScoringResult(0);
|
||||
|
||||
try
|
||||
{
|
||||
// Early exit for no positions
|
||||
if (p.TradeCount == 0)
|
||||
{
|
||||
return 0;
|
||||
result.AddEarlyExitCheck("No Trades", "No trading positions were taken during the backtest period. A strategy must execute trades to be evaluated.");
|
||||
return result;
|
||||
}
|
||||
|
||||
// Early exit for negative PnL - should be 0 score
|
||||
if (p.TotalPnL <= 0)
|
||||
{
|
||||
return 0;
|
||||
result.AddEarlyExitCheck("Negative PnL", $"Total profit/loss is negative (${p.TotalPnL:F2}). Only profitable strategies receive scores.");
|
||||
return result;
|
||||
}
|
||||
|
||||
// Early exit if strategy significantly underperforms HODL (more than 2% worse)
|
||||
if (p.GrowthPercentage < p.HodlPercentage - 2)
|
||||
{
|
||||
return 0;
|
||||
result.AddEarlyExitCheck("HODL Underperformance", $"Strategy growth ({p.GrowthPercentage:F2}%) significantly underperforms HODL ({p.HodlPercentage:F2}%) by more than 2%. Buy-and-hold would have been more profitable.");
|
||||
return result;
|
||||
}
|
||||
|
||||
var baseScore = CalculateBaseScore(p);
|
||||
var finalScore = ApplyProfitabilityRules(baseScore, p);
|
||||
var baseScore = CalculateBaseScore(p, result);
|
||||
var finalScore = ApplyProfitabilityRules(baseScore, p, result);
|
||||
|
||||
return double.Clamp(finalScore, 0, 100);
|
||||
result.Score = double.Clamp(finalScore, 0, 100);
|
||||
result.SummaryMessage = result.GenerateSummaryMessage();
|
||||
|
||||
return result;
|
||||
}
|
||||
catch
|
||||
catch (Exception ex)
|
||||
{
|
||||
return 0;
|
||||
result.AddEarlyExitCheck("Calculation Error", $"An error occurred during score calculation: {ex.Message}");
|
||||
return result;
|
||||
}
|
||||
}
|
||||
|
||||
private static double CalculateBaseScore(BacktestScoringParams p)
|
||||
private static double CalculateBaseScore(BacktestScoringParams p, BacktestScoringResult result)
|
||||
{
|
||||
var componentScores = new Dictionary<string, double>
|
||||
{
|
||||
{ "GrowthPercentage", CalculateGrowthScore(p.GrowthPercentage) },
|
||||
{ "SharpeRatio", CalculateSharpeScore(p.SharpeRatio) },
|
||||
{ "MaxDrawdownUsd", CalculateDrawdownUsdScore(p.MaxDrawdown, p.InitialBalance) },
|
||||
{ "HodlComparison", CalculateHodlComparisonScore(p.GrowthPercentage, p.HodlPercentage) },
|
||||
{ "WinRate", CalculateWinRateScore(p.WinRate, p.TradeCount) },
|
||||
{ "ProfitabilityBonus", CalculateProfitabilityBonus(p.GrowthPercentage) },
|
||||
{ "TradeCount", CalculateTradeCountScore(p.TradeCount) },
|
||||
{ "RecoveryTime", CalculateRecoveryScore(p.MaxDrawdownRecoveryTime, p.Timeframe) },
|
||||
{ "TestDuration", CalculateTestDurationScore(p.StartDate, p.EndDate, p.Timeframe) },
|
||||
{ "FeesImpact", CalculateFeesImpactScore(p.FeesPaid, p.InitialBalance, (decimal)p.TotalPnL) },
|
||||
{ "RiskAdjustedReturn", CalculateRiskAdjustedReturnScore(p.TotalPnL, p.MaxDrawdown, p.InitialBalance) }
|
||||
{ "GrowthPercentage", CalculateGrowthScore(p.GrowthPercentage, result) },
|
||||
{ "SharpeRatio", CalculateSharpeScore(p.SharpeRatio, result) },
|
||||
{ "MaxDrawdownUsd", CalculateDrawdownUsdScore(p.MaxDrawdown, p.InitialBalance, result) },
|
||||
{ "HodlComparison", CalculateHodlComparisonScore(p.GrowthPercentage, p.HodlPercentage, result) },
|
||||
{ "WinRate", CalculateWinRateScore(p.WinRate, p.TradeCount, result) },
|
||||
{ "ProfitabilityBonus", CalculateProfitabilityBonus(p.GrowthPercentage, result) },
|
||||
{ "TradeCount", CalculateTradeCountScore(p.TradeCount, result) },
|
||||
{ "RecoveryTime", CalculateRecoveryScore(p.MaxDrawdownRecoveryTime, p.Timeframe, result) },
|
||||
{ "TestDuration", CalculateTestDurationScore(p.StartDate, p.EndDate, p.Timeframe, result) },
|
||||
{ "FeesImpact", CalculateFeesImpactScore(p.FeesPaid, p.InitialBalance, (decimal)p.TotalPnL, result) },
|
||||
{ "RiskAdjustedReturn", CalculateRiskAdjustedReturnScore(p.TotalPnL, p.MaxDrawdown, p.InitialBalance, result) }
|
||||
};
|
||||
|
||||
return componentScores.Sum(kvp => kvp.Value * Weights[kvp.Key]);
|
||||
var totalScore = componentScores.Sum(kvp => kvp.Value * Weights[kvp.Key]);
|
||||
|
||||
// Add component scores to result
|
||||
foreach (var kvp in componentScores)
|
||||
{
|
||||
var passed = kvp.Value > 0;
|
||||
result.AddCheck(kvp.Key, kvp.Value, Weights[kvp.Key], GetComponentMessage(kvp.Key, kvp.Value, p), passed);
|
||||
}
|
||||
|
||||
return totalScore;
|
||||
}
|
||||
|
||||
private static double ApplyProfitabilityRules(double baseScore, BacktestScoringParams p)
|
||||
private static string GetComponentMessage(string component, double score, BacktestScoringParams p)
|
||||
{
|
||||
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)",
|
||||
"MaxDrawdownUsd" => $"Max drawdown of ${p.MaxDrawdown:F2} ({p.MaxDrawdownPc:F1}% of balance)",
|
||||
"HodlComparison" => $"Strategy vs HODL: {p.GrowthPercentage:F2}% vs {p.HodlPercentage:F2}% (difference: {p.GrowthPercentage - p.HodlPercentage:F2}%)",
|
||||
"WinRate" => $"Win rate of {p.WinRate * 100:F1}% with {p.TradeCount} trades",
|
||||
"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.FeesPaid:F2} ({p.FeesPaid / p.InitialBalance * 100:F2}% of balance)",
|
||||
"RiskAdjustedReturn" => $"PnL/Drawdown ratio: {p.TotalPnL / (double)p.MaxDrawdown:F2}:1",
|
||||
_ => $"Component score: {score:F1}"
|
||||
};
|
||||
}
|
||||
|
||||
private static double ApplyProfitabilityRules(double baseScore, BacktestScoringParams p, BacktestScoringResult result)
|
||||
{
|
||||
var penaltyMultiplier = 1.0;
|
||||
|
||||
@@ -80,30 +123,36 @@ public class BacktestScorer
|
||||
if (p.GrowthPercentage < 0)
|
||||
{
|
||||
var negativePenalty = Math.Abs(p.GrowthPercentage) * 0.1; // 10% penalty per 1% loss
|
||||
penaltyMultiplier *= Math.Max(0.1, 1 - negativePenalty);
|
||||
var newMultiplier = Math.Max(0.1, 1 - negativePenalty);
|
||||
penaltyMultiplier *= newMultiplier;
|
||||
result.AddPenaltyCheck("Negative Growth", newMultiplier, $"Negative growth of {p.GrowthPercentage:F2}% applied {negativePenalty:F1}% penalty");
|
||||
}
|
||||
|
||||
// Note: Negative PnL is now handled by early exit in CalculateTotalScore
|
||||
|
||||
// 3. Win Rate Validation (Dynamic)
|
||||
if (p.WinRate < 0.3 && p.TradeCount > 10)
|
||||
{
|
||||
var winRatePenalty = (0.3 - p.WinRate) * 0.5; // 50% penalty per 10% below 30%
|
||||
penaltyMultiplier *= Math.Max(0.2, 1 - winRatePenalty);
|
||||
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");
|
||||
}
|
||||
|
||||
// 4. Minimum Profit Threshold (Dynamic)
|
||||
if (p.GrowthPercentage < 2 && p.TradeCount > 5)
|
||||
{
|
||||
var profitPenalty = (2 - p.GrowthPercentage) * 0.1; // 10% penalty per 1% below 2%
|
||||
penaltyMultiplier *= Math.Max(0.5, 1 - profitPenalty);
|
||||
var newMultiplier = Math.Max(0.5, 1 - profitPenalty);
|
||||
penaltyMultiplier *= newMultiplier;
|
||||
result.AddPenaltyCheck("Low Profit", newMultiplier, $"Growth of {p.GrowthPercentage:F2}% below 2% minimum applied {profitPenalty:F1}% penalty");
|
||||
}
|
||||
|
||||
// 5. Drawdown Penalty (Dynamic) - Enhanced to consider PnL ratio
|
||||
if (p.MaxDrawdownPc > 20)
|
||||
{
|
||||
var drawdownPenalty = (p.MaxDrawdownPc - 20) * 0.02; // 2% penalty per 1% above 20%
|
||||
penaltyMultiplier *= Math.Max(0.3, 1 - drawdownPenalty);
|
||||
var newMultiplier = Math.Max(0.3, 1 - drawdownPenalty);
|
||||
penaltyMultiplier *= newMultiplier;
|
||||
result.AddPenaltyCheck("High Drawdown", newMultiplier, $"Drawdown of {p.MaxDrawdownPc:F1}% above 20% threshold applied {drawdownPenalty:F1}% penalty");
|
||||
}
|
||||
|
||||
// 6. Enhanced Drawdown vs PnL Penalty
|
||||
@@ -113,7 +162,9 @@ public class BacktestScorer
|
||||
if (drawdownToPnLRatio > 1.5) // If drawdown is more than 150% of PnL
|
||||
{
|
||||
var ratioPenalty = (drawdownToPnLRatio - 1.5) * 0.2; // 20% penalty per 0.5 ratio above 1.5
|
||||
penaltyMultiplier *= Math.Max(0.2, 1 - ratioPenalty);
|
||||
var newMultiplier = Math.Max(0.2, 1 - ratioPenalty);
|
||||
penaltyMultiplier *= newMultiplier;
|
||||
result.AddPenaltyCheck("Poor Risk/Reward", newMultiplier, $"Drawdown/PnL ratio of {drawdownToPnLRatio:F2}:1 above 1.5:1 threshold applied {ratioPenalty:F1}% penalty");
|
||||
}
|
||||
}
|
||||
|
||||
@@ -122,7 +173,9 @@ public class BacktestScorer
|
||||
if (testDurationDays < 30)
|
||||
{
|
||||
var durationPenalty = (30 - testDurationDays) * 0.02; // 2% penalty per day below 30
|
||||
penaltyMultiplier *= Math.Max(0.5, 1 - durationPenalty);
|
||||
var newMultiplier = Math.Max(0.5, 1 - durationPenalty);
|
||||
penaltyMultiplier *= newMultiplier;
|
||||
result.AddPenaltyCheck("Short Test Duration", newMultiplier, $"Test duration of {testDurationDays:F1} days below 30-day minimum applied {durationPenalty:F1}% penalty");
|
||||
}
|
||||
|
||||
// 8. HODL Underperformance Penalty (Dynamic)
|
||||
@@ -130,13 +183,15 @@ public class BacktestScorer
|
||||
{
|
||||
var hodlUnderperformance = p.HodlPercentage - p.GrowthPercentage;
|
||||
var hodlPenalty = hodlUnderperformance * 0.3; // 30% penalty per 1% underperformance
|
||||
penaltyMultiplier *= Math.Max(0.1, 1 - hodlPenalty);
|
||||
var newMultiplier = Math.Max(0.1, 1 - hodlPenalty);
|
||||
penaltyMultiplier *= newMultiplier;
|
||||
result.AddPenaltyCheck("HODL Underperformance", newMultiplier, $"Underperforming HODL by {hodlUnderperformance:F2}% applied {hodlPenalty:F1}% penalty");
|
||||
}
|
||||
|
||||
return baseScore * penaltyMultiplier;
|
||||
}
|
||||
|
||||
private static double CalculateGrowthScore(double growthPercentage)
|
||||
private static double CalculateGrowthScore(double growthPercentage, BacktestScoringResult result)
|
||||
{
|
||||
// More aggressive scoring - harder to reach 100
|
||||
if (growthPercentage < 0)
|
||||
@@ -164,7 +219,7 @@ public class BacktestScorer
|
||||
};
|
||||
}
|
||||
|
||||
private static double CalculateProfitabilityBonus(double growthPercentage)
|
||||
private static double CalculateProfitabilityBonus(double growthPercentage, BacktestScoringResult result)
|
||||
{
|
||||
return growthPercentage switch
|
||||
{
|
||||
@@ -173,7 +228,7 @@ public class BacktestScorer
|
||||
};
|
||||
}
|
||||
|
||||
private static double CalculateSharpeScore(double sharpeRatio)
|
||||
private static double CalculateSharpeScore(double sharpeRatio, BacktestScoringResult result)
|
||||
{
|
||||
return sharpeRatio switch
|
||||
{
|
||||
@@ -183,7 +238,7 @@ public class BacktestScorer
|
||||
};
|
||||
}
|
||||
|
||||
private static double CalculateDrawdownUsdScore(decimal maxDrawdown, decimal initialBalance)
|
||||
private static double CalculateDrawdownUsdScore(decimal maxDrawdown, decimal initialBalance, BacktestScoringResult result)
|
||||
{
|
||||
if (initialBalance <= 0) return 0;
|
||||
|
||||
@@ -195,7 +250,7 @@ public class BacktestScorer
|
||||
};
|
||||
}
|
||||
|
||||
private static double CalculateWinRateScore(double winRate, int tradeCount)
|
||||
private static double CalculateWinRateScore(double winRate, int tradeCount, BacktestScoringResult result)
|
||||
{
|
||||
// Base win rate score
|
||||
var baseScore = winRate * 100;
|
||||
@@ -212,7 +267,7 @@ public class BacktestScorer
|
||||
return baseScore * significanceFactor;
|
||||
}
|
||||
|
||||
private static double CalculateHodlComparisonScore(double strategyGrowth, double hodlGrowth)
|
||||
private static double CalculateHodlComparisonScore(double strategyGrowth, double hodlGrowth, BacktestScoringResult result)
|
||||
{
|
||||
var difference = strategyGrowth - hodlGrowth;
|
||||
|
||||
@@ -228,7 +283,7 @@ public class BacktestScorer
|
||||
};
|
||||
}
|
||||
|
||||
private static double CalculateTradeCountScore(int tradeCount)
|
||||
private static double CalculateTradeCountScore(int tradeCount, BacktestScoringResult result)
|
||||
{
|
||||
return tradeCount switch
|
||||
{
|
||||
@@ -239,7 +294,7 @@ public class BacktestScorer
|
||||
};
|
||||
}
|
||||
|
||||
private static double CalculateRecoveryScore(TimeSpan recoveryTime, Timeframe timeframe)
|
||||
private static double CalculateRecoveryScore(TimeSpan recoveryTime, Timeframe timeframe, BacktestScoringResult result)
|
||||
{
|
||||
var days = recoveryTime.TotalDays;
|
||||
|
||||
@@ -260,7 +315,7 @@ public class BacktestScorer
|
||||
return 100 - (days / maxRecoveryDays * 100);
|
||||
}
|
||||
|
||||
private static double CalculateTestDurationScore(DateTime startDate, DateTime endDate, Timeframe timeframe)
|
||||
private static double CalculateTestDurationScore(DateTime startDate, DateTime endDate, Timeframe timeframe, BacktestScoringResult result)
|
||||
{
|
||||
var durationDays = (endDate - startDate).TotalDays;
|
||||
|
||||
@@ -283,7 +338,7 @@ public class BacktestScorer
|
||||
return 100;
|
||||
}
|
||||
|
||||
private static double CalculateFeesImpactScore(decimal feesPaid, decimal initialBalance, decimal totalPnL)
|
||||
private static double CalculateFeesImpactScore(decimal feesPaid, decimal initialBalance, decimal totalPnL, BacktestScoringResult result)
|
||||
{
|
||||
if (initialBalance <= 0) return 0;
|
||||
|
||||
@@ -307,7 +362,7 @@ public class BacktestScorer
|
||||
return feeEfficiency;
|
||||
}
|
||||
|
||||
private static double CalculateRiskAdjustedReturnScore(double totalPnL, decimal maxDrawdown, decimal initialBalance)
|
||||
private static double CalculateRiskAdjustedReturnScore(double totalPnL, decimal maxDrawdown, decimal initialBalance, BacktestScoringResult result)
|
||||
{
|
||||
if (initialBalance <= 0 || maxDrawdown <= 0) return 0;
|
||||
|
||||
|
||||
@@ -157,7 +157,8 @@ public class BacktestRepository : IBacktestRepository
|
||||
MaxDrawdown = b.Statistics?.MaxDrawdown,
|
||||
Fees = b.Fees,
|
||||
SharpeRatio = b.Statistics?.SharpeRatio != null ? (double)b.Statistics.SharpeRatio : null,
|
||||
Score = b.Score
|
||||
Score = b.Score,
|
||||
ScoreMessage = b.ScoreMessage ?? string.Empty
|
||||
});
|
||||
|
||||
return (mappedBacktests, (int)totalCount);
|
||||
@@ -284,7 +285,8 @@ public class BacktestRepository : IBacktestRepository
|
||||
MaxDrawdown = b.Statistics?.MaxDrawdown,
|
||||
Fees = b.Fees,
|
||||
SharpeRatio = b.Statistics?.SharpeRatio != null ? (double)b.Statistics.SharpeRatio : null,
|
||||
Score = b.Score
|
||||
Score = b.Score,
|
||||
ScoreMessage = b.ScoreMessage ?? string.Empty
|
||||
});
|
||||
|
||||
return (mappedBacktests, (int)totalCount);
|
||||
|
||||
@@ -29,6 +29,7 @@ namespace Managing.Infrastructure.Databases.MongoDb.Collections
|
||||
[BsonRepresentation(BsonType.Decimal128)]
|
||||
public decimal Fees { get; set; }
|
||||
public double Score { get; set; }
|
||||
public string ScoreMessage { get; set; } = string.Empty;
|
||||
public string Identifier { get; set; }
|
||||
public string RequestId { get; set; }
|
||||
public string? Metadata { get; set; }
|
||||
|
||||
@@ -150,6 +150,7 @@ public static class MongoMappers
|
||||
StartDate = b.StartDate,
|
||||
EndDate = b.EndDate,
|
||||
Score = b.Score,
|
||||
ScoreMessage = b.ScoreMessage ?? string.Empty,
|
||||
RequestId = b.RequestId,
|
||||
Metadata = string.IsNullOrEmpty(b.Metadata) ? null : JsonSerializer.Deserialize<object>(b.Metadata)
|
||||
};
|
||||
@@ -179,6 +180,7 @@ public static class MongoMappers
|
||||
StartDate = result.StartDate,
|
||||
EndDate = result.EndDate,
|
||||
Score = result.Score,
|
||||
ScoreMessage = result.ScoreMessage ?? string.Empty,
|
||||
RequestId = result.RequestId,
|
||||
Metadata = result.Metadata == null ? null : JsonSerializer.Serialize(result.Metadata)
|
||||
};
|
||||
|
||||
Reference in New Issue
Block a user