# **Position Sizing Package** ## **IMPLEMENTATION INSTRUCTIONS** Implement the BasicPositionSizer with fixed contracts and fixed dollar risk methods. This component determines how many contracts to trade based on the strategy intent and risk parameters. ### **File 1: `src/NT8.Core/Sizing/BasicPositionSizer.cs`** ```csharp using NT8.Core.Common.Models; using Microsoft.Extensions.Logging; namespace NT8.Core.Sizing; /// /// Basic position sizer with fixed contracts and fixed dollar risk methods /// Handles contract size calculations with proper rounding and clamping /// public class BasicPositionSizer : IPositionSizer { private readonly ILogger _logger; public BasicPositionSizer(ILogger logger) { _logger = logger ?? throw new ArgumentNullException(nameof(logger)); } public SizingResult CalculateSize(StrategyIntent intent, StrategyContext context, SizingConfig config) { if (intent == null) throw new ArgumentNullException(nameof(intent)); if (context == null) throw new ArgumentNullException(nameof(context)); if (config == null) throw new ArgumentNullException(nameof(config)); // Validate intent is suitable for sizing if (!intent.IsValid()) { _logger.LogWarning("Invalid strategy intent provided for sizing: {Intent}", intent); return new SizingResult(0, 0, config.Method, new() { ["error"] = "Invalid intent" }); } return config.Method switch { SizingMethod.FixedContracts => CalculateFixedContracts(intent, context, config), SizingMethod.FixedDollarRisk => CalculateFixedRisk(intent, context, config), _ => throw new NotSupportedException($"Sizing method {config.Method} not supported in Phase 0") }; } private SizingResult CalculateFixedContracts(StrategyIntent intent, StrategyContext context, SizingConfig config) { // Get target contracts from configuration var targetContracts = GetParameterValue(config, "contracts", 1); // Apply min/max clamping var contracts = Math.Max(config.MinContracts, Math.Min(config.MaxContracts, targetContracts)); // Calculate actual risk amount var tickValue = GetTickValue(intent.Symbol); var riskAmount = contracts * intent.StopTicks * tickValue; _logger.LogDebug("Fixed contracts sizing: {Symbol} {TargetContracts}→{ActualContracts} contracts, ${Risk:F2} risk", intent.Symbol, targetContracts, contracts, riskAmount); return new SizingResult( Contracts: contracts, RiskAmount: riskAmount, Method: SizingMethod.FixedContracts, Calculations: new() { ["target_contracts"] = targetContracts, ["clamped_contracts"] = contracts, ["stop_ticks"] = intent.StopTicks, ["tick_value"] = tickValue, ["risk_amount"] = riskAmount, ["min_contracts"] = config.MinContracts, ["max_contracts"] = config.MaxContracts } ); } private SizingResult CalculateFixedRisk(StrategyIntent intent, StrategyContext context, SizingConfig config) { var tickValue = GetTickValue(intent.Symbol); // Validate stop ticks if (intent.StopTicks <= 0) { _logger.LogWarning("Invalid stop ticks {StopTicks} for fixed risk sizing on {Symbol}", intent.StopTicks, intent.Symbol); return new SizingResult(0, 0, SizingMethod.FixedDollarRisk, new() { ["error"] = "Invalid stop ticks", ["stop_ticks"] = intent.StopTicks }); } // Calculate optimal contracts for target risk var targetRisk = config.RiskPerTrade; var riskPerContract = intent.StopTicks * tickValue; var optimalContracts = targetRisk / riskPerContract; // Round down to whole contracts (conservative approach) var contracts = (int)Math.Floor(optimalContracts); // Apply min/max clamping contracts = Math.Max(config.MinContracts, Math.Min(config.MaxContracts, contracts)); // Calculate actual risk with final contract count var actualRisk = contracts * riskPerContract; _logger.LogDebug("Fixed risk sizing: {Symbol} ${TargetRisk:F2}→{OptimalContracts:F2}→{ActualContracts} contracts, ${ActualRisk:F2} actual risk", intent.Symbol, targetRisk, optimalContracts, contracts, actualRisk); return new SizingResult( Contracts: contracts, RiskAmount: actualRisk, Method: SizingMethod.FixedDollarRisk, Calculations: new() { ["target_risk"] = targetRisk, ["stop_ticks"] = intent.StopTicks, ["tick_value"] = tickValue, ["risk_per_contract"] = riskPerContract, ["optimal_contracts"] = optimalContracts, ["clamped_contracts"] = contracts, ["actual_risk"] = actualRisk, ["min_contracts"] = config.MinContracts, ["max_contracts"] = config.MaxContracts } ); } private static T GetParameterValue(SizingConfig config, string key, T defaultValue) { if (config.MethodParameters.TryGetValue(key, out var value)) { try { return (T)Convert.ChangeType(value, typeof(T)); } catch { // If conversion fails, return default return defaultValue; } } return defaultValue; } private static double GetTickValue(string symbol) { // Static tick values for Phase 0 - will be configurable in Phase 1 return symbol switch { "ES" => 12.50, // E-mini S&P 500 "MES" => 1.25, // Micro E-mini S&P 500 "NQ" => 5.00, // E-mini NASDAQ-100 "MNQ" => 0.50, // Micro E-mini NASDAQ-100 "CL" => 10.00, // Crude Oil "GC" => 10.00, // Gold "6E" => 12.50, // Euro FX "6A" => 10.00, // Australian Dollar _ => 12.50 // Default to ES value }; } public SizingMetadata GetMetadata() { return new SizingMetadata( Name: "Basic Position Sizer", Description: "Fixed contracts or fixed dollar risk sizing with contract clamping", RequiredParameters: new List { "method", "risk_per_trade", "min_contracts", "max_contracts" } ); } /// /// Validate sizing configuration parameters /// public static bool ValidateConfig(SizingConfig config, out List errors) { errors = new List(); if (config.MinContracts < 0) errors.Add("MinContracts must be >= 0"); if (config.MaxContracts <= 0) errors.Add("MaxContracts must be > 0"); if (config.MinContracts > config.MaxContracts) errors.Add("MinContracts must be <= MaxContracts"); if (config.RiskPerTrade <= 0) errors.Add("RiskPerTrade must be > 0"); // Method-specific validation switch (config.Method) { case SizingMethod.FixedContracts: if (!config.MethodParameters.ContainsKey("contracts")) errors.Add("FixedContracts method requires 'contracts' parameter"); else if (GetParameterValue(config, "contracts", 0) <= 0) errors.Add("Fixed contracts parameter must be > 0"); break; case SizingMethod.FixedDollarRisk: // No additional parameters required for fixed dollar risk break; default: errors.Add($"Unsupported sizing method: {config.Method}"); break; } return errors.Count == 0; } /// /// Get supported symbols with their tick values /// public static Dictionary GetSupportedSymbols() { return new Dictionary { ["ES"] = 12.50, ["MES"] = 1.25, ["NQ"] = 5.00, ["MNQ"] = 0.50, ["CL"] = 10.00, ["GC"] = 10.00, ["6E"] = 12.50, ["6A"] = 10.00 }; } } ``` ## **COMPREHENSIVE TEST SUITE** ### **File 2: `tests/NT8.Core.Tests/Sizing/BasicPositionSizerTests.cs`** ```csharp using NT8.Core.Sizing; using NT8.Core.Common.Models; using NT8.Core.Tests.TestHelpers; using Microsoft.Extensions.Logging; using Microsoft.Extensions.Logging.Abstractions; using FluentAssertions; using Xunit; namespace NT8.Core.Tests.Sizing; public class BasicPositionSizerTests : IDisposable { private readonly ILogger _logger; private readonly BasicPositionSizer _sizer; public BasicPositionSizerTests() { _logger = NullLogger.Instance; _sizer = new BasicPositionSizer(_logger); } [Fact] public void CalculateSize_FixedContracts_ShouldReturnCorrectSize() { // Arrange var intent = TestDataBuilder.CreateValidIntent(symbol: "ES", stopTicks: 8); var context = TestDataBuilder.CreateTestContext(); var config = new SizingConfig( Method: SizingMethod.FixedContracts, MinContracts: 1, MaxContracts: 10, RiskPerTrade: 200, MethodParameters: new() { ["contracts"] = 3 } ); // Act var result = _sizer.CalculateSize(intent, context, config); // Assert result.Contracts.Should().Be(3); result.Method.Should().Be(SizingMethod.FixedContracts); result.RiskAmount.Should().Be(300.0); // 3 contracts * 8 ticks * $12.50 result.Calculations.Should().ContainKey("target_contracts"); result.Calculations.Should().ContainKey("clamped_contracts"); result.Calculations["target_contracts"].Should().Be(3); result.Calculations["clamped_contracts"].Should().Be(3); } [Fact] public void CalculateSize_FixedContractsWithClamping_ShouldApplyLimits() { // Arrange var intent = TestDataBuilder.CreateValidIntent(symbol: "ES", stopTicks: 10); var context = TestDataBuilder.CreateTestContext(); var config = new SizingConfig( Method: SizingMethod.FixedContracts, MinContracts: 2, MaxContracts: 5, RiskPerTrade: 200, MethodParameters: new() { ["contracts"] = 8 } // Exceeds max ); // Act var result = _sizer.CalculateSize(intent, context, config); // Assert result.Contracts.Should().Be(5); // Clamped to max result.Calculations["target_contracts"].Should().Be(8); result.Calculations["clamped_contracts"].Should().Be(5); result.RiskAmount.Should().Be(625.0); // 5 * 10 * $12.50 } [Fact] public void CalculateSize_FixedDollarRisk_ShouldCalculateCorrectly() { // Arrange var intent = TestDataBuilder.CreateValidIntent(symbol: "ES", stopTicks: 10); var context = TestDataBuilder.CreateTestContext(); var config = new SizingConfig( Method: SizingMethod.FixedDollarRisk, MinContracts: 1, MaxContracts: 10, RiskPerTrade: 250.0, // Target $250 risk MethodParameters: new() ); // Act var result = _sizer.CalculateSize(intent, context, config); // Assert // $250 target / (10 ticks * $12.50) = 2 contracts result.Contracts.Should().Be(2); result.Method.Should().Be(SizingMethod.FixedDollarRisk); result.RiskAmount.Should().Be(250.0); // 2 * 10 * $12.50 result.Calculations["target_risk"].Should().Be(250.0); result.Calculations["optimal_contracts"].Should().Be(2.0); result.Calculations["actual_risk"].Should().Be(250.0); } [Theory] [InlineData("ES", 8, 200.0, 2, 200.0)] // ES: $200 / (8 * $12.50) = 2.0 → 2 contracts [InlineData("MES", 8, 20.0, 2, 20.0)] // MES: $20 / (8 * $1.25) = 2.0 → 2 contracts [InlineData("NQ", 10, 100.0, 2, 100.0)] // NQ: $100 / (10 * $5.00) = 2.0 → 2 contracts [InlineData("CL", 5, 75.0, 1, 50.0)] // CL: $75 / (5 * $10.00) = 1.5 → 1 contract (floor) public void CalculateSize_FixedRiskVariousSymbols_ShouldCalculateCorrectly( string symbol, int stopTicks, double targetRisk, int expectedContracts, double expectedActualRisk) { // Arrange var intent = TestDataBuilder.CreateValidIntent(symbol: symbol, stopTicks: stopTicks); var context = TestDataBuilder.CreateTestContext(); var config = new SizingConfig( Method: SizingMethod.FixedDollarRisk, MinContracts: 1, MaxContracts: 10, RiskPerTrade: targetRisk, MethodParameters: new() ); // Act var result = _sizer.CalculateSize(intent, context, config); // Assert result.Contracts.Should().Be(expectedContracts); result.RiskAmount.Should().Be(expectedActualRisk); result.Method.Should().Be(SizingMethod.FixedDollarRisk); } [Fact] public void CalculateSize_FixedRiskWithMinClamp_ShouldApplyMinimum() { // Arrange - Very small risk that would calculate to 0 contracts var intent = TestDataBuilder.CreateValidIntent(symbol: "ES", stopTicks: 20); var context = TestDataBuilder.CreateTestContext(); var config = new SizingConfig( Method: SizingMethod.FixedDollarRisk, MinContracts: 2, // Force minimum MaxContracts: 10, RiskPerTrade: 100.0, // Only enough for 0.4 contracts MethodParameters: new() ); // Act var result = _sizer.CalculateSize(intent, context, config); // Assert result.Contracts.Should().Be(2); // Applied minimum result.RiskAmount.Should().Be(500.0); // 2 * 20 * $12.50 result.Calculations["optimal_contracts"].Should().Be(0.4); result.Calculations["clamped_contracts"].Should().Be(2); } [Fact] public void CalculateSize_FixedRiskWithMaxClamp_ShouldApplyMaximum() { // Arrange - Large risk that would calculate to many contracts var intent = TestDataBuilder.CreateValidIntent(symbol: "ES", stopTicks: 5); var context = TestDataBuilder.CreateTestContext(); var config = new SizingConfig( Method: SizingMethod.FixedDollarRisk, MinContracts: 1, MaxContracts: 3, // Limit maximum RiskPerTrade: 1000.0, // Enough for 16 contracts MethodParameters: new() ); // Act var result = _sizer.CalculateSize(intent, context, config); // Assert result.Contracts.Should().Be(3); // Applied maximum result.RiskAmount.Should().Be(187.5); // 3 * 5 * $12.50 result.Calculations["optimal_contracts"].Should().Be(16.0); result.Calculations["clamped_contracts"].Should().Be(3); } [Fact] public void CalculateSize_ZeroStopTicks_ShouldReturnZeroContracts() { // Arrange var intent = TestDataBuilder.CreateValidIntent(stopTicks: 0); // Invalid var context = TestDataBuilder.CreateTestContext(); var config = TestDataBuilder.CreateTestSizingConfig(SizingMethod.FixedDollarRisk); // Act var result = _sizer.CalculateSize(intent, context, config); // Assert result.Contracts.Should().Be(0); result.RiskAmount.Should().Be(0); result.Calculations.Should().ContainKey("error"); } [Fact] public void CalculateSize_InvalidIntent_ShouldReturnZeroContracts() { // Arrange - Create invalid intent var intent = new StrategyIntent( Symbol: "", // Invalid empty symbol Side: OrderSide.Buy, EntryType: OrderType.Market, LimitPrice: null, StopTicks: 10, TargetTicks: 20, Confidence: 0.8, Reason: "Test", Metadata: new() ); var context = TestDataBuilder.CreateTestContext(); var config = TestDataBuilder.CreateTestSizingConfig(); // Act var result = _sizer.CalculateSize(intent, context, config); // Assert result.Contracts.Should().Be(0); result.RiskAmount.Should().Be(0); result.Calculations.Should().ContainKey("error"); } [Fact] public void CalculateSize_WithNullParameters_ShouldThrow() { // Arrange var intent = TestDataBuilder.CreateValidIntent(); var context = TestDataBuilder.CreateTestContext(); var config = TestDataBuilder.CreateTestSizingConfig(); // Act & Assert Assert.Throws(() => _sizer.CalculateSize(null, context, config)); Assert.Throws(() => _sizer.CalculateSize(intent, null, config)); Assert.Throws(() => _sizer.CalculateSize(intent, context, null)); } [Fact] public void CalculateSize_UnsupportedMethod_ShouldThrow() { // Arrange var intent = TestDataBuilder.CreateValidIntent(); var context = TestDataBuilder.CreateTestContext(); var config = new SizingConfig( Method: SizingMethod.OptimalF, // Not supported in Phase 0 MinContracts: 1, MaxContracts: 10, RiskPerTrade: 200, MethodParameters: new() ); // Act & Assert Assert.Throws(() => _sizer.CalculateSize(intent, context, config)); } [Fact] public void GetMetadata_ShouldReturnCorrectInformation() { // Act var metadata = _sizer.GetMetadata(); // Assert metadata.Name.Should().Be("Basic Position Sizer"); metadata.Description.Should().Contain("Fixed contracts"); metadata.Description.Should().Contain("fixed dollar risk"); metadata.RequiredParameters.Should().Contain("method"); metadata.RequiredParameters.Should().Contain("risk_per_trade"); } [Fact] public void ValidateConfig_ValidConfiguration_ShouldReturnTrue() { // Arrange var config = new SizingConfig( Method: SizingMethod.FixedContracts, MinContracts: 1, MaxContracts: 10, RiskPerTrade: 200, MethodParameters: new() { ["contracts"] = 2 } ); // Act var isValid = BasicPositionSizer.ValidateConfig(config, out var errors); // Assert isValid.Should().BeTrue(); errors.Should().BeEmpty(); } [Fact] public void ValidateConfig_InvalidConfiguration_ShouldReturnErrors() { // Arrange var config = new SizingConfig( Method: SizingMethod.FixedContracts, MinContracts: 5, MaxContracts: 2, // Invalid: min > max RiskPerTrade: -100, // Invalid: negative risk MethodParameters: new() // Missing required parameter ); // Act var isValid = BasicPositionSizer.ValidateConfig(config, out var errors); // Assert isValid.Should().BeFalse(); errors.Should().Contain("MinContracts must be <= MaxContracts"); errors.Should().Contain("RiskPerTrade must be > 0"); errors.Should().Contain("FixedContracts method requires 'contracts' parameter"); } [Fact] public void GetSupportedSymbols_ShouldReturnAllSymbolsWithTickValues() { // Act var symbols = BasicPositionSizer.GetSupportedSymbols(); // Assert symbols.Should().ContainKey("ES").WhoseValue.Should().Be(12.50); symbols.Should().ContainKey("MES").WhoseValue.Should().Be(1.25); symbols.Should().ContainKey("NQ").WhoseValue.Should().Be(5.00); symbols.Should().ContainKey("MNQ").WhoseValue.Should().Be(0.50); symbols.Should().ContainKey("CL").WhoseValue.Should().Be(10.00); symbols.Should().ContainKey("GC").WhoseValue.Should().Be(10.00); symbols.Count.Should().BeGreaterOrEqualTo(6); } [Fact] public void CalculateSize_ConsistentResults_ShouldBeDeterministic() { // Arrange var intent = TestDataBuilder.CreateValidIntent(symbol: "ES", stopTicks: 12); var context = TestDataBuilder.CreateTestContext(); var config = new SizingConfig( Method: SizingMethod.FixedDollarRisk, MinContracts: 1, MaxContracts: 10, RiskPerTrade: 300, MethodParameters: new() ); // Act - Calculate multiple times var results = new List(); for (int i = 0; i < 5; i++) { results.Add(_sizer.CalculateSize(intent, context, config)); } // Assert - All results should be identical var firstResult = results[0]; foreach (var result in results.Skip(1)) { result.Contracts.Should().Be(firstResult.Contracts); result.RiskAmount.Should().Be(firstResult.RiskAmount); result.Method.Should().Be(firstResult.Method); } } public void Dispose() { // Cleanup if needed } } ``` ## **CALCULATION EXAMPLES TEST DATA** ### **File 3: `test-data/calculation-examples.json`** ```json { "description": "Position sizing calculation examples for validation", "test_cases": [ { "name": "ES Fixed Contracts", "symbol": "ES", "stop_ticks": 8, "method": "FixedContracts", "method_parameters": { "contracts": 2 }, "min_contracts": 1, "max_contracts": 10, "risk_per_trade": 200, "expected_contracts": 2, "expected_risk": 200.0, "calculation": "2 contracts * 8 ticks * $12.50 = $200" }, { "name": "ES Fixed Dollar Risk", "symbol": "ES", "stop_ticks": 10, "method": "FixedDollarRisk", "method_parameters": {}, "min_contracts": 1, "max_contracts": 10, "risk_per_trade": 250, "expected_contracts": 2, "expected_risk": 250.0, "calculation": "$250 / (10 ticks * $12.50) = 2.0 contracts" }, { "name": "MES Fixed Dollar Risk", "symbol": "MES", "stop_ticks": 16, "method": "FixedDollarRisk", "method_parameters": {}, "min_contracts": 1, "max_contracts": 50, "risk_per_trade": 100, "expected_contracts": 5, "expected_risk": 100.0, "calculation": "$100 / (16 ticks * $1.25) = 5.0 contracts" }, { "name": "NQ Fixed Risk with Rounding", "symbol": "NQ", "stop_ticks": 12, "method": "FixedDollarRisk", "method_parameters": {}, "min_contracts": 1, "max_contracts": 10, "risk_per_trade": 175, "expected_contracts": 2, "expected_risk": 120.0, "calculation": "$175 / (12 ticks * $5.00) = 2.916... → 2 contracts (floor)" }, { "name": "CL with Min Clamp", "symbol": "CL", "stop_ticks": 20, "method": "FixedDollarRisk", "method_parameters": {}, "min_contracts": 3, "max_contracts": 10, "risk_per_trade": 150, "expected_contracts": 3, "expected_risk": 600.0, "calculation": "$150 / (20 * $10) = 0.75 → clamped to min 3 contracts" }, { "name": "GC with Max Clamp", "symbol": "GC", "stop_ticks": 5, "method": "FixedDollarRisk", "method_parameters": {}, "min_contracts": 1, "max_contracts": 2, "risk_per_trade": 500, "expected_contracts": 2, "expected_risk": 100.0, "calculation": "$500 / (5 * $10) = 10 → clamped to max 2 contracts" } ] } ``` ## **VALIDATION SCRIPT** ### **File 4: `tools/validate-sizing-implementation.ps1`** ```powershell # Position Sizing Validation Script Write-Host "📏 Validating Position Sizing Implementation..." -ForegroundColor Yellow # Build check Write-Host "📦 Building solution..." -ForegroundColor Blue $buildResult = dotnet build --configuration Release --verbosity quiet if ($LASTEXITCODE -ne 0) { Write-Host "❌ Build failed" -ForegroundColor Red exit 1 } # Test execution Write-Host "🧪 Running position sizing tests..." -ForegroundColor Blue $testResult = dotnet test tests/NT8.Core.Tests/NT8.Core.Tests.csproj --filter "Category=Sizing|FullyQualifiedName~Sizing" --configuration Release --verbosity quiet if ($LASTEXITCODE -ne 0) { Write-Host "❌ Position sizing tests failed" -ForegroundColor Red exit 1 } # Validate specific calculation examples Write-Host "🔢 Validating calculation examples..." -ForegroundColor Blue $calculationTests = @( "BasicPositionSizerTests.CalculateSize_FixedContracts_ShouldReturnCorrectSize", "BasicPositionSizerTests.CalculateSize_FixedDollarRisk_ShouldCalculateCorrectly", "BasicPositionSizerTests.CalculateSize_FixedRiskVariousSymbols_ShouldCalculateCorrectly" ) foreach ($test in $calculationTests) { $result = dotnet test --filter "FullyQualifiedName~$test" --configuration Release --verbosity quiet if ($LASTEXITCODE -eq 0) { Write-Host " ✅ $test" -ForegroundColor Green } else { Write-Host " ❌ $test" -ForegroundColor Red exit 1 } } # Test configuration validation Write-Host "⚙️ Testing configuration validation..." -ForegroundColor Blue $configTests = @( "BasicPositionSizerTests.ValidateConfig_ValidConfiguration_ShouldReturnTrue", "BasicPositionSizerTests.ValidateConfig_InvalidConfiguration_ShouldReturnErrors" ) foreach ($test in $configTests) { $result = dotnet test --filter "FullyQualifiedName~$test" --configuration Release --verbosity quiet if ($LASTEXITCODE -eq 0) { Write-Host " ✅ $test" -ForegroundColor Green } else { Write-Host " ❌ $test" -ForegroundColor Red exit 1 } } Write-Host "🎉 Position sizing validation completed successfully!" -ForegroundColor Green # Summary Write-Host "" Write-Host "📊 Position Sizing Implementation Summary:" -ForegroundColor Cyan Write-Host " ✅ Fixed contracts sizing method" -ForegroundColor Green Write-Host " ✅ Fixed dollar risk sizing method" -ForegroundColor Green Write-Host " ✅ Contract clamping (min/max limits)" -ForegroundColor Green Write-Host " ✅ Multi-symbol support with correct tick values" -ForegroundColor Green Write-Host " ✅ Comprehensive error handling" -ForegroundColor Green Write-Host " ✅ Configuration validation" -ForegroundColor Green ``` ## **SUCCESS CRITERIA** ✅ **BasicPositionSizer.cs implemented exactly as specified** ✅ **Fixed contracts sizing method working correctly** ✅ **Fixed dollar risk sizing method with proper rounding** ✅ **Contract clamping applied (min/max limits)** ✅ **Multi-symbol support with accurate tick values** ✅ **Comprehensive test suite with >90% coverage** ✅ **All calculation examples produce exact expected results** ✅ **Configuration validation prevents invalid setups** ✅ **Error handling for edge cases (zero stops, invalid intents)** ## **CRITICAL REQUIREMENTS** 1. **Exact Calculations**: Must match calculation examples precisely 2. **Conservative Rounding**: Always round down (floor) for contract quantities 3. **Proper Clamping**: Apply min/max contract limits after calculation 4. **Symbol Support**: Support all specified symbols with correct tick values 5. **Error Handling**: Handle invalid inputs gracefully 6. **Deterministic**: Same inputs must always produce same outputs **Once this is complete, position sizing is fully functional and ready for integration with the strategy framework.**