using System; using System.Collections.Generic; using System.Linq; using NT8.Core.Intelligence; using NT8.Core.Logging; namespace NT8.Core.Analytics { /// /// Grade-level aggregate analysis report. /// public class GradePerformanceReport { /// /// Metrics by grade. /// public Dictionary MetricsByGrade { get; set; } /// /// Accuracy by grade. /// public Dictionary GradeAccuracy { get; set; } /// /// Suggested threshold. /// public TradeGrade SuggestedThreshold { get; set; } /// /// Creates a report instance. /// public GradePerformanceReport() { MetricsByGrade = new Dictionary(); GradeAccuracy = new Dictionary(); SuggestedThreshold = TradeGrade.F; } } /// /// Analyzes performance by confluence grade. /// public class GradePerformanceAnalyzer { private readonly ILogger _logger; private readonly PerformanceCalculator _calculator; /// /// Initializes analyzer. /// /// Logger dependency. public GradePerformanceAnalyzer(ILogger logger) { if (logger == null) throw new ArgumentNullException("logger"); _logger = logger; _calculator = new PerformanceCalculator(logger); } /// /// Produces grade-level performance report. /// /// Trade records. /// Performance report. public GradePerformanceReport AnalyzeByGrade(List trades) { if (trades == null) throw new ArgumentNullException("trades"); try { var report = new GradePerformanceReport(); foreach (TradeGrade grade in Enum.GetValues(typeof(TradeGrade))) { var subset = trades.Where(t => t.Grade == grade).ToList(); report.MetricsByGrade[grade] = _calculator.Calculate(subset); report.GradeAccuracy[grade] = CalculateGradeAccuracy(grade, trades); } report.SuggestedThreshold = FindOptimalThreshold(trades); return report; } catch (Exception ex) { _logger.LogError("AnalyzeByGrade failed: {0}", ex.Message); throw; } } /// /// Calculates percentage of profitable trades for a grade. /// /// Target grade. /// Trade records. /// Accuracy in range [0,1]. public double CalculateGradeAccuracy(TradeGrade grade, List trades) { if (trades == null) throw new ArgumentNullException("trades"); try { var subset = trades.Where(t => t.Grade == grade).ToList(); if (subset.Count == 0) return 0.0; var winners = subset.Count(t => t.RealizedPnL > 0.0); return (double)winners / subset.Count; } catch (Exception ex) { _logger.LogError("CalculateGradeAccuracy failed: {0}", ex.Message); throw; } } /// /// Finds threshold with best expectancy for accepted grades and above. /// /// Trade records. /// Suggested threshold grade. public TradeGrade FindOptimalThreshold(List trades) { if (trades == null) throw new ArgumentNullException("trades"); try { var ordered = new List { TradeGrade.APlus, TradeGrade.A, TradeGrade.B, TradeGrade.C, TradeGrade.D, TradeGrade.F }; var bestGrade = TradeGrade.F; var bestExpectancy = double.MinValue; foreach (var threshold in ordered) { var accepted = trades.Where(t => (int)t.Grade >= (int)threshold).ToList(); if (accepted.Count == 0) continue; var expectancy = _calculator.CalculateExpectancy(accepted); if (expectancy > bestExpectancy) { bestExpectancy = expectancy; bestGrade = threshold; } } return bestGrade; } catch (Exception ex) { _logger.LogError("FindOptimalThreshold failed: {0}", ex.Message); throw; } } /// /// Gets metrics grouped by grade. /// /// Trade records. /// Metrics by grade. public Dictionary GetMetricsByGrade(List trades) { if (trades == null) throw new ArgumentNullException("trades"); try { var result = new Dictionary(); foreach (TradeGrade grade in Enum.GetValues(typeof(TradeGrade))) { var subset = trades.Where(t => t.Grade == grade).ToList(); result.Add(grade, _calculator.Calculate(subset)); } return result; } catch (Exception ex) { _logger.LogError("GetMetricsByGrade failed: {0}", ex.Message); throw; } } } }