Files
nt8-sdk/src/NT8.Core/Analytics/ConfluenceValidator.cs
mo 0e36fe5d23
Some checks failed
Build and Test / build (push) Has been cancelled
feat: Complete Phase 5 Analytics & Reporting implementation
Analytics Layer (15 components):
- TradeRecorder: Full trade lifecycle tracking with partial fills
- PerformanceCalculator: Sharpe, Sortino, win rate, profit factor, expectancy
- PnLAttributor: Multi-dimensional attribution (grade/regime/time/strategy)
- DrawdownAnalyzer: Period detection and recovery metrics
- GradePerformanceAnalyzer: Grade-level edge analysis
- RegimePerformanceAnalyzer: Regime segmentation and transitions
- ConfluenceValidator: Factor validation and weighting optimization
- ReportGenerator: Daily/weekly/monthly reporting with export
- TradeBlotter: Real-time trade ledger with filtering
- ParameterOptimizer: Grid search and walk-forward scaffolding
- MonteCarloSimulator: Confidence intervals and risk-of-ruin
- PortfolioOptimizer: Multi-strategy allocation and portfolio metrics

Test Coverage (90 new tests):
- 240+ total tests, 100% pass rate
- >85% code coverage
- Zero new warnings

Project Status: Phase 5 complete (85% overall), ready for NT8 integration
2026-02-16 21:30:51 -05:00

304 lines
9.4 KiB
C#

using System;
using System.Collections.Generic;
using System.Linq;
using NT8.Core.Intelligence;
using NT8.Core.Logging;
namespace NT8.Core.Analytics
{
/// <summary>
/// Factor-level analysis report.
/// </summary>
public class FactorAnalysisReport
{
public FactorType Factor { get; set; }
public double CorrelationToPnL { get; set; }
public double Importance { get; set; }
public Dictionary<string, double> BucketWinRate { get; set; }
public Dictionary<string, double> BucketAvgPnL { get; set; }
public FactorAnalysisReport()
{
BucketWinRate = new Dictionary<string, double>();
BucketAvgPnL = new Dictionary<string, double>();
}
}
/// <summary>
/// Validates confluence score quality and recommends weight adjustments.
/// </summary>
public class ConfluenceValidator
{
private readonly ILogger _logger;
public ConfluenceValidator(ILogger logger)
{
if (logger == null)
throw new ArgumentNullException("logger");
_logger = logger;
}
/// <summary>
/// Analyzes one factor against trade outcomes.
/// </summary>
public FactorAnalysisReport AnalyzeFactor(FactorType factor, List<TradeRecord> 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<int>();
var medium = new List<int>();
var high = new List<int>();
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;
}
}
/// <summary>
/// Estimates factor importance values normalized to 1.0.
/// </summary>
public Dictionary<FactorType, double> CalculateFactorImportance(List<TradeRecord> trades)
{
if (trades == null)
throw new ArgumentNullException("trades");
try
{
var result = new Dictionary<FactorType, double>();
var raw = new Dictionary<FactorType, double>();
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;
}
}
/// <summary>
/// Recommends confluence weights based on observed importance.
/// </summary>
public Dictionary<FactorType, double> RecommendWeights(List<TradeRecord> 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;
}
}
/// <summary>
/// Validates whether score implies expected outcome.
/// </summary>
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<int> indices, List<TradeRecord> 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<double> ExtractFactorValues(FactorType factor, List<TradeRecord> trades)
{
var values = new List<double>();
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<double> xs, List<double> 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);
}
}
}