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