using NT8.Core.Risk; using NT8.Core.Common.Models; using NT8.Core.Logging; using Microsoft.VisualStudio.TestTools.UnitTesting; using System; namespace NT8.Core.Tests.Risk { [TestClass] public class BasicRiskManagerTests { private ILogger _logger; private BasicRiskManager _riskManager; [TestInitialize] public void TestInitialize() { _logger = new BasicLogger("BasicRiskManagerTests"); _riskManager = new BasicRiskManager(_logger); } [TestMethod] public void ValidateOrder_WithinLimits_ShouldAllow() { // Arrange var intent = TestDataBuilder.CreateValidIntent(stopTicks: 8); var context = TestDataBuilder.CreateTestContext(); var config = TestDataBuilder.CreateTestRiskConfig(); // Act var result = _riskManager.ValidateOrder(intent, context, config); // Assert Assert.IsTrue(result.Allow); Assert.IsNull(result.RejectReason); Assert.AreEqual(RiskLevel.Low, result.RiskLevel); Assert.IsTrue(result.RiskMetrics.ContainsKey("trade_risk")); Assert.IsTrue(result.RiskMetrics.ContainsKey("daily_pnl")); } [TestMethod] public void ValidateOrder_ExceedsDailyLimit_ShouldReject() { // Arrange var intent = TestDataBuilder.CreateValidIntent(); var context = TestDataBuilder.CreateTestContext(); var config = new RiskConfig( dailyLossLimit: 1000, maxTradeRisk: 500, maxOpenPositions: 5, emergencyFlattenEnabled: true ); // Simulate daily loss exceeding limit _riskManager.OnPnLUpdate(0, -1001); // Act var result = _riskManager.ValidateOrder(intent, context, config); // Assert Assert.IsFalse(result.Allow); // Accept either "Trading halted" or "Daily loss limit" as valid rejection reasons Assert.IsTrue(result.RejectReason.Contains("Trading halted") || result.RejectReason.Contains("Daily loss limit breached"), "Expected reject reason to contain either 'Trading halted' or 'Daily loss limit breached', but got: " + result.RejectReason); Assert.AreEqual(RiskLevel.Critical, result.RiskLevel); Assert.AreEqual(-1001.0, result.RiskMetrics["daily_pnl"]); } [TestMethod] public void ValidateOrder_ExceedsTradeRisk_ShouldReject() { // Arrange var intent = TestDataBuilder.CreateValidIntent(stopTicks: 100); // High risk trade var context = TestDataBuilder.CreateTestContext(); var config = new RiskConfig( dailyLossLimit: 10000, maxTradeRisk: 500, // Lower than calculated trade risk maxOpenPositions: 5, emergencyFlattenEnabled: true ); // Act var result = _riskManager.ValidateOrder(intent, context, config); // Assert Assert.IsFalse(result.Allow); // Accept either "Trading halted" or "Trade risk too high" as valid rejection reasons Assert.IsTrue(result.RejectReason.Contains("Trading halted") || result.RejectReason.Contains("Trade risk too high"), "Expected reject reason to contain either 'Trading halted' or 'Trade risk too high', but got: " + result.RejectReason); Assert.AreEqual(RiskLevel.High, result.RiskLevel); // Verify risk calculation var expectedRisk = 100 * 12.50; // 100 ticks * ES tick value Assert.AreEqual(expectedRisk, result.RiskMetrics["trade_risk"]); } [TestMethod] public void ValidateOrder_WithNullParameters_ShouldThrow() { // Arrange var intent = TestDataBuilder.CreateValidIntent(); var context = TestDataBuilder.CreateTestContext(); var config = TestDataBuilder.CreateTestRiskConfig(); // Act & Assert Assert.ThrowsException(() => _riskManager.ValidateOrder(null, context, config)); Assert.ThrowsException(() => _riskManager.ValidateOrder(intent, null, config)); Assert.ThrowsException(() => _riskManager.ValidateOrder(intent, context, null)); } } }