using System; using System.Collections.Generic; using System.Linq; using NT8.Core.Intelligence; using NT8.Core.Logging; namespace NT8.Core.Analytics { /// /// Factor-level analysis report. /// public class FactorAnalysisReport { public FactorType Factor { get; set; } public double CorrelationToPnL { get; set; } public double Importance { get; set; } public Dictionary BucketWinRate { get; set; } public Dictionary BucketAvgPnL { get; set; } public FactorAnalysisReport() { BucketWinRate = new Dictionary(); BucketAvgPnL = new Dictionary(); } } /// /// Validates confluence score quality and recommends weight adjustments. /// public class ConfluenceValidator { private readonly ILogger _logger; public ConfluenceValidator(ILogger logger) { if (logger == null) throw new ArgumentNullException("logger"); _logger = logger; } /// /// Analyzes one factor against trade outcomes. /// public FactorAnalysisReport AnalyzeFactor(FactorType factor, List trades) { if (trades == null) throw new ArgumentNullException("trades"); try { var report = new FactorAnalysisReport(); report.Factor = factor; var values = ExtractFactorValues(factor, trades); report.CorrelationToPnL = Correlation(values, trades.Select(t => t.RealizedPnL).ToList()); report.Importance = Math.Abs(report.CorrelationToPnL); var low = new List(); var medium = new List(); var high = new List(); for (var i = 0; i < values.Count; i++) { var v = values[i]; if (v < 0.5) low.Add(i); else if (v < 0.8) medium.Add(i); else high.Add(i); } AddBucket(report, "Low", low, trades); AddBucket(report, "Medium", medium, trades); AddBucket(report, "High", high, trades); return report; } catch (Exception ex) { _logger.LogError("AnalyzeFactor failed: {0}", ex.Message); throw; } } /// /// Estimates factor importance values normalized to 1.0. /// public Dictionary CalculateFactorImportance(List trades) { if (trades == null) throw new ArgumentNullException("trades"); try { var result = new Dictionary(); var raw = new Dictionary(); var total = 0.0; var supported = new[] { FactorType.Setup, FactorType.Trend, FactorType.Volatility, FactorType.Timing, FactorType.ExecutionQuality }; foreach (var factor in supported) { var analysis = AnalyzeFactor(factor, trades); var score = Math.Max(0.0001, analysis.Importance); raw.Add(factor, score); total += score; } foreach (var kvp in raw) { result.Add(kvp.Key, kvp.Value / total); } return result; } catch (Exception ex) { _logger.LogError("CalculateFactorImportance failed: {0}", ex.Message); throw; } } /// /// Recommends confluence weights based on observed importance. /// public Dictionary RecommendWeights(List trades) { if (trades == null) throw new ArgumentNullException("trades"); try { var importance = CalculateFactorImportance(trades); return importance; } catch (Exception ex) { _logger.LogError("RecommendWeights failed: {0}", ex.Message); throw; } } /// /// Validates whether score implies expected outcome. /// public bool ValidateScore(ConfluenceScore score, TradeOutcome outcome) { if (score == null) throw new ArgumentNullException("score"); try { if (score.WeightedScore >= 0.7) return outcome == TradeOutcome.Win; if (score.WeightedScore <= 0.4) return outcome == TradeOutcome.Loss; return outcome != TradeOutcome.Breakeven; } catch (Exception ex) { _logger.LogError("ValidateScore failed: {0}", ex.Message); throw; } } private static void AddBucket(FactorAnalysisReport report, string bucket, List indices, List trades) { if (indices.Count == 0) { report.BucketWinRate[bucket] = 0.0; report.BucketAvgPnL[bucket] = 0.0; return; } var selected = indices.Select(i => trades[i]).ToList(); report.BucketWinRate[bucket] = (double)selected.Count(t => t.RealizedPnL > 0.0) / selected.Count; report.BucketAvgPnL[bucket] = selected.Average(t => t.RealizedPnL); } private static List ExtractFactorValues(FactorType factor, List trades) { var values = new List(); foreach (var trade in trades) { switch (factor) { case FactorType.Setup: values.Add(trade.ConfluenceScore); break; case FactorType.Trend: values.Add(TrendScore(trade.TrendRegime)); break; case FactorType.Volatility: values.Add(VolatilityScore(trade.VolatilityRegime)); break; case FactorType.Timing: values.Add(TimingScore(trade.EntryTime)); break; case FactorType.ExecutionQuality: values.Add(ExecutionQualityScore(trade)); break; default: values.Add(0.5); break; } } return values; } private static double TrendScore(TrendRegime trend) { switch (trend) { case TrendRegime.StrongUp: case TrendRegime.StrongDown: return 0.9; case TrendRegime.WeakUp: case TrendRegime.WeakDown: return 0.7; default: return 0.5; } } private static double VolatilityScore(VolatilityRegime volatility) { switch (volatility) { case VolatilityRegime.Low: case VolatilityRegime.BelowNormal: return 0.8; case VolatilityRegime.Normal: return 0.6; case VolatilityRegime.Elevated: return 0.4; default: return 0.2; } } private static double TimingScore(DateTime timestamp) { var t = timestamp.TimeOfDay; if (t < new TimeSpan(10, 30, 0)) return 0.8; if (t < new TimeSpan(14, 0, 0)) return 0.5; if (t < new TimeSpan(16, 0, 0)) return 0.7; return 0.3; } private static double ExecutionQualityScore(TradeRecord trade) { if (trade.StopTicks <= 0) return 0.5; var scaled = trade.RMultiple / 3.0; if (scaled < 0.0) scaled = 0.0; if (scaled > 1.0) scaled = 1.0; return scaled; } private static double Correlation(List xs, List ys) { if (xs.Count != ys.Count || xs.Count < 2) return 0.0; var xAvg = xs.Average(); var yAvg = ys.Average(); var sumXY = 0.0; var sumXX = 0.0; var sumYY = 0.0; for (var i = 0; i < xs.Count; i++) { var dx = xs[i] - xAvg; var dy = ys[i] - yAvg; sumXY += dx * dy; sumXX += dx * dx; sumYY += dy * dy; } if (sumXX <= 0.0 || sumYY <= 0.0) return 0.0; return sumXY / Math.Sqrt(sumXX * sumYY); } } }