using System; using Microsoft.VisualStudio.TestTools.UnitTesting; using NT8.Core.Execution; using NT8.Core.Tests.Mocks; namespace NT8.Core.Tests.Execution { [TestClass] public class ExecutionQualityTrackerTests { private ExecutionQualityTracker _tracker; [TestInitialize] public void Setup() { _tracker = new ExecutionQualityTracker(new MockLogger()); } [TestMethod] public void Constructor_NullLogger_ThrowsArgumentNullException() { Assert.ThrowsException(delegate { new ExecutionQualityTracker(null); }); } [TestMethod] public void RecordExecution_ValidInput_StoresMetrics() { var now = DateTime.UtcNow; _tracker.RecordExecution("ES-1", 5000m, 5000.25m, now.AddMilliseconds(20), now.AddMilliseconds(5), now); var metrics = _tracker.GetExecutionMetrics("ES-1"); Assert.IsNotNull(metrics); Assert.AreEqual("ES-1", metrics.OrderId); } [TestMethod] public void RecordExecution_NullOrderId_ThrowsArgumentNullException() { Assert.ThrowsException(delegate { _tracker.RecordExecution(null, 1m, 1m, DateTime.UtcNow, DateTime.UtcNow, DateTime.UtcNow); }); } [TestMethod] public void GetExecutionMetrics_UnknownOrder_ReturnsNull() { var metrics = _tracker.GetExecutionMetrics("UNKNOWN-1"); Assert.IsNull(metrics); } [TestMethod] public void GetExecutionMetrics_NullOrderId_ThrowsArgumentNullException() { Assert.ThrowsException(delegate { _tracker.GetExecutionMetrics(null); }); } [TestMethod] public void GetSymbolStatistics_NoHistory_ReturnsDefaults() { var stats = _tracker.GetSymbolStatistics("ES"); Assert.AreEqual("ES", stats.Symbol); Assert.AreEqual(0.0, stats.AverageSlippage, 0.000001); Assert.AreEqual(TimeSpan.Zero, stats.AverageFillLatency); Assert.AreEqual(ExecutionQuality.Poor, stats.AverageQuality); } [TestMethod] public void GetSymbolStatistics_WithHistory_ComputesAverages() { var t0 = DateTime.UtcNow; _tracker.RecordExecution("ES-1", 5000m, 5000.25m, t0.AddMilliseconds(30), t0.AddMilliseconds(5), t0); _tracker.RecordExecution("ES-2", 5000m, 4999.75m, t0.AddMilliseconds(40), t0.AddMilliseconds(10), t0); var stats = _tracker.GetSymbolStatistics("ES"); Assert.AreEqual("ES", stats.Symbol); Assert.AreEqual(2, stats.PositiveSlippageCount + stats.NegativeSlippageCount); Assert.IsTrue(stats.AverageFillLatency.TotalMilliseconds > 0.0); } [TestMethod] public void GetSymbolStatistics_NullSymbol_ThrowsArgumentNullException() { Assert.ThrowsException(delegate { _tracker.GetSymbolStatistics(null); }); } [TestMethod] public void GetAverageSlippage_WithHistory_ReturnsAverage() { var t0 = DateTime.UtcNow; _tracker.RecordExecution("NQ-1", 100m, 101m, t0.AddMilliseconds(5), t0.AddMilliseconds(2), t0); _tracker.RecordExecution("NQ-2", 100m, 99m, t0.AddMilliseconds(6), t0.AddMilliseconds(2), t0); var avg = _tracker.GetAverageSlippage("NQ"); Assert.AreEqual(0.0, avg, 0.000001); } [TestMethod] public void GetAverageSlippage_NullSymbol_ThrowsArgumentNullException() { Assert.ThrowsException(delegate { _tracker.GetAverageSlippage(null); }); } [TestMethod] public void IsExecutionQualityAcceptable_WhenNoDataAndThresholdFair_ReturnsTrue_ByCurrentEnumOrdering() { var ok = _tracker.IsExecutionQualityAcceptable("GC", ExecutionQuality.Fair); Assert.IsTrue(ok); } [TestMethod] public void IsExecutionQualityAcceptable_WithLowThreshold_ReturnsTrue() { var ok = _tracker.IsExecutionQualityAcceptable("GC", ExecutionQuality.Poor); Assert.IsTrue(ok); } [TestMethod] public void IsExecutionQualityAcceptable_NullSymbol_ThrowsArgumentNullException() { Assert.ThrowsException(delegate { _tracker.IsExecutionQualityAcceptable(null, ExecutionQuality.Poor); }); } [TestMethod] public void GetTotalExecutionCount_AfterRecords_ReturnsCount() { var t0 = DateTime.UtcNow; _tracker.RecordExecution("MES-1", 1m, 1m, t0.AddMilliseconds(2), t0.AddMilliseconds(1), t0); _tracker.RecordExecution("MES-2", 1m, 1m, t0.AddMilliseconds(2), t0.AddMilliseconds(1), t0); Assert.AreEqual(2, _tracker.GetTotalExecutionCount()); } [TestMethod] public void ClearSymbolHistory_RemovesHistoryForSymbol() { var t0 = DateTime.UtcNow; _tracker.RecordExecution("CL-1", 80m, 80.1m, t0.AddMilliseconds(2), t0.AddMilliseconds(1), t0); _tracker.ClearSymbolHistory("CL"); var stats = _tracker.GetSymbolStatistics("CL"); Assert.AreEqual(0.0, stats.AverageSlippage, 0.000001); Assert.AreEqual(ExecutionQuality.Poor, stats.AverageQuality); } [TestMethod] public void ClearSymbolHistory_NullSymbol_ThrowsArgumentNullException() { Assert.ThrowsException(delegate { _tracker.ClearSymbolHistory(null); }); } [TestMethod] public void RollingWindow_UsesLast100Executions() { var t0 = DateTime.UtcNow; for (var i = 0; i < 120; i++) { _tracker.RecordExecution("RTY-" + i, 2000m, 2000m + (i % 2 == 0 ? 0.25m : -0.25m), t0.AddMilliseconds(10), t0.AddMilliseconds(5), t0); } var stats = _tracker.GetSymbolStatistics("RTY"); Assert.IsTrue(stats.PositiveSlippageCount + stats.NegativeSlippageCount <= 100); } [TestMethod] public void RecordExecution_PositiveSlippage_TagsPositiveType() { var t0 = DateTime.UtcNow; _tracker.RecordExecution("ES-POS", 100m, 101m, t0.AddMilliseconds(10), t0.AddMilliseconds(5), t0); var metrics = _tracker.GetExecutionMetrics("ES-POS"); Assert.AreEqual(SlippageType.Positive, metrics.SlippageType); } [TestMethod] public void RecordExecution_NegativeSlippage_TagsNegativeType() { var t0 = DateTime.UtcNow; _tracker.RecordExecution("ES-NEG", 100m, 99m, t0.AddMilliseconds(10), t0.AddMilliseconds(5), t0); var metrics = _tracker.GetExecutionMetrics("ES-NEG"); Assert.AreEqual(SlippageType.Negative, metrics.SlippageType); } } }