using System; using FinlyticCore.Dtos.Simulation; namespace FinlyticSimulation.Services; /// /// Pure scoring function for FinlyticSimulation's backtest-reliability matrix, extracted out of /// QuantSimulationEngine.RunBacktestAsync (which previously mixed candle-fetch-with-fallback, replay /// orchestration, DB persistence, AND this scoring math into one large method with inline magic numbers). No /// I/O, no DB access - just + threshold settings in, a verdict out, so this is /// independently unit-testable without spinning up a DbContext or a real backtest. /// public static class ReliabilityMatrixCalculator { /// 0-100, a blend of profit factor (max 1.5x weight, capped) and win rate. /// /// Whether -style consumers should trust this /// strategy/asset combination. Defaults to approved when there isn't yet enough sample data to judge it /// (Rules.md ยง4: "not enough data" must never read the same as "actively vetoed"). /// /// "BOOST_SCORE" / "NEUTRAL" / "VETO_DISABLE" - see . public record Result(decimal ReliabilityScore, bool IsApproved, string RecommendedAction); /// /// Scores a single completed backtest report against the given approval thresholds /// (SimulationSettingKeys.MinSampleTradesForApproval/HighProfitFactorThreshold/LowProfitFactorThreshold). /// public static Result Calculate( BacktestReportDto report, decimal minSampleTrades, decimal highProfitFactorThreshold, decimal lowProfitFactorThreshold) { // 0..100 blend: profit factor contributes up to 75 points (capped at PF=3.0 -> 1.5 * 50), win rate // contributes up to 50 points (100% WR * 0.5) - deliberately not a simple average, since a high win // rate with a poor profit factor (many tiny wins, rare huge losses) should not score as "reliable". decimal rawScore = (Math.Clamp(report.ProfitFactor / 2.0m, 0m, 1.5m) * 50m) + (report.WinRatePercent * 0.5m); decimal reliabilityScore = Math.Clamp(Math.Round(rawScore, 2), 0m, 100m); bool isApproved = report.ProfitFactor >= lowProfitFactorThreshold || report.TotalTrades < minSampleTrades; string recommendedAction = "NEUTRAL"; if (report.TotalTrades >= minSampleTrades) { if (report.ProfitFactor >= highProfitFactorThreshold) recommendedAction = "BOOST_SCORE"; else if (report.ProfitFactor < lowProfitFactorThreshold) recommendedAction = "VETO_DISABLE"; } return new Result(reliabilityScore, isApproved, recommendedAction); } }