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);
}
}
}