Optimize AdvancedPositionSizer performance with object pooling and metrics tracking. Added performance tests.
Some checks failed
Build and Test / build (push) Has been cancelled
Some checks failed
Build and Test / build (push) Has been cancelled
This commit is contained in:
@@ -0,0 +1,299 @@
|
||||
using Microsoft.VisualStudio.TestTools.UnitTesting;
|
||||
using NT8.Core.Common.Models;
|
||||
using NT8.Core.Logging;
|
||||
using NT8.Core.Sizing;
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Diagnostics;
|
||||
using System.Threading.Tasks;
|
||||
|
||||
namespace NT8.Core.Tests.Sizing
|
||||
{
|
||||
[TestClass]
|
||||
public class AdvancedPositionSizerPerformanceTests
|
||||
{
|
||||
private TestLogger _logger;
|
||||
private AdvancedPositionSizer _positionSizer;
|
||||
|
||||
[TestInitialize]
|
||||
public void TestInitialize()
|
||||
{
|
||||
_logger = new TestLogger();
|
||||
_positionSizer = new AdvancedPositionSizer(_logger);
|
||||
}
|
||||
|
||||
[TestMethod]
|
||||
public void AdvancedPositionSizer_Performance_MetricsRecording()
|
||||
{
|
||||
// Arrange
|
||||
var intent = CreateValidIntent();
|
||||
var context = CreateTestContext();
|
||||
var config = CreateTestSizingConfig(SizingMethod.OptimalF);
|
||||
|
||||
// Act
|
||||
var stopwatch = Stopwatch.StartNew();
|
||||
var result = _positionSizer.CalculateSize(intent, context, config);
|
||||
stopwatch.Stop();
|
||||
|
||||
// Assert
|
||||
Assert.IsNotNull(result);
|
||||
Assert.IsTrue(result.Contracts >= 0);
|
||||
|
||||
// Check that metrics were recorded
|
||||
var metricsSnapshot = _positionSizer.GetMetricsSnapshot();
|
||||
Assert.IsNotNull(metricsSnapshot);
|
||||
Assert.IsTrue(metricsSnapshot.TotalOperations >= 1);
|
||||
Assert.IsTrue(metricsSnapshot.OptimalFOperations >= 1);
|
||||
Assert.IsTrue(metricsSnapshot.TotalProcessingTimeMs >= 0);
|
||||
Assert.IsTrue(metricsSnapshot.AverageProcessingTimeMs >= 0);
|
||||
}
|
||||
|
||||
[TestMethod]
|
||||
public void AdvancedPositionSizer_Performance_ObjectPooling()
|
||||
{
|
||||
// Arrange
|
||||
var intent = CreateValidIntent();
|
||||
var context = CreateTestContext();
|
||||
var config = CreateTestSizingConfig(SizingMethod.KellyCriterion);
|
||||
|
||||
// Act & Assert
|
||||
// Run multiple calculations to test object pooling
|
||||
for (int i = 0; i < 100; i++)
|
||||
{
|
||||
var result = _positionSizer.CalculateSize(intent, context, config);
|
||||
Assert.IsNotNull(result);
|
||||
Assert.IsTrue(result.Contracts >= 0);
|
||||
}
|
||||
|
||||
// Check that we still have reasonable performance
|
||||
var metricsSnapshot = _positionSizer.GetMetricsSnapshot();
|
||||
Assert.IsTrue(metricsSnapshot.TotalOperations >= 100);
|
||||
Assert.IsTrue(metricsSnapshot.AverageProcessingTimeMs < 100); // Should be fast with pooling
|
||||
}
|
||||
|
||||
[TestMethod]
|
||||
public async Task AdvancedPositionSizer_Performance_ConcurrentAccess()
|
||||
{
|
||||
// Arrange
|
||||
var tasks = new List<Task<SizingResult>>();
|
||||
var intent = CreateValidIntent();
|
||||
var context = CreateTestContext();
|
||||
var config = CreateTestSizingConfig(SizingMethod.VolatilityAdjusted);
|
||||
|
||||
// Act
|
||||
var stopwatch = Stopwatch.StartNew();
|
||||
for (int i = 0; i < 50; i++)
|
||||
{
|
||||
var task = Task.Run(() => _positionSizer.CalculateSize(intent, context, config));
|
||||
tasks.Add(task);
|
||||
}
|
||||
|
||||
var results = await Task.WhenAll(tasks);
|
||||
stopwatch.Stop();
|
||||
|
||||
// Assert
|
||||
Assert.AreEqual(50, results.Length);
|
||||
foreach (var result in results)
|
||||
{
|
||||
Assert.IsNotNull(result);
|
||||
Assert.IsTrue(result.Contracts >= 0);
|
||||
}
|
||||
|
||||
// Check metrics
|
||||
var metricsSnapshot = _positionSizer.GetMetricsSnapshot();
|
||||
Assert.IsTrue(metricsSnapshot.TotalOperations >= 50);
|
||||
Assert.IsTrue(metricsSnapshot.VolatilityAdjustedOperations >= 50);
|
||||
}
|
||||
|
||||
[TestMethod]
|
||||
public void AdvancedPositionSizer_Performance_Throughput()
|
||||
{
|
||||
// Arrange
|
||||
var intent = CreateValidIntent();
|
||||
var context = CreateTestContext();
|
||||
var config = CreateTestSizingConfig(SizingMethod.OptimalF);
|
||||
|
||||
// Act
|
||||
var stopwatch = Stopwatch.StartNew();
|
||||
const int iterations = 1000;
|
||||
|
||||
for (int i = 0; i < iterations; i++)
|
||||
{
|
||||
var result = _positionSizer.CalculateSize(intent, context, config);
|
||||
Assert.IsNotNull(result);
|
||||
}
|
||||
|
||||
stopwatch.Stop();
|
||||
|
||||
// Assert
|
||||
var metricsSnapshot = _positionSizer.GetMetricsSnapshot();
|
||||
Assert.IsTrue(metricsSnapshot.TotalOperations >= iterations);
|
||||
|
||||
// Calculate throughput
|
||||
var throughput = (double)iterations / stopwatch.Elapsed.TotalSeconds;
|
||||
Assert.IsTrue(throughput > 100); // Should process at least 100 operations per second
|
||||
|
||||
_logger.LogInformation(String.Format("Processed {0} operations in {1:F2} ms ({2:F2} ops/sec)",
|
||||
iterations, stopwatch.Elapsed.TotalMilliseconds, throughput));
|
||||
}
|
||||
|
||||
[TestMethod]
|
||||
public void AdvancedPositionSizer_Performance_MemoryAllocation()
|
||||
{
|
||||
// Arrange
|
||||
var intent = CreateValidIntent();
|
||||
var context = CreateTestContext();
|
||||
var config = CreateTestSizingConfig(SizingMethod.KellyCriterion);
|
||||
|
||||
// Force garbage collection to get a clean baseline
|
||||
GC.Collect();
|
||||
GC.WaitForPendingFinalizers();
|
||||
GC.Collect();
|
||||
|
||||
var initialMemory = GC.GetTotalMemory(false);
|
||||
|
||||
// Act
|
||||
for (int i = 0; i < 1000; i++)
|
||||
{
|
||||
var result = _positionSizer.CalculateSize(intent, context, config);
|
||||
Assert.IsNotNull(result);
|
||||
}
|
||||
|
||||
// Force garbage collection
|
||||
GC.Collect();
|
||||
GC.WaitForPendingFinalizers();
|
||||
var finalMemory = GC.GetTotalMemory(false);
|
||||
|
||||
// Assert - with object pooling, memory growth should be minimal
|
||||
var memoryGrowth = finalMemory - initialMemory;
|
||||
Assert.IsTrue(memoryGrowth < 10 * 1024 * 1024); // Less than 10MB growth for 1000 operations
|
||||
|
||||
_logger.LogInformation(String.Format("Memory growth: {0:F2} KB for 1000 operations",
|
||||
memoryGrowth / 1024.0));
|
||||
}
|
||||
|
||||
[TestMethod]
|
||||
public void AdvancedPositionSizer_Performance_MetricsConsistency()
|
||||
{
|
||||
// Arrange
|
||||
var intent = CreateValidIntent();
|
||||
var context = CreateTestContext();
|
||||
var config1 = CreateTestSizingConfig(SizingMethod.OptimalF);
|
||||
var config2 = CreateTestSizingConfig(SizingMethod.KellyCriterion);
|
||||
var config3 = CreateTestSizingConfig(SizingMethod.VolatilityAdjusted);
|
||||
|
||||
// Act
|
||||
// Run mixed operations
|
||||
for (int i = 0; i < 30; i++)
|
||||
{
|
||||
_positionSizer.CalculateSize(intent, context, config1);
|
||||
_positionSizer.CalculateSize(intent, context, config2);
|
||||
_positionSizer.CalculateSize(intent, context, config3);
|
||||
}
|
||||
|
||||
// Assert
|
||||
var metricsSnapshot = _positionSizer.GetMetricsSnapshot();
|
||||
Assert.IsTrue(metricsSnapshot.TotalOperations >= 90);
|
||||
Assert.IsTrue(metricsSnapshot.OptimalFOperations >= 30);
|
||||
Assert.IsTrue(metricsSnapshot.KellyCriterionOperations >= 30);
|
||||
Assert.IsTrue(metricsSnapshot.VolatilityAdjustedOperations >= 30);
|
||||
|
||||
// Verify timing metrics are reasonable
|
||||
Assert.IsTrue(metricsSnapshot.MaxProcessingTimeMs >= metricsSnapshot.MinProcessingTimeMs);
|
||||
Assert.IsTrue(metricsSnapshot.AverageProcessingTimeMs >= 0);
|
||||
}
|
||||
|
||||
#region Helper Methods
|
||||
|
||||
private StrategyIntent CreateValidIntent(
|
||||
string symbol = "ES",
|
||||
int stopTicks = 8,
|
||||
OrderSide side = OrderSide.Buy)
|
||||
{
|
||||
return new StrategyIntent(
|
||||
symbol: symbol,
|
||||
side: side,
|
||||
entryType: OrderType.Market,
|
||||
limitPrice: null,
|
||||
stopTicks: stopTicks,
|
||||
targetTicks: 16,
|
||||
confidence: 0.8,
|
||||
reason: "Test intent",
|
||||
metadata: new Dictionary<string, object>()
|
||||
);
|
||||
}
|
||||
|
||||
private StrategyContext CreateTestContext(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<string, object>()
|
||||
);
|
||||
}
|
||||
|
||||
private SizingConfig CreateTestSizingConfig(SizingMethod method)
|
||||
{
|
||||
var methodParameters = new Dictionary<string, object>();
|
||||
if (method == SizingMethod.KellyCriterion)
|
||||
{
|
||||
methodParameters.Add("kelly_fraction", 0.5);
|
||||
}
|
||||
|
||||
return new SizingConfig(
|
||||
method: method,
|
||||
minContracts: 1,
|
||||
maxContracts: 10,
|
||||
riskPerTrade: 500,
|
||||
methodParameters: methodParameters
|
||||
);
|
||||
}
|
||||
|
||||
#endregion
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Test implementation of ILogger for testing
|
||||
/// </summary>
|
||||
public class TestLogger : ILogger
|
||||
{
|
||||
public void LogCritical(string message, params object[] args)
|
||||
{
|
||||
// No-op for testing
|
||||
}
|
||||
|
||||
public void LogDebug(string message, params object[] args)
|
||||
{
|
||||
// No-op for testing
|
||||
}
|
||||
|
||||
public void LogError(string message, params object[] args)
|
||||
{
|
||||
// No-op for testing
|
||||
}
|
||||
|
||||
public void LogError(Exception exception, string message, params object[] args)
|
||||
{
|
||||
// No-op for testing
|
||||
}
|
||||
|
||||
public void LogInformation(string message, params object[] args)
|
||||
{
|
||||
// No-op for testing
|
||||
Console.WriteLine("[INFO] " + message, args);
|
||||
}
|
||||
|
||||
public void LogWarning(string message, params object[] args)
|
||||
{
|
||||
// No-op for testing
|
||||
}
|
||||
|
||||
public bool IsEnabled(int logLevel)
|
||||
{
|
||||
return true;
|
||||
}
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user