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);