using System;
using System.Collections.Generic;
using System.Threading.Tasks;
using Microsoft.VisualStudio.TestTools.UnitTesting;
using NT8.Adapters.NinjaTrader;
using NT8.Core.OMS;
namespace NT8.Core.Tests.Adapters
{
///
/// Unit tests for NT8ExecutionAdapter.
///
[TestClass]
public class NT8ExecutionAdapterTests
{
[TestMethod]
public void SubmitOrder_WithValidRequest_ShouldCreateTrackingInfo()
{
var adapter = new NT8ExecutionAdapter();
var request = CreateRequest();
var info = adapter.SubmitOrder(request, "SDK-1");
Assert.IsNotNull(info);
Assert.AreEqual("SDK-1", info.SdkOrderId);
Assert.AreEqual(OrderState.Pending, info.CurrentState);
Assert.AreEqual(0, info.FilledQuantity);
}
[TestMethod]
public void SubmitOrder_WithDuplicateOrderId_ShouldThrowInvalidOperationException()
{
var adapter = new NT8ExecutionAdapter();
adapter.SubmitOrder(CreateRequest(), "SDK-1");
Assert.ThrowsException(
delegate
{
adapter.SubmitOrder(CreateRequest(), "SDK-1");
});
}
[TestMethod]
public void SubmitOrder_WithNullRequest_ShouldThrowArgumentNullException()
{
var adapter = new NT8ExecutionAdapter();
Assert.ThrowsException(
delegate
{
adapter.SubmitOrder(null, "SDK-1");
});
}
[TestMethod]
public void ProcessOrderUpdate_WithWorkingState_ShouldUpdateState()
{
var adapter = new NT8ExecutionAdapter();
adapter.SubmitOrder(CreateRequest(), "SDK-1");
adapter.ProcessOrderUpdate("NT8-1", "SDK-1", "WORKING", 0, 0.0, 0, null);
var status = adapter.GetOrderStatus("SDK-1");
Assert.IsNotNull(status);
Assert.AreEqual(OrderState.Working, status.State);
}
[TestMethod]
public void ProcessOrderUpdate_WithFilledState_ShouldMarkFilled()
{
var adapter = new NT8ExecutionAdapter();
adapter.SubmitOrder(CreateRequest(2), "SDK-1");
adapter.ProcessOrderUpdate("NT8-1", "SDK-1", "FILLED", 2, 4205.25, 0, null);
var status = adapter.GetOrderStatus("SDK-1");
Assert.AreEqual(OrderState.Filled, status.State);
Assert.AreEqual(2, status.FilledQuantity);
}
[TestMethod]
public void ProcessOrderUpdate_WithRejection_ShouldSetRejectedState()
{
var adapter = new NT8ExecutionAdapter();
adapter.SubmitOrder(CreateRequest(), "SDK-1");
adapter.ProcessOrderUpdate("NT8-1", "SDK-1", "WORKING", 0, 0.0, 123, "Rejected by broker");
var status = adapter.GetOrderStatus("SDK-1");
Assert.AreEqual(OrderState.Rejected, status.State);
}
[TestMethod]
public void ProcessExecution_WithFullFill_ShouldMarkFilled()
{
var adapter = new NT8ExecutionAdapter();
adapter.SubmitOrder(CreateRequest(3), "SDK-1");
adapter.ProcessOrderUpdate("NT8-1", "SDK-1", "PARTFILLED", 3, 4202.0, 0, null);
adapter.ProcessExecution("NT8-1", "EX-1", 4202.0, 3, DateTime.UtcNow);
var status = adapter.GetOrderStatus("SDK-1");
Assert.AreEqual(OrderState.Filled, status.State);
}
[TestMethod]
public void ProcessExecution_WithPartialFill_ShouldMarkPartiallyFilled()
{
var adapter = new NT8ExecutionAdapter();
adapter.SubmitOrder(CreateRequest(3), "SDK-1");
adapter.ProcessOrderUpdate("NT8-1", "SDK-1", "PARTFILLED", 1, 4202.0, 0, null);
adapter.ProcessExecution("NT8-1", "EX-1", 4202.0, 1, DateTime.UtcNow);
var status = adapter.GetOrderStatus("SDK-1");
Assert.AreEqual(OrderState.PartiallyFilled, status.State);
}
[TestMethod]
public void CancelOrder_WithWorkingOrder_ShouldReturnTrue()
{
var adapter = new NT8ExecutionAdapter();
adapter.SubmitOrder(CreateRequest(), "SDK-1");
adapter.ProcessOrderUpdate("NT8-1", "SDK-1", "WORKING", 0, 0.0, 0, null);
var result = adapter.CancelOrder("SDK-1");
Assert.IsTrue(result);
}
[TestMethod]
public void CancelOrder_WithFilledOrder_ShouldReturnFalse()
{
var adapter = new NT8ExecutionAdapter();
adapter.SubmitOrder(CreateRequest(), "SDK-1");
adapter.ProcessOrderUpdate("NT8-1", "SDK-1", "FILLED", 1, 4202.0, 0, null);
var result = adapter.CancelOrder("SDK-1");
Assert.IsFalse(result);
}
[TestMethod]
public void GetOrderStatus_WithExistingOrder_ShouldReturnStatus()
{
var adapter = new NT8ExecutionAdapter();
adapter.SubmitOrder(CreateRequest(), "SDK-1");
var status = adapter.GetOrderStatus("SDK-1");
Assert.IsNotNull(status);
Assert.AreEqual("SDK-1", status.OrderId);
Assert.AreEqual("ES", status.Symbol);
}
[TestMethod]
public void GetOrderStatus_WithNonExistentOrder_ShouldReturnNull()
{
var adapter = new NT8ExecutionAdapter();
var status = adapter.GetOrderStatus("MISSING");
Assert.IsNull(status);
}
[DataTestMethod]
[DataRow("ACCEPTED", OrderState.Working)]
[DataRow("FILLED", OrderState.Filled)]
[DataRow("CANCELLED", OrderState.Cancelled)]
[DataRow("REJECTED", OrderState.Rejected)]
public void MapNT8OrderState_WithKnownStates_ShouldMapCorrectly(string nt8State, OrderState expected)
{
var adapter = new NT8ExecutionAdapter();
adapter.SubmitOrder(CreateRequest(), "SDK-1");
adapter.ProcessOrderUpdate("NT8-1", "SDK-1", nt8State, 0, 0.0, 0, null);
var status = adapter.GetOrderStatus("SDK-1");
Assert.AreEqual(expected, status.State);
}
[TestMethod]
public void SubmitOrder_WithConcurrentCalls_ShouldBeThreadSafe()
{
var adapter = new NT8ExecutionAdapter();
var tasks = new List();
var count = 50;
for (var i = 0; i < count; i++)
{
var index = i;
tasks.Add(Task.Run(
delegate
{
adapter.SubmitOrder(CreateRequest(), string.Format("SDK-{0}", index));
}));
}
Task.WaitAll(tasks.ToArray());
for (var i = 0; i < count; i++)
{
var status = adapter.GetOrderStatus(string.Format("SDK-{0}", i));
Assert.IsNotNull(status);
}
}
[TestMethod]
public void ProcessExecution_WithMultipleCallsForSameOrder_ShouldAccumulate()
{
var adapter = new NT8ExecutionAdapter();
adapter.SubmitOrder(CreateRequest(3), "SDK-1");
adapter.ProcessOrderUpdate("NT8-1", "SDK-1", "PARTFILLED", 1, 4201.0, 0, null);
adapter.ProcessExecution("NT8-1", "EX-1", 4201.0, 1, DateTime.UtcNow);
var statusAfterFirst = adapter.GetOrderStatus("SDK-1");
Assert.AreEqual(OrderState.PartiallyFilled, statusAfterFirst.State);
adapter.ProcessOrderUpdate("NT8-1", "SDK-1", "FILLED", 3, 4202.0, 0, null);
adapter.ProcessExecution("NT8-1", "EX-2", 4202.0, 2, DateTime.UtcNow);
var statusAfterSecond = adapter.GetOrderStatus("SDK-1");
Assert.AreEqual(OrderState.Filled, statusAfterSecond.State);
Assert.AreEqual(3, statusAfterSecond.FilledQuantity);
}
private static OrderRequest CreateRequest()
{
return CreateRequest(1);
}
private static OrderRequest CreateRequest(int quantity)
{
var request = new OrderRequest();
request.Symbol = "ES";
request.Side = OrderSide.Buy;
request.Type = OrderType.Market;
request.Quantity = quantity;
request.TimeInForce = TimeInForce.Day;
request.ClientOrderId = Guid.NewGuid().ToString();
return request;
}
}
}