Phase 0 completion: NT8 SDK core framework with risk management and position sizing
Some checks failed
Build and Test / build (push) Has been cancelled

This commit is contained in:
Billy Valentine
2025-09-09 17:06:37 -04:00
parent 97e5050d3e
commit 92f3732b3d
109 changed files with 38593 additions and 380 deletions

View File

@@ -0,0 +1,785 @@
# **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.**