using Microsoft.VisualStudio.TestTools.UnitTesting; using NT8.Core.Common.Models; using NT8.Core.Logging; using NT8.Core.Sizing; using System; using System.Collections.Generic; namespace NT8.Core.Tests.Sizing { [TestClass] public class AdvancedPositionSizerTests { private AdvancedPositionSizer _sizer; [TestInitialize] public void TestInitialize() { _sizer = new AdvancedPositionSizer(new BasicLogger("AdvancedPositionSizerTests")); } [TestMethod] public void CalculateSize_OptimalF_NoHistory_UsesFallbackMethod() { // Arrange var intent = CreateValidIntent(); var context = CreateContext(); var config = CreateConfig(SizingMethod.OptimalF); // Act var result = _sizer.CalculateSize(intent, context, config); // Assert Assert.IsNotNull(result); Assert.AreEqual(SizingMethod.FixedDollarRisk, result.Method); Assert.IsTrue(result.Contracts >= config.MinContracts); Assert.IsTrue(result.Contracts <= config.MaxContracts); } [TestMethod] public void CalculateSize_KellyCriterion_WithFraction_ReturnsValidContracts() { // Arrange var intent = CreateValidIntent(); var context = CreateContext(); var config = CreateConfig(SizingMethod.KellyCriterion); config.MethodParameters.Add("kelly_fraction", 0.5); // Act var result = _sizer.CalculateSize(intent, context, config); // Assert Assert.IsNotNull(result); Assert.IsTrue(result.Contracts >= config.MinContracts); Assert.IsTrue(result.Contracts <= config.MaxContracts); Assert.IsTrue(result.RiskAmount >= 0); } [TestMethod] public void CalculateSize_VolatilityAdjusted_ReturnsValidContracts() { // Arrange var intent = CreateValidIntent(symbol: "NQ", stopTicks: 10); var context = CreateContext(symbol: "NQ"); var config = CreateConfig(SizingMethod.VolatilityAdjusted); // Act var result = _sizer.CalculateSize(intent, context, config); // Assert Assert.IsNotNull(result); Assert.AreEqual(SizingMethod.VolatilityAdjusted, result.Method); Assert.IsTrue(result.Contracts >= config.MinContracts); Assert.IsTrue(result.Contracts <= config.MaxContracts); } [TestMethod] public void CalculateSize_InvalidIntent_ReturnsZeroContracts() { // Arrange var invalidIntent = new StrategyIntent( symbol: "ES", side: OrderSide.Flat, entryType: OrderType.Market, limitPrice: null, stopTicks: 0, targetTicks: null, confidence: 0.8, reason: "Invalid for test", metadata: new Dictionary()); var context = CreateContext(); var config = CreateConfig(SizingMethod.OptimalF); // Act var result = _sizer.CalculateSize(invalidIntent, context, config); // Assert Assert.IsNotNull(result); Assert.AreEqual(0, result.Contracts); } [TestMethod] public void ValidateConfig_InvalidValues_ReturnsFalseAndErrors() { // Arrange var config = new SizingConfig( method: SizingMethod.KellyCriterion, minContracts: 5, maxContracts: 1, riskPerTrade: -1, methodParameters: new Dictionary()); // Act List errors; var isValid = AdvancedPositionSizer.ValidateConfig(config, out errors); // Assert Assert.IsFalse(isValid); Assert.IsNotNull(errors); Assert.IsTrue(errors.Count > 0); } [TestMethod] public void GetMetadata_ReturnsExpectedFields() { // Act var metadata = _sizer.GetMetadata(); // Assert Assert.IsNotNull(metadata); Assert.AreEqual("Advanced Position Sizer", metadata.Name); Assert.IsTrue(metadata.RequiredParameters.Contains("method")); Assert.IsTrue(metadata.RequiredParameters.Contains("risk_per_trade")); } private static StrategyIntent CreateValidIntent(string symbol = "ES", int stopTicks = 8) { return new StrategyIntent( symbol: symbol, side: OrderSide.Buy, entryType: OrderType.Market, limitPrice: null, stopTicks: stopTicks, targetTicks: 16, confidence: 0.8, reason: "Test intent", metadata: new Dictionary()); } private static StrategyContext CreateContext(string symbol = "ES") { return new StrategyContext( symbol: symbol, currentTime: DateTime.UtcNow, currentPosition: new Position(symbol, 0, 0, 0, 0, DateTime.UtcNow), account: new AccountInfo(50000, 50000, 0, 0, DateTime.UtcNow), session: new MarketSession(DateTime.Today.AddHours(9.5), DateTime.Today.AddHours(16), true, "RTH"), customData: new Dictionary()); } private static SizingConfig CreateConfig(SizingMethod method) { return new SizingConfig( method: method, minContracts: 1, maxContracts: 10, riskPerTrade: 500, methodParameters: new Dictionary()); } } }