Production hardening: kill switch, circuit breaker, trailing stops, log level, holiday calendar
Some checks failed
Build and Test / build (push) Has been cancelled
Some checks failed
Build and Test / build (push) Has been cancelled
This commit is contained in:
354
tests/NT8.Core.Tests/Adapters/NT8DataConverterTests.cs
Normal file
354
tests/NT8.Core.Tests/Adapters/NT8DataConverterTests.cs
Normal file
@@ -0,0 +1,354 @@
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using Microsoft.VisualStudio.TestTools.UnitTesting;
|
||||
using NT8.Adapters.NinjaTrader;
|
||||
using NT8.Core.Common.Models;
|
||||
|
||||
namespace NT8.Core.Tests.Adapters
|
||||
{
|
||||
/// <summary>
|
||||
/// Unit tests for NT8DataConverter.
|
||||
/// </summary>
|
||||
[TestClass]
|
||||
public class NT8DataConverterTests
|
||||
{
|
||||
[TestMethod]
|
||||
public void ConvertBar_WithValidESBar_ShouldCreateBarData()
|
||||
{
|
||||
var time = new DateTime(2026, 2, 17, 9, 30, 0, DateTimeKind.Utc);
|
||||
|
||||
var result = NT8DataConverter.ConvertBar("ES", time, 4200.0, 4210.0, 4195.0, 4208.0, 10000, 5);
|
||||
|
||||
Assert.AreEqual("ES", result.Symbol);
|
||||
Assert.AreEqual(time, result.Time);
|
||||
Assert.AreEqual(4200.0, result.Open);
|
||||
Assert.AreEqual(4210.0, result.High);
|
||||
Assert.AreEqual(4195.0, result.Low);
|
||||
Assert.AreEqual(4208.0, result.Close);
|
||||
Assert.AreEqual(10000L, result.Volume);
|
||||
Assert.AreEqual(TimeSpan.FromMinutes(5), result.BarSize);
|
||||
}
|
||||
|
||||
[DataTestMethod]
|
||||
[DataRow(null)]
|
||||
[DataRow("")]
|
||||
[DataRow(" ")]
|
||||
public void ConvertBar_WithInvalidSymbol_ShouldThrowArgumentException(string symbol)
|
||||
{
|
||||
var ex = Assert.ThrowsException<ArgumentException>(
|
||||
delegate
|
||||
{
|
||||
NT8DataConverter.ConvertBar(symbol, DateTime.UtcNow, 1, 2, 0.5, 1.5, 10, 5);
|
||||
});
|
||||
|
||||
Assert.AreEqual("symbol", ex.Message);
|
||||
}
|
||||
|
||||
[DataTestMethod]
|
||||
[DataRow(0)]
|
||||
[DataRow(-1)]
|
||||
[DataRow(-60)]
|
||||
public void ConvertBar_WithInvalidBarSize_ShouldThrowArgumentException(int barSize)
|
||||
{
|
||||
var ex = Assert.ThrowsException<ArgumentException>(
|
||||
delegate
|
||||
{
|
||||
NT8DataConverter.ConvertBar("ES", DateTime.UtcNow, 1, 2, 0.5, 1.5, 10, barSize);
|
||||
});
|
||||
|
||||
Assert.AreEqual("barSizeMinutes", ex.Message);
|
||||
}
|
||||
|
||||
[TestMethod]
|
||||
public void ConvertBar_WithDifferentTimeframes_ShouldSetCorrectBarSize()
|
||||
{
|
||||
var sizes = new[] { 1, 5, 15, 30, 60, 240, 1440 };
|
||||
|
||||
for (var i = 0; i < sizes.Length; i++)
|
||||
{
|
||||
var value = sizes[i];
|
||||
var result = NT8DataConverter.ConvertBar("ES", DateTime.UtcNow, 1, 2, 0.5, 1.5, 10, value);
|
||||
Assert.AreEqual(TimeSpan.FromMinutes(value), result.BarSize);
|
||||
}
|
||||
}
|
||||
|
||||
[TestMethod]
|
||||
public void ConvertBar_WithHighLessThanLow_ShouldStillCreate()
|
||||
{
|
||||
var result = NT8DataConverter.ConvertBar("ES", DateTime.UtcNow, 100, 95, 105, 99, 1000, 5);
|
||||
|
||||
Assert.AreEqual(95.0, result.High);
|
||||
Assert.AreEqual(105.0, result.Low);
|
||||
}
|
||||
|
||||
[TestMethod]
|
||||
public void ConvertBar_WithZeroVolume_ShouldCreateBar()
|
||||
{
|
||||
var result = NT8DataConverter.ConvertBar("MES", DateTime.UtcNow, 5000, 5005, 4995, 5001, 0, 1);
|
||||
|
||||
Assert.AreEqual(0L, result.Volume);
|
||||
}
|
||||
|
||||
[TestMethod]
|
||||
public void ConvertBar_WithNegativePrices_ShouldHandleCorrectly()
|
||||
{
|
||||
var result = NT8DataConverter.ConvertBar("ZN", DateTime.UtcNow, -1.2, -0.9, -1.4, -1.0, 2500, 5);
|
||||
|
||||
Assert.AreEqual(-1.2, result.Open);
|
||||
Assert.AreEqual(-0.9, result.High);
|
||||
Assert.AreEqual(-1.4, result.Low);
|
||||
Assert.AreEqual(-1.0, result.Close);
|
||||
}
|
||||
|
||||
[TestMethod]
|
||||
public void ConvertBar_WithLargeVolume_ShouldHandleCorrectly()
|
||||
{
|
||||
var result = NT8DataConverter.ConvertBar("NQ", DateTime.UtcNow, 100, 110, 95, 108, 10000000, 5);
|
||||
|
||||
Assert.AreEqual(10000000L, result.Volume);
|
||||
}
|
||||
|
||||
[TestMethod]
|
||||
public void ConvertAccount_WithPositiveValues_ShouldCreateAccountInfo()
|
||||
{
|
||||
var now = DateTime.UtcNow;
|
||||
var result = NT8DataConverter.ConvertAccount(100000, 250000, 1250.50, 0.05, now);
|
||||
|
||||
Assert.AreEqual(100000.0, result.Equity);
|
||||
Assert.AreEqual(250000.0, result.BuyingPower);
|
||||
Assert.AreEqual(1250.50, result.DailyPnL);
|
||||
Assert.AreEqual(0.05, result.MaxDrawdown);
|
||||
Assert.AreEqual(now, result.LastUpdate);
|
||||
}
|
||||
|
||||
[TestMethod]
|
||||
public void ConvertAccount_WithNegativePnL_ShouldHandleCorrectly()
|
||||
{
|
||||
var result = NT8DataConverter.ConvertAccount(100000, 250000, -2500.75, 0.05, DateTime.UtcNow);
|
||||
|
||||
Assert.AreEqual(-2500.75, result.DailyPnL);
|
||||
}
|
||||
|
||||
[TestMethod]
|
||||
public void ConvertAccount_WithZeroValues_ShouldCreateAccount()
|
||||
{
|
||||
var result = NT8DataConverter.ConvertAccount(0, 0, 0, 0, DateTime.UtcNow);
|
||||
|
||||
Assert.AreEqual(0.0, result.Equity);
|
||||
Assert.AreEqual(0.0, result.BuyingPower);
|
||||
Assert.AreEqual(0.0, result.DailyPnL);
|
||||
Assert.AreEqual(0.0, result.MaxDrawdown);
|
||||
}
|
||||
|
||||
[TestMethod]
|
||||
public void ConvertAccount_WithLargeEquity_ShouldHandleCorrectly()
|
||||
{
|
||||
var result = NT8DataConverter.ConvertAccount(10000000, 25000000, 5000, 100000, DateTime.UtcNow);
|
||||
|
||||
Assert.AreEqual(10000000.0, result.Equity);
|
||||
Assert.AreEqual(25000000.0, result.BuyingPower);
|
||||
}
|
||||
|
||||
[TestMethod]
|
||||
public void ConvertPosition_WithLongPosition_ShouldCreatePosition()
|
||||
{
|
||||
var result = NT8DataConverter.ConvertPosition("ES", 2, 4200.50, 250, 500, DateTime.UtcNow);
|
||||
|
||||
Assert.AreEqual("ES", result.Symbol);
|
||||
Assert.IsTrue(result.Quantity > 0);
|
||||
Assert.AreEqual(2, result.Quantity);
|
||||
}
|
||||
|
||||
[TestMethod]
|
||||
public void ConvertPosition_WithShortPosition_ShouldHandleNegativeQuantity()
|
||||
{
|
||||
var result = NT8DataConverter.ConvertPosition("ES", -1, 4200.50, -150, 200, DateTime.UtcNow);
|
||||
|
||||
Assert.IsTrue(result.Quantity < 0);
|
||||
Assert.AreEqual(-1, result.Quantity);
|
||||
}
|
||||
|
||||
[TestMethod]
|
||||
public void ConvertPosition_WithFlatPosition_ShouldHandleZeroQuantity()
|
||||
{
|
||||
var result = NT8DataConverter.ConvertPosition("ES", 0, 0, 0, 0, DateTime.UtcNow);
|
||||
|
||||
Assert.AreEqual(0, result.Quantity);
|
||||
Assert.AreEqual(0.0, result.AveragePrice);
|
||||
}
|
||||
|
||||
[DataTestMethod]
|
||||
[DataRow(null)]
|
||||
[DataRow("")]
|
||||
[DataRow(" ")]
|
||||
public void ConvertPosition_WithInvalidSymbol_ShouldThrowArgumentException(string symbol)
|
||||
{
|
||||
var ex = Assert.ThrowsException<ArgumentException>(
|
||||
delegate
|
||||
{
|
||||
NT8DataConverter.ConvertPosition(symbol, 1, 1, 1, 1, DateTime.UtcNow);
|
||||
});
|
||||
|
||||
Assert.AreEqual("symbol", ex.Message);
|
||||
}
|
||||
|
||||
[TestMethod]
|
||||
public void ConvertPosition_WithNegativeUnrealizedPnL_ShouldHandleCorrectly()
|
||||
{
|
||||
var result = NT8DataConverter.ConvertPosition("ES", 1, 4200.50, -350.25, 20, DateTime.UtcNow);
|
||||
|
||||
Assert.AreEqual(-350.25, result.UnrealizedPnL);
|
||||
}
|
||||
|
||||
[TestMethod]
|
||||
public void ConvertSession_WithRTHSession_ShouldCreateMarketSession()
|
||||
{
|
||||
var start = new DateTime(2026, 2, 17, 9, 30, 0, DateTimeKind.Utc);
|
||||
var end = new DateTime(2026, 2, 17, 16, 0, 0, DateTimeKind.Utc);
|
||||
|
||||
var result = NT8DataConverter.ConvertSession(start, end, true, "RTH");
|
||||
|
||||
Assert.IsTrue(result.IsRth);
|
||||
Assert.AreEqual("RTH", result.SessionName);
|
||||
}
|
||||
|
||||
[TestMethod]
|
||||
public void ConvertSession_WithETHSession_ShouldCreateMarketSession()
|
||||
{
|
||||
var start = new DateTime(2026, 2, 17, 18, 0, 0, DateTimeKind.Utc);
|
||||
var end = new DateTime(2026, 2, 18, 9, 30, 0, DateTimeKind.Utc);
|
||||
|
||||
var result = NT8DataConverter.ConvertSession(start, end, false, "ETH");
|
||||
|
||||
Assert.IsFalse(result.IsRth);
|
||||
Assert.AreEqual(start, result.SessionStart);
|
||||
Assert.AreEqual(end, result.SessionEnd);
|
||||
}
|
||||
|
||||
[DataTestMethod]
|
||||
[DataRow(null)]
|
||||
[DataRow("")]
|
||||
[DataRow(" ")]
|
||||
public void ConvertSession_WithInvalidName_ShouldThrowArgumentException(string name)
|
||||
{
|
||||
var ex = Assert.ThrowsException<ArgumentException>(
|
||||
delegate
|
||||
{
|
||||
NT8DataConverter.ConvertSession(DateTime.UtcNow, DateTime.UtcNow.AddHours(1), true, name);
|
||||
});
|
||||
|
||||
Assert.AreEqual("sessionName", ex.Message);
|
||||
}
|
||||
|
||||
[TestMethod]
|
||||
public void ConvertSession_WithEndBeforeStart_ShouldThrowArgumentException()
|
||||
{
|
||||
var start = new DateTime(2026, 2, 17, 16, 0, 0, DateTimeKind.Utc);
|
||||
var end = new DateTime(2026, 2, 17, 9, 30, 0, DateTimeKind.Utc);
|
||||
|
||||
var ex = Assert.ThrowsException<ArgumentException>(
|
||||
delegate
|
||||
{
|
||||
NT8DataConverter.ConvertSession(start, end, true, "RTH");
|
||||
});
|
||||
|
||||
Assert.AreEqual("sessionEnd", ex.Message);
|
||||
}
|
||||
|
||||
[TestMethod]
|
||||
public void ConvertContext_WithValidInputs_ShouldCreateStrategyContext()
|
||||
{
|
||||
var position = CreatePosition();
|
||||
var account = CreateAccount();
|
||||
var session = CreateSession();
|
||||
var customData = new Dictionary<string, object>();
|
||||
customData.Add("a", 1);
|
||||
customData.Add("b", "value");
|
||||
|
||||
var result = NT8DataConverter.ConvertContext("ES", DateTime.UtcNow, position, account, session, customData);
|
||||
|
||||
Assert.AreEqual("ES", result.Symbol);
|
||||
Assert.AreEqual(position, result.CurrentPosition);
|
||||
Assert.AreEqual(account, result.Account);
|
||||
Assert.AreEqual(session, result.Session);
|
||||
Assert.AreEqual(2, result.CustomData.Count);
|
||||
Assert.AreEqual(1, (int)result.CustomData["a"]);
|
||||
Assert.AreEqual("value", (string)result.CustomData["b"]);
|
||||
}
|
||||
|
||||
[TestMethod]
|
||||
public void ConvertContext_WithNullCustomData_ShouldCreateEmptyDictionary()
|
||||
{
|
||||
var result = NT8DataConverter.ConvertContext("ES", DateTime.UtcNow, CreatePosition(), CreateAccount(), CreateSession(), null);
|
||||
|
||||
Assert.IsNotNull(result.CustomData);
|
||||
Assert.AreEqual(0, result.CustomData.Count);
|
||||
}
|
||||
|
||||
[DataTestMethod]
|
||||
[DataRow(null)]
|
||||
[DataRow("")]
|
||||
[DataRow(" ")]
|
||||
public void ConvertContext_WithInvalidSymbol_ShouldThrowArgumentException(string symbol)
|
||||
{
|
||||
var ex = Assert.ThrowsException<ArgumentException>(
|
||||
delegate
|
||||
{
|
||||
NT8DataConverter.ConvertContext(symbol, DateTime.UtcNow, CreatePosition(), CreateAccount(), CreateSession(), null);
|
||||
});
|
||||
|
||||
Assert.AreEqual("symbol", ex.Message);
|
||||
}
|
||||
|
||||
[TestMethod]
|
||||
public void ConvertContext_WithNullPosition_ShouldThrowArgumentNullException()
|
||||
{
|
||||
var ex = Assert.ThrowsException<ArgumentNullException>(
|
||||
delegate
|
||||
{
|
||||
NT8DataConverter.ConvertContext("ES", DateTime.UtcNow, null, CreateAccount(), CreateSession(), null);
|
||||
});
|
||||
|
||||
Assert.AreEqual("currentPosition", ex.ParamName);
|
||||
}
|
||||
|
||||
[TestMethod]
|
||||
public void ConvertContext_WithNullAccount_ShouldThrowArgumentNullException()
|
||||
{
|
||||
var ex = Assert.ThrowsException<ArgumentNullException>(
|
||||
delegate
|
||||
{
|
||||
NT8DataConverter.ConvertContext("ES", DateTime.UtcNow, CreatePosition(), null, CreateSession(), null);
|
||||
});
|
||||
|
||||
Assert.AreEqual("account", ex.ParamName);
|
||||
}
|
||||
|
||||
[TestMethod]
|
||||
public void ConvertContext_WithNullSession_ShouldThrowArgumentNullException()
|
||||
{
|
||||
var ex = Assert.ThrowsException<ArgumentNullException>(
|
||||
delegate
|
||||
{
|
||||
NT8DataConverter.ConvertContext("ES", DateTime.UtcNow, CreatePosition(), CreateAccount(), null, null);
|
||||
});
|
||||
|
||||
Assert.AreEqual("session", ex.ParamName);
|
||||
}
|
||||
|
||||
private static Position CreatePosition()
|
||||
{
|
||||
return new Position("ES", 1, 4200.0, 10.0, 5.0, DateTime.UtcNow);
|
||||
}
|
||||
|
||||
private static AccountInfo CreateAccount()
|
||||
{
|
||||
return new AccountInfo(100000.0, 250000.0, 500.0, 2500.0, DateTime.UtcNow);
|
||||
}
|
||||
|
||||
private static MarketSession CreateSession()
|
||||
{
|
||||
return new MarketSession(DateTime.UtcNow.Date.AddHours(9.5), DateTime.UtcNow.Date.AddHours(16), true, "RTH");
|
||||
}
|
||||
}
|
||||
}
|
||||
245
tests/NT8.Core.Tests/Adapters/NT8ExecutionAdapterTests.cs
Normal file
245
tests/NT8.Core.Tests/Adapters/NT8ExecutionAdapterTests.cs
Normal file
@@ -0,0 +1,245 @@
|
||||
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
|
||||
{
|
||||
/// <summary>
|
||||
/// Unit tests for NT8ExecutionAdapter.
|
||||
/// </summary>
|
||||
[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<InvalidOperationException>(
|
||||
delegate
|
||||
{
|
||||
adapter.SubmitOrder(CreateRequest(), "SDK-1");
|
||||
});
|
||||
}
|
||||
|
||||
[TestMethod]
|
||||
public void SubmitOrder_WithNullRequest_ShouldThrowArgumentNullException()
|
||||
{
|
||||
var adapter = new NT8ExecutionAdapter();
|
||||
|
||||
Assert.ThrowsException<ArgumentNullException>(
|
||||
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<Task>();
|
||||
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;
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,57 @@
|
||||
using System;
|
||||
using Microsoft.VisualStudio.TestTools.UnitTesting;
|
||||
using NT8.Core.Execution;
|
||||
using NT8.Core.OMS;
|
||||
using NT8.Core.Tests.Mocks;
|
||||
using ExecutionTrailingStopConfig = NT8.Core.Execution.TrailingStopConfig;
|
||||
|
||||
namespace NT8.Core.Tests.Execution
|
||||
{
|
||||
[TestClass]
|
||||
public class TrailingStopManagerFixedTests
|
||||
{
|
||||
private TrailingStopManager _manager;
|
||||
|
||||
[TestInitialize]
|
||||
public void TestInitialize()
|
||||
{
|
||||
_manager = new TrailingStopManager(new MockLogger<TrailingStopManager>());
|
||||
}
|
||||
|
||||
[TestMethod]
|
||||
public void CalculateNewStopPrice_FixedTrailing_LongAt5100With8Ticks_Returns5098()
|
||||
{
|
||||
var position = CreatePosition(OrderSide.Buy, 5000m);
|
||||
var config = new ExecutionTrailingStopConfig(StopType.FixedTrailing, 8, 2m, true);
|
||||
|
||||
var stop = _manager.CalculateNewStopPrice(StopType.FixedTrailing, position, 5100m, config);
|
||||
|
||||
Assert.AreEqual(5098.0m, stop);
|
||||
}
|
||||
|
||||
[TestMethod]
|
||||
public void CalculateNewStopPrice_FixedTrailing_ShortAt5100With8Ticks_Returns5102()
|
||||
{
|
||||
var position = CreatePosition(OrderSide.Sell, 5000m);
|
||||
var config = new ExecutionTrailingStopConfig(StopType.FixedTrailing, 8, 2m, true);
|
||||
|
||||
var stop = _manager.CalculateNewStopPrice(StopType.FixedTrailing, position, 5100m, config);
|
||||
|
||||
Assert.AreEqual(5102.0m, stop);
|
||||
}
|
||||
|
||||
private static OrderStatus CreatePosition(OrderSide side, decimal averageFillPrice)
|
||||
{
|
||||
var position = new OrderStatus();
|
||||
position.OrderId = Guid.NewGuid().ToString();
|
||||
position.Symbol = "ES";
|
||||
position.Side = side;
|
||||
position.Quantity = 1;
|
||||
position.AverageFillPrice = averageFillPrice;
|
||||
position.State = OrderState.Working;
|
||||
position.FilledQuantity = 1;
|
||||
position.CreatedTime = DateTime.UtcNow;
|
||||
return position;
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -13,6 +13,7 @@
|
||||
|
||||
<ItemGroup>
|
||||
<ProjectReference Include="..\..\src\NT8.Core\NT8.Core.csproj" />
|
||||
<ProjectReference Include="..\..\src\NT8.Adapters\NT8.Adapters.csproj" />
|
||||
</ItemGroup>
|
||||
|
||||
</Project>
|
||||
</Project>
|
||||
|
||||
Reference in New Issue
Block a user