102 lines
3.7 KiB
C#
102 lines
3.7 KiB
C#
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<WinRateCalculator> _logger;
|
|
private readonly string _feedbackDir;
|
|
|
|
private readonly object _cacheLock = new();
|
|
private List<TradeFeedbackRecord>? _cachedRecords;
|
|
private DateTime _lastCacheTime = DateTime.MinValue;
|
|
private static readonly TimeSpan CacheTtl = TimeSpan.FromMinutes(3);
|
|
|
|
public WinRateCalculator(ILogger<WinRateCalculator> logger)
|
|
{
|
|
_logger = logger;
|
|
_feedbackDir = Path.Combine(AppDomain.CurrentDomain.BaseDirectory, "data", "feedback");
|
|
if (!Directory.Exists(_feedbackDir))
|
|
{
|
|
Directory.CreateDirectory(_feedbackDir);
|
|
}
|
|
}
|
|
|
|
/// <summary>
|
|
/// 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.
|
|
/// </summary>
|
|
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<TradeFeedbackRecord> GetCachedOrLoadRecords()
|
|
{
|
|
lock (_cacheLock)
|
|
{
|
|
if (_cachedRecords != null && (DateTime.UtcNow - _lastCacheTime) < CacheTtl)
|
|
{
|
|
return _cachedRecords;
|
|
}
|
|
|
|
var loadedList = new List<TradeFeedbackRecord>();
|
|
|
|
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<TradeFeedbackRecord[]>(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;
|
|
}
|
|
}
|
|
}
|