feat(analyzer): dynamic settings, IFinlyticLogger, live log streaming, and EF migration
This commit is contained in:
@@ -3,15 +3,16 @@ using System.Collections.Generic;
|
||||
using System.IO;
|
||||
using System.Linq;
|
||||
using System.Text.Json;
|
||||
using FinlyticAnalyzer.Util;
|
||||
using FinlyticCore.Models.Analyzer;
|
||||
using FinlyticCore.Models.Trades;
|
||||
using Microsoft.Extensions.Logging;
|
||||
using FinlyticCore.Services;
|
||||
|
||||
namespace FinlyticAnalyzer.Services;
|
||||
|
||||
public class WinRateCalculator : IWinRateCalculator
|
||||
{
|
||||
private readonly ILogger<WinRateCalculator> _logger;
|
||||
private readonly IFinlyticLogger<WinRateCalculator> _finlyticLogger;
|
||||
private readonly string _feedbackDir;
|
||||
|
||||
private readonly object _cacheLock = new();
|
||||
@@ -19,9 +20,9 @@ public class WinRateCalculator : IWinRateCalculator
|
||||
private DateTime _lastCacheTime = DateTime.MinValue;
|
||||
private static readonly TimeSpan CacheTtl = TimeSpan.FromMinutes(3);
|
||||
|
||||
public WinRateCalculator(ILogger<WinRateCalculator> logger)
|
||||
public WinRateCalculator(IFinlyticLogger<WinRateCalculator> finlyticLogger)
|
||||
{
|
||||
_logger = logger;
|
||||
_finlyticLogger = finlyticLogger;
|
||||
_feedbackDir = Path.Combine(AppDomain.CurrentDomain.BaseDirectory, "data", "feedback");
|
||||
if (!Directory.Exists(_feedbackDir))
|
||||
{
|
||||
@@ -31,7 +32,6 @@ public class WinRateCalculator : IWinRateCalculator
|
||||
|
||||
/// <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)
|
||||
{
|
||||
@@ -53,27 +53,23 @@ public class WinRateCalculator : IWinRateCalculator
|
||||
{
|
||||
try
|
||||
{
|
||||
// 1. N8n AI Confidence Score (Weight: 40%)
|
||||
double n8nComponent = 62.0;
|
||||
if (n8nEvalScore.HasValue && n8nEvalScore.Value > 0)
|
||||
{
|
||||
n8nComponent = n8nEvalScore.Value <= 1.0 ? n8nEvalScore.Value * 100.0 : n8nEvalScore.Value;
|
||||
}
|
||||
|
||||
// 2. Technical Score (Weight: 30%)
|
||||
double taComponent = 60.0;
|
||||
if (technicalScore.HasValue && technicalScore.Value > 0)
|
||||
{
|
||||
taComponent = technicalScore.Value <= 1.0 ? technicalScore.Value * 100.0 : technicalScore.Value;
|
||||
}
|
||||
|
||||
// 3. Sentiment Score (Weight: 15%)
|
||||
double sentComponent = 58.0;
|
||||
if (sentimentScore.HasValue)
|
||||
{
|
||||
if (sentimentScore.Value >= -1.0 && sentimentScore.Value <= 1.0)
|
||||
{
|
||||
// Map sentiment from -1.0..+1.0 into 35.0..85.0
|
||||
sentComponent = 50.0 + (sentimentScore.Value * 25.0);
|
||||
}
|
||||
else
|
||||
@@ -82,29 +78,25 @@ public class WinRateCalculator : IWinRateCalculator
|
||||
}
|
||||
}
|
||||
|
||||
// 4. Fundamental Score (Weight: 15%)
|
||||
double fundComponent = 60.0;
|
||||
if (fundamentalScore.HasValue && fundamentalScore.Value > 0)
|
||||
{
|
||||
fundComponent = fundamentalScore.Value <= 1.0 ? fundamentalScore.Value * 100.0 : fundamentalScore.Value;
|
||||
}
|
||||
|
||||
// Multi-factor weighted composite
|
||||
double composite = (n8nComponent * 0.40) + (taComponent * 0.30) + (sentComponent * 0.15) + (fundComponent * 0.15);
|
||||
|
||||
// 5. Market Regime & Volatility Adjustment
|
||||
double vixAdjustment = regime switch
|
||||
{
|
||||
VixMarketRegime.LowVol => +4.0, // Calm trending market
|
||||
VixMarketRegime.Normal => +1.5, // Normal conditions
|
||||
VixMarketRegime.HighVol => -3.5, // Increased whipsaws
|
||||
VixMarketRegime.Panic => -8.0, // High panic / uncertainty
|
||||
VixMarketRegime.LowVol => +4.0,
|
||||
VixMarketRegime.Normal => +1.5,
|
||||
VixMarketRegime.HighVol => -3.5,
|
||||
VixMarketRegime.Panic => -8.0,
|
||||
_ => 0.0
|
||||
};
|
||||
|
||||
composite += vixAdjustment;
|
||||
|
||||
// 6. Historical track record calibration (if available in feedback records)
|
||||
var records = GetCachedOrLoadRecords();
|
||||
if (records.Count > 0)
|
||||
{
|
||||
@@ -120,17 +112,16 @@ public class WinRateCalculator : IWinRateCalculator
|
||||
}
|
||||
}
|
||||
|
||||
// Clamp between realistic financial statistical bounds (45.0% to 92.0%)
|
||||
double finalWinRate = Math.Clamp(Math.Round(composite, 1), 45.0, 92.0);
|
||||
|
||||
_logger.LogInformation("[{Channel}] Dynamic Win-Rate for {Symbol} ({Sector}): {WinRate:F1}% [AI: {N8n:F1}%, TA: {TA:F1}%, Sent: {Sent:F1}%, Regime: {Regime}]",
|
||||
"AnalyzerChannel", symbol, sector, finalWinRate, n8nComponent, taComponent, sentComponent, regime);
|
||||
_ = _finlyticLogger.LogInfoAsync(SettingKeys.AnalyzerChannel, "[WinRateCalculator] Dynamic Win-Rate for {Symbol} ({Sector}): {WinRate:F1}% [AI: {N8n:F1}%, TA: {TA:F1}%, Sent: {Sent:F1}%, Regime: {Regime}]",
|
||||
symbol, sector, finalWinRate, n8nComponent, taComponent, sentComponent, regime);
|
||||
|
||||
return finalWinRate;
|
||||
}
|
||||
catch (Exception ex)
|
||||
{
|
||||
_logger.LogWarning(ex, "[{Channel}] Error calculating dynamic win-rate for {Symbol}. Fallback applied.", "AnalyzerChannel", symbol);
|
||||
_ = _finlyticLogger.LogWarningAsync(SettingKeys.AnalyzerChannel, ex, "[WinRateCalculator] Error calculating dynamic win-rate for {Symbol}. Fallback applied.", symbol);
|
||||
return 65.0;
|
||||
}
|
||||
}
|
||||
@@ -162,7 +153,7 @@ public class WinRateCalculator : IWinRateCalculator
|
||||
}
|
||||
catch (Exception ex)
|
||||
{
|
||||
_logger.LogWarning(ex, "[{Channel}] Failed to read or parse feedback file '{File}'", "AnalyzerChannel", file);
|
||||
_ = _finlyticLogger.LogWarningAsync(SettingKeys.AnalyzerChannel, ex, "[WinRateCalculator] Failed to read or parse feedback file '{File}'", file);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user