using System; using Microsoft.VisualStudio.TestTools.UnitTesting; using NT8.Core.Execution; using NT8.Core.OMS; using NT8.Core.Tests.Mocks; namespace NT8.Core.Tests.Execution { [TestClass] public class TrailingStopManagerTests { private TrailingStopManager _manager; [TestInitialize] public void Setup() { _manager = new TrailingStopManager(new MockLogger()); } [TestMethod] public void Constructor_NullLogger_ThrowsArgumentNullException() { Assert.ThrowsException(delegate { new TrailingStopManager(null); }); } [TestMethod] public void StartTrailing_ValidLongPosition_CreatesStop() { var position = CreatePosition(OrderSide.Buy, 5000m); var config = new NT8.Core.Execution.TrailingStopConfig(8); _manager.StartTrailing("ORD-1", position, config); var stop = _manager.GetCurrentStopPrice("ORD-1"); Assert.IsTrue(stop.HasValue); } [TestMethod] public void StartTrailing_ValidShortPosition_CreatesStop() { var position = CreatePosition(OrderSide.Sell, 5000m); var config = new NT8.Core.Execution.TrailingStopConfig(8); _manager.StartTrailing("ORD-2", position, config); var stop = _manager.GetCurrentStopPrice("ORD-2"); Assert.IsTrue(stop.HasValue); } [TestMethod] public void StartTrailing_NullOrderId_ThrowsArgumentNullException() { var position = CreatePosition(OrderSide.Buy, 5000m); var config = new NT8.Core.Execution.TrailingStopConfig(8); Assert.ThrowsException(delegate { _manager.StartTrailing(null, position, config); }); } [TestMethod] public void StartTrailing_NullPosition_ThrowsArgumentNullException() { var config = new NT8.Core.Execution.TrailingStopConfig(8); Assert.ThrowsException(delegate { _manager.StartTrailing("ORD-3", null, config); }); } [TestMethod] public void UpdateTrailingStop_UnknownOrder_ReturnsNull() { var result = _manager.UpdateTrailingStop("UNKNOWN", 5001m); Assert.IsFalse(result.HasValue); } [TestMethod] public void UpdateTrailingStop_LongImprovingPrice_CanReturnUpdatedStop() { var position = CreatePosition(OrderSide.Buy, 5000m); var config = new NT8.Core.Execution.TrailingStopConfig(8); _manager.StartTrailing("ORD-4", position, config); var updated = _manager.UpdateTrailingStop("ORD-4", 5005m); var current = _manager.GetCurrentStopPrice("ORD-4"); Assert.IsTrue(current.HasValue); if (updated.HasValue) { Assert.IsTrue(updated.Value <= 5005m); } } [TestMethod] public void UpdateTrailingStop_ShortImprovingPrice_CanReturnUpdatedStop() { var position = CreatePosition(OrderSide.Sell, 5000m); var config = new NT8.Core.Execution.TrailingStopConfig(8); _manager.StartTrailing("ORD-5", position, config); var updated = _manager.UpdateTrailingStop("ORD-5", 4995m); var current = _manager.GetCurrentStopPrice("ORD-5"); Assert.IsTrue(current.HasValue); if (updated.HasValue) { Assert.IsTrue(updated.Value >= 4995m || updated.Value <= 5000m); } } [TestMethod] public void CalculateNewStopPrice_FixedTrailing_Long_ReturnsValue() { var position = CreatePosition(OrderSide.Buy, 5000m); var stop = _manager.CalculateNewStopPrice(StopType.FixedTrailing, position, 5001m); Assert.IsTrue(stop > 0m); } [TestMethod] public void CalculateNewStopPrice_ATRTrailing_Short_ReturnsValue() { var position = CreatePosition(OrderSide.Sell, 5000m); var stop = _manager.CalculateNewStopPrice(StopType.ATRTrailing, position, 4998m); Assert.IsTrue(stop > 0m); } [TestMethod] public void CalculateNewStopPrice_PercentageTrailing_ReturnsValue() { var position = CreatePosition(OrderSide.Buy, 5000m); var stop = _manager.CalculateNewStopPrice(StopType.PercentageTrailing, position, 5010m); Assert.IsTrue(stop > 0m); } [TestMethod] public void ShouldMoveToBreakeven_EnabledAndInProfit_ReturnsTrue() { var position = CreatePosition(OrderSide.Buy, 5000m); var config = new AutoBreakevenConfig(4, true, 1, true); var shouldMove = _manager.ShouldMoveToBreakeven(position, 5002m, config); Assert.IsTrue(shouldMove); } [TestMethod] public void ShouldMoveToBreakeven_Disabled_ReturnsFalse() { var position = CreatePosition(OrderSide.Buy, 5000m); var config = new AutoBreakevenConfig(4, true, 1, false); var shouldMove = _manager.ShouldMoveToBreakeven(position, 5005m, config); Assert.IsFalse(shouldMove); } [TestMethod] public void DeactivateTrailing_PreventsFurtherUpdates() { var position = CreatePosition(OrderSide.Buy, 5000m); var config = new NT8.Core.Execution.TrailingStopConfig(8); _manager.StartTrailing("ORD-6", position, config); _manager.DeactivateTrailing("ORD-6"); var updated = _manager.UpdateTrailingStop("ORD-6", 5010m); Assert.IsFalse(updated.HasValue); } [TestMethod] public void RemoveTrailing_DeletesTracking() { var position = CreatePosition(OrderSide.Buy, 5000m); var config = new NT8.Core.Execution.TrailingStopConfig(8); _manager.StartTrailing("ORD-7", position, config); _manager.RemoveTrailing("ORD-7"); var current = _manager.GetCurrentStopPrice("ORD-7"); Assert.IsFalse(current.HasValue); } [TestMethod] public void GetCurrentStopPrice_NullOrderId_ThrowsArgumentNullException() { Assert.ThrowsException(delegate { _manager.GetCurrentStopPrice(null); }); } private static OrderStatus CreatePosition(OrderSide side, decimal avgFillPrice) { return new OrderStatus { OrderId = Guid.NewGuid().ToString(), Symbol = "ES", Side = side, Quantity = 1, AverageFillPrice = avgFillPrice, State = OrderState.Working, FilledQuantity = 1, CreatedTime = DateTime.UtcNow }; } } }