using Microsoft.VisualStudio.TestTools.UnitTesting; using NT8.Core.Common.Models; using NT8.Core.Logging; using NT8.Core.Risk; using System; using System.Collections.Generic; namespace NT8.Core.Tests.Risk { [TestClass] public class AdvancedRiskManagerTests { private AdvancedRiskManager _advancedRiskManager; [TestInitialize] public void TestInitialize() { _advancedRiskManager = CreateManager( weeklyLossLimit: 10000, trailingDrawdownLimit: 5000, maxCrossStrategyExposure: 100000, maxCorrelatedExposure: 100000, tradingTimeWindows: new List()); } [TestMethod] public void ValidateOrder_AllChecksPass_ShouldAllow() { // Arrange var intent = TestDataBuilder.CreateValidIntent(symbol: "ES", stopTicks: 8); var context = TestDataBuilder.CreateTestContext(symbol: "ES"); var config = TestDataBuilder.CreateTestRiskConfig(); // Act var result = _advancedRiskManager.ValidateOrder(intent, context, config); // Assert Assert.IsTrue(result.Allow); Assert.IsNull(result.RejectReason); Assert.IsTrue(result.RiskMetrics.ContainsKey("weekly_pnl")); Assert.IsTrue(result.RiskMetrics.ContainsKey("trailing_drawdown")); Assert.IsTrue(result.RiskMetrics.ContainsKey("active_strategies")); } [TestMethod] public void ValidateOrder_WeeklyLossLimitBreached_ShouldReject() { // Arrange var manager = CreateManager( weeklyLossLimit: 5000, trailingDrawdownLimit: 50000, maxCrossStrategyExposure: 100000, maxCorrelatedExposure: 100000, tradingTimeWindows: new List()); var intent = TestDataBuilder.CreateValidIntent(symbol: "ES", stopTicks: 8); var context = TestDataBuilder.CreateTestContext(symbol: "ES"); var config = TestDataBuilder.CreateTestRiskConfig(); // Keep daily PnL above BasicRiskManager emergency threshold (-900), // but accumulate enough weekly loss to breach advanced weekly limit. for (var i = 0; i < 9; i++) { manager.OnPnLUpdate(50000, -600); } // Act var result = manager.ValidateOrder(intent, context, config); // Assert Assert.IsFalse(result.Allow); Assert.IsTrue(result.RejectReason.Contains("Weekly loss limit breached")); Assert.AreEqual(RiskLevel.Critical, result.RiskLevel); } [TestMethod] public void ValidateOrder_TrailingDrawdownBreached_ShouldReject() { // Arrange var manager = CreateManager( weeklyLossLimit: 100000, trailingDrawdownLimit: 1000, maxCrossStrategyExposure: 100000, maxCorrelatedExposure: 100000, tradingTimeWindows: new List()); var intent = TestDataBuilder.CreateValidIntent(symbol: "ES", stopTicks: 8); var context = new StrategyContext( symbol: "ES", currentTime: DateTime.UtcNow, currentPosition: new Position("ES", 0, 0, 0, 0, DateTime.UtcNow), account: new AccountInfo(48000, 48000, 0, 0, DateTime.UtcNow), session: new MarketSession(DateTime.Today.AddHours(9.5), DateTime.Today.AddHours(16), true, "RTH"), customData: new Dictionary()); var config = TestDataBuilder.CreateTestRiskConfig(); // Build peak equity in manager state, then validate with lower account equity in context. manager.OnPnLUpdate(50000, 100); // Act var result = manager.ValidateOrder(intent, context, config); // Assert Assert.IsFalse(result.Allow); Assert.IsTrue(result.RejectReason.Contains("Trailing drawdown limit breached")); Assert.AreEqual(RiskLevel.Critical, result.RiskLevel); } [TestMethod] public void ValidateOrder_CrossStrategyExposureExceeded_ShouldReject() { // Arrange var manager = CreateManager( weeklyLossLimit: 100000, trailingDrawdownLimit: 50000, maxCrossStrategyExposure: 50, maxCorrelatedExposure: 100000, tradingTimeWindows: new List()); var intent = TestDataBuilder.CreateValidIntent(symbol: "ES", stopTicks: 8); var context = TestDataBuilder.CreateTestContext(symbol: "ES"); var config = TestDataBuilder.CreateTestRiskConfig(); // Act var result = manager.ValidateOrder(intent, context, config); // Assert Assert.IsFalse(result.Allow); Assert.IsTrue(result.RejectReason.Contains("Cross-strategy exposure limit exceeded")); Assert.AreEqual(RiskLevel.High, result.RiskLevel); } [TestMethod] public void ValidateOrder_CorrelatedExposureExceeded_ShouldReject() { // Arrange var manager = CreateManager( weeklyLossLimit: 100000, trailingDrawdownLimit: 50000, maxCrossStrategyExposure: 100000, maxCorrelatedExposure: 50, tradingTimeWindows: new List()); var intent = TestDataBuilder.CreateValidIntent(symbol: "ES", stopTicks: 8); var context = TestDataBuilder.CreateTestContext(symbol: "ES"); var config = TestDataBuilder.CreateTestRiskConfig(); // Act var result = manager.ValidateOrder(intent, context, config); // Assert Assert.IsFalse(result.Allow); Assert.IsTrue(result.RejectReason.Contains("Correlated exposure limit exceeded")); Assert.AreEqual(RiskLevel.High, result.RiskLevel); } [TestMethod] public void ValidateOrder_OutsideTradingWindow_ShouldReject() { // Arrange var now = DateTime.UtcNow.TimeOfDay; var windows = new List(); windows.Add(new TradingTimeWindow(now.Add(TimeSpan.FromHours(1)), now.Add(TimeSpan.FromHours(2)))); var manager = CreateManager( weeklyLossLimit: 100000, trailingDrawdownLimit: 50000, maxCrossStrategyExposure: 100000, maxCorrelatedExposure: 100000, tradingTimeWindows: windows); var intent = TestDataBuilder.CreateValidIntent(symbol: "ES", stopTicks: 8); var context = TestDataBuilder.CreateTestContext(symbol: "ES"); var config = TestDataBuilder.CreateTestRiskConfig(); // Act var result = manager.ValidateOrder(intent, context, config); // Assert Assert.IsFalse(result.Allow); Assert.IsTrue(result.RejectReason.Contains("outside allowed trading time windows")); Assert.AreEqual(RiskLevel.Medium, result.RiskLevel); } private static AdvancedRiskManager CreateManager( double weeklyLossLimit, double trailingDrawdownLimit, double? maxCrossStrategyExposure, double? maxCorrelatedExposure, List tradingTimeWindows) { ILogger logger = new BasicLogger("AdvancedRiskManagerTests"); var basicRiskManager = new BasicRiskManager(logger); var advancedConfig = new AdvancedRiskConfig( weeklyLossLimit, trailingDrawdownLimit, maxCrossStrategyExposure, TimeSpan.FromMinutes(30), maxCorrelatedExposure, tradingTimeWindows); return new AdvancedRiskManager(logger, basicRiskManager, advancedConfig); } } }