using System; using System.Collections.Generic; using Microsoft.VisualStudio.TestTools.UnitTesting; using NT8.Core.Common.Models; using NT8.Core.Intelligence; using NT8.Core.Logging; using NT8.Core.Sizing; namespace NT8.Core.Tests.Sizing { [TestClass] public class GradeBasedSizerTests { [TestMethod] public void Constructor_NullLogger_ThrowsArgumentNullException() { Assert.ThrowsException(delegate { new GradeBasedSizer(null, new GradeFilter()); }); } [TestMethod] public void Constructor_NullGradeFilter_ThrowsArgumentNullException() { Assert.ThrowsException(delegate { new GradeBasedSizer(new BasicLogger("test"), null); }); } [TestMethod] public void CombineMultipliers_MultipliesValues() { var sizer = CreateSizer(); var result = sizer.CombineMultipliers(1.25, 0.8); Assert.AreEqual(1.0, result, 0.000001); } [TestMethod] public void ApplyConstraints_BelowMin_ReturnsMin() { var sizer = CreateSizer(); var result = sizer.ApplyConstraints(0, 1, 10); Assert.AreEqual(1, result); } [TestMethod] public void ApplyConstraints_AboveMax_ReturnsMax() { var sizer = CreateSizer(); var result = sizer.ApplyConstraints(20, 1, 10); Assert.AreEqual(10, result); } [TestMethod] public void ApplyConstraints_WithinRange_ReturnsInput() { var sizer = CreateSizer(); var result = sizer.ApplyConstraints(5, 1, 10); Assert.AreEqual(5, result); } [TestMethod] public void CalculateGradeBasedSize_RejectedGrade_ReturnsZeroContracts() { var sizer = CreateSizer(); var baseSizer = new StubPositionSizer(4, 400.0, SizingMethod.FixedDollarRisk); var intent = CreateIntent(); var context = CreateContext(); var confluence = CreateScore(TradeGrade.C, 0.6); var config = CreateSizingConfig(); var modeConfig = CreateModeConfig(RiskMode.DCP, 0.5, TradeGrade.A); var result = sizer.CalculateGradeBasedSize( intent, context, confluence, RiskMode.DCP, config, baseSizer, modeConfig); Assert.IsNotNull(result); Assert.AreEqual(0, result.Contracts); Assert.IsTrue(result.Calculations.ContainsKey("rejected")); } [TestMethod] public void CalculateGradeBasedSize_AcceptedGrade_AppliesMultipliers() { var sizer = CreateSizer(); var baseSizer = new StubPositionSizer(4, 400.0, SizingMethod.FixedDollarRisk); var intent = CreateIntent(); var context = CreateContext(); var confluence = CreateScore(TradeGrade.A, 0.85); var config = CreateSizingConfig(); var modeConfig = CreateModeConfig(RiskMode.ECP, 1.5, TradeGrade.B); var result = sizer.CalculateGradeBasedSize( intent, context, confluence, RiskMode.ECP, config, baseSizer, modeConfig); // Base contracts = 4 // Grade multiplier (ECP, A) = 1.25 // Mode multiplier = 1.5 // Raw = 7.5, floor => 7 Assert.AreEqual(7, result.Contracts); Assert.IsTrue(result.Calculations.ContainsKey("combined_multiplier")); } [TestMethod] public void CalculateGradeBasedSize_RespectsMaxContracts() { var sizer = CreateSizer(); var baseSizer = new StubPositionSizer(8, 800.0, SizingMethod.FixedDollarRisk); var intent = CreateIntent(); var context = CreateContext(); var confluence = CreateScore(TradeGrade.APlus, 0.92); var config = new SizingConfig(SizingMethod.FixedDollarRisk, 1, 10, 500.0, new Dictionary()); var modeConfig = CreateModeConfig(RiskMode.ECP, 1.5, TradeGrade.B); var result = sizer.CalculateGradeBasedSize( intent, context, confluence, RiskMode.ECP, config, baseSizer, modeConfig); // 8 * 1.5 * 1.5 = 18 -> clamp 10 Assert.AreEqual(10, result.Contracts); } [TestMethod] public void CalculateGradeBasedSize_RespectsMinContracts_WhenAccepted() { var sizer = CreateSizer(); var baseSizer = new StubPositionSizer(1, 100.0, SizingMethod.FixedDollarRisk); var intent = CreateIntent(); var context = CreateContext(); var confluence = CreateScore(TradeGrade.C, 0.61); var config = new SizingConfig(SizingMethod.FixedDollarRisk, 2, 10, 500.0, new Dictionary()); var modeConfig = CreateModeConfig(RiskMode.PCP, 1.0, TradeGrade.C); var result = sizer.CalculateGradeBasedSize( intent, context, confluence, RiskMode.PCP, config, baseSizer, modeConfig); Assert.AreEqual(2, result.Contracts); } [TestMethod] public void CalculateGradeBasedSize_NullInputs_Throw() { var sizer = CreateSizer(); var baseSizer = new StubPositionSizer(1, 100.0, SizingMethod.FixedDollarRisk); var intent = CreateIntent(); var context = CreateContext(); var confluence = CreateScore(TradeGrade.A, 0.8); var config = CreateSizingConfig(); var modeConfig = CreateModeConfig(RiskMode.PCP, 1.0, TradeGrade.C); Assert.ThrowsException(delegate { sizer.CalculateGradeBasedSize(null, context, confluence, RiskMode.PCP, config, baseSizer, modeConfig); }); Assert.ThrowsException(delegate { sizer.CalculateGradeBasedSize(intent, null, confluence, RiskMode.PCP, config, baseSizer, modeConfig); }); Assert.ThrowsException(delegate { sizer.CalculateGradeBasedSize(intent, context, null, RiskMode.PCP, config, baseSizer, modeConfig); }); } private static GradeBasedSizer CreateSizer() { return new GradeBasedSizer(new BasicLogger("GradeBasedSizerTests"), new GradeFilter()); } private static StrategyIntent CreateIntent() { return new StrategyIntent( "ES", OrderSide.Buy, OrderType.Market, null, 8, 16, 0.8, "Test intent", new Dictionary()); } private static StrategyContext CreateContext() { return new StrategyContext( "ES", DateTime.UtcNow, new Position("ES", 0, 0, 0, 0, DateTime.UtcNow), new AccountInfo(100000, 100000, 0, 0, DateTime.UtcNow), new MarketSession(DateTime.Today.AddHours(9.5), DateTime.Today.AddHours(16), true, "RTH"), new Dictionary()); } private static ConfluenceScore CreateScore(TradeGrade grade, double weighted) { var factors = new List(); factors.Add(new ConfluenceFactor(FactorType.Setup, "Setup", weighted, 1.0, "test", new Dictionary())); return new ConfluenceScore( weighted, weighted, grade, factors, DateTime.UtcNow, new Dictionary()); } private static SizingConfig CreateSizingConfig() { return new SizingConfig(SizingMethod.FixedDollarRisk, 1, 10, 500.0, new Dictionary()); } private static RiskModeConfig CreateModeConfig(RiskMode mode, double sizeMultiplier, TradeGrade minGrade) { return new RiskModeConfig(mode, sizeMultiplier, minGrade, 1000.0, 3, false, new Dictionary()); } private class StubPositionSizer : IPositionSizer { private readonly int _contracts; private readonly double _risk; private readonly SizingMethod _method; public StubPositionSizer(int contracts, double risk, SizingMethod method) { _contracts = contracts; _risk = risk; _method = method; } public SizingResult CalculateSize(StrategyIntent intent, StrategyContext context, SizingConfig config) { return new SizingResult(_contracts, _risk, _method, new Dictionary()); } public SizingMetadata GetMetadata() { return new SizingMetadata("Stub", "Stub sizer", new List()); } } } }