using Microsoft.VisualStudio.TestTools.UnitTesting; using NT8.Core.Common.Models; using NT8.Core.Logging; using NT8.Core.Risk; using NT8.Core.Sizing; using System; using System.Collections.Generic; namespace NT8.Integration.Tests { /// /// Integration tests for Phase 2 risk + sizing workflow. /// [TestClass] public class RiskSizingIntegrationTests { /// /// Verifies that a valid intent passes advanced risk and then receives a valid size. /// [TestMethod] public void EndToEnd_ValidIntent_RiskAllows_ThenSizingReturnsContracts() { // Arrange var logger = new BasicLogger("RiskSizingIntegrationTests"); var basicRiskManager = new BasicRiskManager(logger); var advancedRiskManager = new AdvancedRiskManager( logger, basicRiskManager, CreateAdvancedRiskConfig(weeklyLossLimit: 10000, trailingDrawdownLimit: 5000)); var sizer = new AdvancedPositionSizer(logger); var intent = CreateIntent("ES", 8, OrderSide.Buy); var context = CreateContext("ES", 50000, 0); var riskConfig = CreateRiskConfig(); var sizingConfig = CreateSizingConfig(SizingMethod.VolatilityAdjusted, 1, 10, 500); // Act var riskDecision = advancedRiskManager.ValidateOrder(intent, context, riskConfig); SizingResult sizingResult = null; if (riskDecision.Allow) { sizingResult = sizer.CalculateSize(intent, context, sizingConfig); } // Assert Assert.IsTrue(riskDecision.Allow); Assert.IsNotNull(sizingResult); Assert.AreEqual(SizingMethod.VolatilityAdjusted, sizingResult.Method); Assert.IsTrue(sizingResult.Contracts >= sizingConfig.MinContracts); Assert.IsTrue(sizingResult.Contracts <= sizingConfig.MaxContracts); } /// /// Verifies that weekly loss limit rejection blocks order flow before sizing. /// [TestMethod] public void EndToEnd_WeeklyLimitBreached_RiskRejects_AndSizingIsSkipped() { // Arrange var logger = new BasicLogger("RiskSizingIntegrationTests"); var basicRiskManager = new BasicRiskManager(logger); var advancedRiskManager = new AdvancedRiskManager( logger, basicRiskManager, CreateAdvancedRiskConfig(weeklyLossLimit: 3000, trailingDrawdownLimit: 50000)); var sizer = new AdvancedPositionSizer(logger); var intent = CreateIntent("ES", 8, OrderSide.Buy); var context = CreateContext("ES", 50000, 0); var riskConfig = CreateRiskConfig(); var sizingConfig = CreateSizingConfig(SizingMethod.OptimalF, 1, 10, 500); // Accumulate weekly losses while staying above basic emergency stop threshold. for (var i = 0; i < 6; i++) { advancedRiskManager.OnPnLUpdate(50000, -600); } // Act var riskDecision = advancedRiskManager.ValidateOrder(intent, context, riskConfig); SizingResult sizingResult = null; if (riskDecision.Allow) { sizingResult = sizer.CalculateSize(intent, context, sizingConfig); } // Assert Assert.IsFalse(riskDecision.Allow); Assert.IsTrue(riskDecision.RejectReason.Contains("Weekly loss limit breached")); Assert.IsNull(sizingResult); } /// /// Verifies that risk metrics and sizing calculations are both populated in a full pass. /// [TestMethod] public void EndToEnd_ApprovedFlow_ProducesRiskAndSizingDiagnostics() { // Arrange var logger = new BasicLogger("RiskSizingIntegrationTests"); var basicRiskManager = new BasicRiskManager(logger); var advancedRiskManager = new AdvancedRiskManager( logger, basicRiskManager, CreateAdvancedRiskConfig(weeklyLossLimit: 10000, trailingDrawdownLimit: 5000)); var sizer = new AdvancedPositionSizer(logger); var intent = CreateIntent("NQ", 10, OrderSide.Sell); var context = CreateContext("NQ", 60000, 250); var riskConfig = CreateRiskConfig(); var sizingConfig = CreateSizingConfig(SizingMethod.KellyCriterion, 1, 12, 750); sizingConfig.MethodParameters.Add("kelly_fraction", 0.5); // Act var riskDecision = advancedRiskManager.ValidateOrder(intent, context, riskConfig); var sizingResult = sizer.CalculateSize(intent, context, sizingConfig); // Assert Assert.IsTrue(riskDecision.Allow); Assert.IsNotNull(riskDecision.RiskMetrics); Assert.IsTrue(riskDecision.RiskMetrics.ContainsKey("weekly_pnl")); Assert.IsTrue(riskDecision.RiskMetrics.ContainsKey("trailing_drawdown")); Assert.IsNotNull(sizingResult); Assert.IsNotNull(sizingResult.Calculations); Assert.IsTrue(sizingResult.Calculations.Count > 0); Assert.IsTrue(sizingResult.Calculations.ContainsKey("actual_risk")); Assert.IsTrue(sizingResult.Contracts >= sizingConfig.MinContracts); Assert.IsTrue(sizingResult.Contracts <= sizingConfig.MaxContracts); } private static AdvancedRiskConfig CreateAdvancedRiskConfig(double weeklyLossLimit, double trailingDrawdownLimit) { return new AdvancedRiskConfig( weeklyLossLimit, trailingDrawdownLimit, 100000, TimeSpan.FromMinutes(30), 100000, new List()); } private static RiskConfig CreateRiskConfig() { return new RiskConfig( dailyLossLimit: 1000, maxTradeRisk: 500, maxOpenPositions: 5, emergencyFlattenEnabled: true, weeklyLossLimit: 10000, trailingDrawdownLimit: 5000, maxCrossStrategyExposure: 100000, maxCorrelatedExposure: 100000); } private static SizingConfig CreateSizingConfig(SizingMethod method, int minContracts, int maxContracts, double riskPerTrade) { return new SizingConfig( method, minContracts, maxContracts, riskPerTrade, new Dictionary()); } private static StrategyIntent CreateIntent(string symbol, int stopTicks, OrderSide side) { return new StrategyIntent( symbol, side, OrderType.Market, null, stopTicks, 2 * stopTicks, 0.8, "Integration flow test intent", new Dictionary()); } private static StrategyContext CreateContext(string symbol, double equity, double dailyPnL) { return new StrategyContext( symbol, DateTime.UtcNow, new Position(symbol, 0, 0, 0, 0, DateTime.UtcNow), new AccountInfo(equity, equity, dailyPnL, 0, DateTime.UtcNow), new MarketSession(DateTime.Today.AddHours(9.5), DateTime.Today.AddHours(16), true, "RTH"), new Dictionary()); } } }