using System; using System.Collections.Generic; using System.Linq; using Microsoft.Extensions.Logging; namespace NT8.Core.Execution { /// /// Tracks execution quality for orders and maintains statistics /// public class ExecutionQualityTracker { private readonly ILogger _logger; private readonly object _lock = new object(); // Store execution metrics for each order private readonly Dictionary _executionMetrics; // Store execution history by symbol private readonly Dictionary> _symbolExecutionHistory; // Rolling window size for statistics private const int ROLLING_WINDOW_SIZE = 100; /// /// Constructor for ExecutionQualityTracker /// /// Logger instance public ExecutionQualityTracker(ILogger logger) { if (logger == null) throw new ArgumentNullException("logger"); _logger = logger; _executionMetrics = new Dictionary(); _symbolExecutionHistory = new Dictionary>(); } /// /// Records an execution for tracking /// /// Order ID /// Intended price when order was placed /// Actual fill price /// Time of fill /// Time of submission /// Time of intent formation public void RecordExecution( string orderId, decimal intendedPrice, decimal fillPrice, DateTime fillTime, DateTime submitTime, DateTime intentTime) { if (string.IsNullOrEmpty(orderId)) throw new ArgumentNullException("orderId"); try { var slippage = fillPrice - intendedPrice; var slippageType = slippage > 0 ? SlippageType.Positive : slippage < 0 ? SlippageType.Negative : SlippageType.Zero; var submitLatency = submitTime - intentTime; var fillLatency = fillTime - intentTime; var quality = CalculateExecutionQuality(slippage, submitLatency, fillLatency); var metrics = new ExecutionMetrics( orderId, intentTime, submitTime, fillTime, intendedPrice, fillPrice, slippage, slippageType, submitLatency, fillLatency, quality); lock (_lock) { _executionMetrics[orderId] = metrics; // Add to symbol history var symbol = ExtractSymbolFromOrderId(orderId); if (!_symbolExecutionHistory.ContainsKey(symbol)) { _symbolExecutionHistory[symbol] = new Queue(); } var symbolHistory = _symbolExecutionHistory[symbol]; symbolHistory.Enqueue(metrics); // Keep only the last N executions while (symbolHistory.Count > ROLLING_WINDOW_SIZE) { symbolHistory.Dequeue(); } } _logger.LogDebug("Recorded execution for {OrderId}: Slippage={Slippage:F4}, Quality={Quality}", orderId, slippage, quality); } catch (Exception ex) { _logger.LogError("Failed to record execution for {OrderId}: {Message}", orderId, ex.Message); throw; } } /// /// Gets execution metrics for a specific order /// /// Order ID to get metrics for /// Execution metrics for the order public ExecutionMetrics GetExecutionMetrics(string orderId) { if (string.IsNullOrEmpty(orderId)) throw new ArgumentNullException("orderId"); try { lock (_lock) { ExecutionMetrics metrics; _executionMetrics.TryGetValue(orderId, out metrics); return metrics; } } catch (Exception ex) { _logger.LogError("Failed to get execution metrics for {OrderId}: {Message}", orderId, ex.Message); throw; } } /// /// Gets execution statistics for a symbol /// /// Symbol to get statistics for /// Execution statistics for the symbol public ExecutionStatistics GetSymbolStatistics(string symbol) { if (string.IsNullOrEmpty(symbol)) throw new ArgumentNullException("symbol"); try { lock (_lock) { if (_symbolExecutionHistory.ContainsKey(symbol)) { var history = _symbolExecutionHistory[symbol].ToList(); if (history.Count == 0) { return new ExecutionStatistics( symbol, 0, TimeSpan.Zero, TimeSpan.Zero, 0, 0, ExecutionQuality.Poor ); } var avgSlippage = history.Average(x => (double)x.Slippage); var avgSubmitLatency = TimeSpan.FromMilliseconds(history.Average(x => x.SubmitLatency.TotalMilliseconds)); var avgFillLatency = TimeSpan.FromMilliseconds(history.Average(x => x.FillLatency.TotalMilliseconds)); var avgQuality = history.GroupBy(x => x.Quality) .OrderByDescending(g => g.Count()) .First().Key; var positiveSlippageCount = history.Count(x => x.Slippage > 0); var negativeSlippageCount = history.Count(x => x.Slippage < 0); var zeroSlippageCount = history.Count(x => x.Slippage == 0); return new ExecutionStatistics( symbol, avgSlippage, avgSubmitLatency, avgFillLatency, positiveSlippageCount, negativeSlippageCount, avgQuality ); } return new ExecutionStatistics( symbol, 0, TimeSpan.Zero, TimeSpan.Zero, 0, 0, ExecutionQuality.Poor ); } } catch (Exception ex) { _logger.LogError("Failed to get execution statistics for {Symbol}: {Message}", symbol, ex.Message); throw; } } /// /// Gets the average slippage for a symbol /// /// Symbol to get average slippage for /// Average slippage for the symbol public double GetAverageSlippage(string symbol) { if (string.IsNullOrEmpty(symbol)) throw new ArgumentNullException("symbol"); try { var stats = GetSymbolStatistics(symbol); return stats.AverageSlippage; } catch (Exception ex) { _logger.LogError("Failed to get average slippage for {Symbol}: {Message}", symbol, ex.Message); throw; } } /// /// Checks if execution quality for a symbol is acceptable /// /// Symbol to check /// Minimum acceptable quality /// True if execution quality is acceptable, false otherwise public bool IsExecutionQualityAcceptable(string symbol, ExecutionQuality threshold = ExecutionQuality.Fair) { if (string.IsNullOrEmpty(symbol)) throw new ArgumentNullException("symbol"); try { var stats = GetSymbolStatistics(symbol); return stats.AverageQuality >= threshold; } catch (Exception ex) { _logger.LogError("Failed to check execution quality for {Symbol}: {Message}", symbol, ex.Message); throw; } } /// /// Calculates execution quality based on slippage and latencies /// /// Price slippage /// Submission latency /// Fill latency /// Calculated execution quality private ExecutionQuality CalculateExecutionQuality(decimal slippage, TimeSpan submitLatency, TimeSpan fillLatency) { // Determine quality based on slippage and latencies // Positive slippage is good, negative is bad // Lower latencies are better // If we have positive slippage (better than expected), quality is higher if (slippage > 0) { // Low latency is excellent, high latency is good if (fillLatency.TotalMilliseconds < 100) // Less than 100ms return ExecutionQuality.Excellent; else return ExecutionQuality.Good; } else if (slippage == 0) { // No slippage, check latencies if (fillLatency.TotalMilliseconds < 100) return ExecutionQuality.Good; else return ExecutionQuality.Fair; } else // slippage < 0 { // Negative slippage, check severity if (Math.Abs((double)slippage) < 0.01) // Small negative slippage { if (fillLatency.TotalMilliseconds < 100) return ExecutionQuality.Fair; else return ExecutionQuality.Poor; } else // Significant negative slippage { return ExecutionQuality.Poor; } } } /// /// Extracts symbol from order ID (assumes format SYMBOL-XXXX) /// /// Order ID to extract symbol from /// Extracted symbol private string ExtractSymbolFromOrderId(string orderId) { if (string.IsNullOrEmpty(orderId)) return "UNKNOWN"; // Split by hyphen and take first part as symbol var parts = orderId.Split('-'); return parts.Length > 0 ? parts[0] : "UNKNOWN"; } /// /// Gets total number of executions tracked /// /// Total execution count public int GetTotalExecutionCount() { try { lock (_lock) { return _executionMetrics.Count; } } catch (Exception ex) { _logger.LogError("Failed to get total execution count: {Message}", ex.Message); throw; } } /// /// Clears execution history for a symbol /// /// Symbol to clear history for public void ClearSymbolHistory(string symbol) { if (string.IsNullOrEmpty(symbol)) throw new ArgumentNullException("symbol"); try { lock (_lock) { if (_symbolExecutionHistory.ContainsKey(symbol)) { _symbolExecutionHistory[symbol].Clear(); _logger.LogDebug("Cleared execution history for {Symbol}", symbol); } } } catch (Exception ex) { _logger.LogError("Failed to clear execution history for {Symbol}: {Message}", symbol, ex.Message); throw; } } } /// /// Execution statistics for a symbol /// public class ExecutionStatistics { /// /// Symbol these statistics are for /// public string Symbol { get; set; } /// /// Average slippage /// public double AverageSlippage { get; set; } /// /// Average submission latency /// public TimeSpan AverageSubmitLatency { get; set; } /// /// Average fill latency /// public TimeSpan AverageFillLatency { get; set; } /// /// Count of executions with positive slippage /// public int PositiveSlippageCount { get; set; } /// /// Count of executions with negative slippage /// public int NegativeSlippageCount { get; set; } /// /// Average execution quality /// public ExecutionQuality AverageQuality { get; set; } /// /// Constructor for ExecutionStatistics /// /// Symbol for statistics /// Average slippage /// Average submission latency /// Average fill latency /// Positive slippage count /// Negative slippage count /// Average quality public ExecutionStatistics( string symbol, double avgSlippage, TimeSpan avgSubmitLatency, TimeSpan avgFillLatency, int posSlippageCount, int negSlippageCount, ExecutionQuality avgQuality) { if (string.IsNullOrEmpty(symbol)) throw new ArgumentNullException("symbol"); Symbol = symbol; AverageSlippage = avgSlippage; AverageSubmitLatency = avgSubmitLatency; AverageFillLatency = avgFillLatency; PositiveSlippageCount = posSlippageCount; NegativeSlippageCount = negSlippageCount; AverageQuality = avgQuality; } } }