# **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;

/// <summary>
/// Basic position sizer with fixed contracts and fixed dollar risk methods
/// Handles contract size calculations with proper rounding and clamping
/// </summary>
public class BasicPositionSizer : IPositionSizer
{
    private readonly ILogger<BasicPositionSizer> _logger;
    
    public BasicPositionSizer(ILogger<BasicPositionSizer> 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<int>(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<T>(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<string> { "method", "risk_per_trade", "min_contracts", "max_contracts" }
        );
    }
    
    /// <summary>
    /// Validate sizing configuration parameters
    /// </summary>
    public static bool ValidateConfig(SizingConfig config, out List<string> errors)
    {
        errors = new List<string>();
        
        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<int>(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;
    }
    
    /// <summary>
    /// Get supported symbols with their tick values
    /// </summary>
    public static Dictionary<string, double> GetSupportedSymbols()
    {
        return new Dictionary<string, double>
        {
            ["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<BasicPositionSizer> _logger;
    private readonly BasicPositionSizer _sizer;
    
    public BasicPositionSizerTests()
    {
        _logger = NullLogger<BasicPositionSizer>.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<ArgumentNullException>(() => _sizer.CalculateSize(null, context, config));
        Assert.Throws<ArgumentNullException>(() => _sizer.CalculateSize(intent, null, config));
        Assert.Throws<ArgumentNullException>(() => _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<NotSupportedException>(() => _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<SizingResult>();
        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.**