namespace KArtSell.Modules.ModelOperations.ShadowRun; /// /// Calculates metrics for a single market phase. /// Deterministic, stateless, PIT-safe (uses only provided returns). /// public sealed class PhaseMetricsCalculator { private const decimal AnnualizationFactor = 252m; // Trading days per year /// /// Calculate Sharpe, Calmar, Max DD, Win Rate for a phase's daily returns. /// public static PhaseMetricsDto Calculate(List dailyReturns) { if (dailyReturns.Count == 0) return new PhaseMetricsDto( TradingDays: 0, Return: 0m, Sharpe: 0m, WinRate: 0m, MaxDrawdown: 0m); var totalReturn = CalculateTotalReturn(dailyReturns); var (sharpe, _) = CalculateSharpeAndStdDev(dailyReturns); var winRate = CalculateWinRate(dailyReturns); var maxDD = CalculateMaxDrawdown(dailyReturns); return new PhaseMetricsDto( TradingDays: dailyReturns.Count, Return: totalReturn, Sharpe: sharpe, WinRate: winRate, MaxDrawdown: maxDD); } private static decimal CalculateTotalReturn(List returns) { return (decimal)(returns.Aggregate(1.0, (acc, r) => acc * (double)(1 + r)) - 1); } private static (decimal Sharpe, decimal StdDev) CalculateSharpeAndStdDev(List returns) { var mean = returns.Average(); var variance = returns.Average(r => (r - mean) * (r - mean)); var stdDev = (decimal)Math.Sqrt((double)variance); if (stdDev == 0m) return (0m, 0m); var sharpe = (mean / stdDev) * (decimal)Math.Sqrt((double)AnnualizationFactor); return (sharpe, stdDev); } private static decimal CalculateWinRate(List returns) { if (returns.Count == 0) return 0m; var winDays = returns.Count(r => r > 0); return (decimal)winDays / returns.Count; } private static decimal CalculateMaxDrawdown(List returns) { if (returns.Count == 0) return 0m; var cumulative = 1m; var peak = 1m; var maxDD = 0m; foreach (var r in returns) { cumulative *= (1 + r); if (cumulative > peak) peak = cumulative; var drawdown = (cumulative - peak) / peak; if (drawdown < maxDD) maxDD = drawdown; } return Math.Abs(maxDD); } } /// /// Metrics for a single market phase. /// public record PhaseMetricsDto( int TradingDays, decimal Return, decimal Sharpe, decimal WinRate, decimal MaxDrawdown);