using System; using System.Collections.Generic; using System.IO; using System.Linq; using System.Text.Json; using FinlyticCore.Models.Analyzer; using FinlyticCore.Models.Trades; using Microsoft.Extensions.Logging; namespace FinlyticAnalyzer.Services; public class WinRateCalculator : IWinRateCalculator { private readonly ILogger _logger; private readonly string _feedbackDir; private readonly object _cacheLock = new(); private List? _cachedRecords; private DateTime _lastCacheTime = DateTime.MinValue; private static readonly TimeSpan CacheTtl = TimeSpan.FromMinutes(3); public WinRateCalculator(ILogger logger) { _logger = logger; _feedbackDir = Path.Combine(AppDomain.CurrentDomain.BaseDirectory, "data", "feedback"); if (!Directory.Exists(_feedbackDir)) { Directory.CreateDirectory(_feedbackDir); } } /// /// Calculates the win rate for a given sector and symbol under the specified market regime. /// Uses cached feedback records (3-minute TTL) to prevent disk I/O bottlenecks. /// public double CalculateWinRate(string sector, string symbol, VixMarketRegime regime) { try { var records = GetCachedOrLoadRecords(); if (records.Count == 0) return 65.0; var matching = records.Where(r => string.Equals(r.Sector, sector, StringComparison.OrdinalIgnoreCase) && r.VixRegime == regime).ToList(); if (matching.Count > 0) { int winningTrades = matching.Count(r => r.IsWin); double calculatedWinRate = (double)winningTrades / matching.Count * 100.0; _logger.LogInformation("[{Channel}] Calculated win-rate for Sector '{Sector}' in Regime '{Regime}': {WinRate:F1}% ({Wins}/{Total})", "AnalyzerChannel", sector, regime, calculatedWinRate, winningTrades, matching.Count); return Math.Round(calculatedWinRate, 1); } } catch (Exception ex) { _logger.LogWarning(ex, "[{Channel}] Error reading feedback files for win-rate calculation. Falling back to default.", "AnalyzerChannel"); } return 65.0; // Default baseline win-rate } private List GetCachedOrLoadRecords() { lock (_cacheLock) { if (_cachedRecords != null && (DateTime.UtcNow - _lastCacheTime) < CacheTtl) { return _cachedRecords; } var loadedList = new List(); if (Directory.Exists(_feedbackDir)) { var jsonFiles = Directory.GetFiles(_feedbackDir, "*.json", SearchOption.AllDirectories); foreach (var file in jsonFiles) { try { var content = File.ReadAllText(file); var records = JsonSerializer.Deserialize(content); if (records != null && records.Length > 0) { loadedList.AddRange(records); } } catch (Exception ex) { _logger.LogWarning(ex, "[{Channel}] Failed to read or parse feedback file '{File}'", "AnalyzerChannel", file); } } } _cachedRecords = loadedList; _lastCacheTime = DateTime.UtcNow; return _cachedRecords; } } }