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:
@@ -1,6 +1,7 @@
|
||||
using NT8.Core.Common.Models;
|
||||
using NT8.Core.Logging;
|
||||
using System;
|
||||
using System.Collections.Concurrent;
|
||||
using System.Collections.Generic;
|
||||
using System.Linq;
|
||||
|
||||
@@ -14,6 +15,16 @@ namespace NT8.Core.Sizing
|
||||
{
|
||||
private readonly ILogger _logger;
|
||||
|
||||
// Performance metrics
|
||||
private readonly SizingMetrics _metrics = new SizingMetrics();
|
||||
|
||||
// Object pools for frequently used objects
|
||||
private readonly ConcurrentQueue<Dictionary<string, object>> _dictionaryPool = new ConcurrentQueue<Dictionary<string, object>>();
|
||||
private readonly ConcurrentQueue<List<TradeResult>> _tradeListPool = new ConcurrentQueue<List<TradeResult>>();
|
||||
|
||||
// Pool sizes
|
||||
private const int MaxPoolSize = 100;
|
||||
|
||||
public AdvancedPositionSizer(ILogger logger)
|
||||
{
|
||||
if (logger == null) throw new ArgumentNullException("logger");
|
||||
@@ -26,36 +37,75 @@ namespace NT8.Core.Sizing
|
||||
if (context == null) throw new ArgumentNullException("context");
|
||||
if (config == null) throw new ArgumentNullException("config");
|
||||
|
||||
var startTime = DateTime.UtcNow;
|
||||
|
||||
// Validate intent is suitable for sizing
|
||||
if (!intent.IsValid())
|
||||
{
|
||||
_logger.LogWarning("Invalid strategy intent provided for sizing: {0}", intent);
|
||||
|
||||
var errorCalcs = new Dictionary<string, object>();
|
||||
Dictionary<string, object> errorCalcs;
|
||||
if (!_dictionaryPool.TryDequeue(out errorCalcs))
|
||||
{
|
||||
errorCalcs = new Dictionary<string, object>();
|
||||
}
|
||||
errorCalcs.Clear();
|
||||
errorCalcs.Add("error", "Invalid intent");
|
||||
|
||||
return new SizingResult(0, 0, config.Method, errorCalcs);
|
||||
var result = new SizingResult(0, 0, config.Method, errorCalcs);
|
||||
|
||||
// Record metrics
|
||||
var endTime = DateTime.UtcNow;
|
||||
var processingTime = (endTime - startTime).TotalMilliseconds;
|
||||
_metrics.RecordOperation(config.Method, (long)processingTime);
|
||||
|
||||
return result;
|
||||
}
|
||||
|
||||
SizingResult sizingResult;
|
||||
|
||||
switch (config.Method)
|
||||
{
|
||||
case SizingMethod.OptimalF:
|
||||
return CalculateOptimalF(intent, context, config);
|
||||
sizingResult = CalculateOptimalF(intent, context, config);
|
||||
break;
|
||||
case SizingMethod.KellyCriterion:
|
||||
return CalculateKellyCriterion(intent, context, config);
|
||||
sizingResult = CalculateKellyCriterion(intent, context, config);
|
||||
break;
|
||||
case SizingMethod.VolatilityAdjusted:
|
||||
return CalculateVolatilityAdjustedSizing(intent, context, config);
|
||||
sizingResult = CalculateVolatilityAdjustedSizing(intent, context, config);
|
||||
break;
|
||||
default:
|
||||
throw new NotSupportedException(String.Format("Sizing method {0} not supported in AdvancedPositionSizer", config.Method));
|
||||
}
|
||||
|
||||
// Record metrics
|
||||
var endTime2 = DateTime.UtcNow;
|
||||
var processingTime2 = (endTime2 - startTime).TotalMilliseconds;
|
||||
_metrics.RecordOperation(config.Method, (long)processingTime2);
|
||||
|
||||
return sizingResult;
|
||||
}
|
||||
|
||||
private SizingResult CalculateOptimalF(StrategyIntent intent, StrategyContext context, SizingConfig config)
|
||||
{
|
||||
// Get trade history for calculating Optimal f
|
||||
var tradeHistory = GetRecentTradeHistory(context, config);
|
||||
List<TradeResult> tradeHistory;
|
||||
if (!_tradeListPool.TryDequeue(out tradeHistory))
|
||||
{
|
||||
tradeHistory = new List<TradeResult>();
|
||||
}
|
||||
tradeHistory.Clear();
|
||||
tradeHistory.AddRange(GetRecentTradeHistory(context, config));
|
||||
|
||||
if (tradeHistory.Count == 0)
|
||||
{
|
||||
// Return trade history to pool
|
||||
if (_tradeListPool.Count < MaxPoolSize)
|
||||
{
|
||||
_tradeListPool.Enqueue(tradeHistory);
|
||||
}
|
||||
|
||||
// Fall back to fixed risk if no trade history
|
||||
return CalculateFixedRiskFallback(intent, context, config);
|
||||
}
|
||||
@@ -85,7 +135,12 @@ namespace NT8.Core.Sizing
|
||||
_logger.LogDebug("Optimal f sizing: {0} f={1:F4} ${2:F2}→{3:F2}→{4} contracts, ${5:F2} actual risk",
|
||||
intent.Symbol, optimalF, equity, optimalContracts, contracts, actualRisk);
|
||||
|
||||
var calculations = new Dictionary<string, object>();
|
||||
Dictionary<string, object> calculations;
|
||||
if (!_dictionaryPool.TryDequeue(out calculations))
|
||||
{
|
||||
calculations = new Dictionary<string, object>();
|
||||
}
|
||||
calculations.Clear();
|
||||
calculations.Add("optimal_f", optimalF);
|
||||
calculations.Add("equity", equity);
|
||||
calculations.Add("max_loss", maxLoss);
|
||||
@@ -98,20 +153,41 @@ namespace NT8.Core.Sizing
|
||||
calculations.Add("min_contracts", config.MinContracts);
|
||||
calculations.Add("max_contracts", config.MaxContracts);
|
||||
|
||||
return new SizingResult(
|
||||
// Return trade history to pool
|
||||
if (_tradeListPool.Count < MaxPoolSize)
|
||||
{
|
||||
_tradeListPool.Enqueue(tradeHistory);
|
||||
}
|
||||
|
||||
var result = new SizingResult(
|
||||
contracts: contracts,
|
||||
riskAmount: actualRisk,
|
||||
method: SizingMethod.OptimalF,
|
||||
calculations: calculations
|
||||
);
|
||||
|
||||
return result;
|
||||
}
|
||||
|
||||
private SizingResult CalculateKellyCriterion(StrategyIntent intent, StrategyContext context, SizingConfig config)
|
||||
{
|
||||
// Get trade history for calculating win rate and average win/loss
|
||||
var tradeHistory = GetRecentTradeHistory(context, config);
|
||||
List<TradeResult> tradeHistory;
|
||||
if (!_tradeListPool.TryDequeue(out tradeHistory))
|
||||
{
|
||||
tradeHistory = new List<TradeResult>();
|
||||
}
|
||||
tradeHistory.Clear();
|
||||
tradeHistory.AddRange(GetRecentTradeHistory(context, config));
|
||||
|
||||
if (tradeHistory.Count == 0)
|
||||
{
|
||||
// Return trade history to pool
|
||||
if (_tradeListPool.Count < MaxPoolSize)
|
||||
{
|
||||
_tradeListPool.Enqueue(tradeHistory);
|
||||
}
|
||||
|
||||
// Fall back to fixed risk if no trade history
|
||||
return CalculateFixedRiskFallback(intent, context, config);
|
||||
}
|
||||
@@ -149,7 +225,12 @@ namespace NT8.Core.Sizing
|
||||
_logger.LogDebug("Kelly Criterion sizing: {0} K={1:F4} adj={2:F4} ${3:F2}→{4:F2}→{5} contracts, ${6:F2} actual risk",
|
||||
intent.Symbol, kellyFraction, adjustedKelly, equity, kellyContracts, contracts, actualRisk);
|
||||
|
||||
var calculations = new Dictionary<string, object>();
|
||||
Dictionary<string, object> calculations;
|
||||
if (!_dictionaryPool.TryDequeue(out calculations))
|
||||
{
|
||||
calculations = new Dictionary<string, object>();
|
||||
}
|
||||
calculations.Clear();
|
||||
calculations.Add("win_rate", winRate);
|
||||
calculations.Add("avg_win", avgWin);
|
||||
calculations.Add("avg_loss", avgLoss);
|
||||
@@ -166,12 +247,20 @@ namespace NT8.Core.Sizing
|
||||
calculations.Add("min_contracts", config.MinContracts);
|
||||
calculations.Add("max_contracts", config.MaxContracts);
|
||||
|
||||
return new SizingResult(
|
||||
// Return trade history to pool
|
||||
if (_tradeListPool.Count < MaxPoolSize)
|
||||
{
|
||||
_tradeListPool.Enqueue(tradeHistory);
|
||||
}
|
||||
|
||||
var result = new SizingResult(
|
||||
contracts: contracts,
|
||||
riskAmount: actualRisk,
|
||||
method: SizingMethod.KellyCriterion,
|
||||
calculations: calculations
|
||||
);
|
||||
|
||||
return result;
|
||||
}
|
||||
|
||||
private SizingResult CalculateVolatilityAdjustedSizing(StrategyIntent intent, StrategyContext context, SizingConfig config)
|
||||
@@ -202,7 +291,12 @@ namespace NT8.Core.Sizing
|
||||
_logger.LogDebug("Volatility-adjusted sizing: {0} ATR={1:F4} adj={2:F4} ${3:F2}→${4:F2}→{5} contracts, ${6:F2} actual risk",
|
||||
intent.Symbol, atr, volatilityAdjustment, baseRisk, adjustedRisk, contracts, actualRisk);
|
||||
|
||||
var calculations = new Dictionary<string, object>();
|
||||
Dictionary<string, object> calculations;
|
||||
if (!_dictionaryPool.TryDequeue(out calculations))
|
||||
{
|
||||
calculations = new Dictionary<string, object>();
|
||||
}
|
||||
calculations.Clear();
|
||||
calculations.Add("atr", atr);
|
||||
calculations.Add("volatility_adjustment", volatilityAdjustment);
|
||||
calculations.Add("base_risk", baseRisk);
|
||||
@@ -216,12 +310,14 @@ namespace NT8.Core.Sizing
|
||||
calculations.Add("min_contracts", config.MinContracts);
|
||||
calculations.Add("max_contracts", config.MaxContracts);
|
||||
|
||||
return new SizingResult(
|
||||
var result = new SizingResult(
|
||||
contracts: contracts,
|
||||
riskAmount: actualRisk,
|
||||
method: SizingMethod.VolatilityAdjusted,
|
||||
calculations: calculations
|
||||
);
|
||||
|
||||
return result;
|
||||
}
|
||||
|
||||
private SizingResult CalculateFixedRiskFallback(StrategyIntent intent, StrategyContext context, SizingConfig config)
|
||||
@@ -234,11 +330,18 @@ namespace NT8.Core.Sizing
|
||||
_logger.LogWarning("Invalid stop ticks {0} for fixed risk sizing on {1}",
|
||||
intent.StopTicks, intent.Symbol);
|
||||
|
||||
var errorCalcs = new Dictionary<string, object>();
|
||||
Dictionary<string, object> errorCalcs;
|
||||
if (!_dictionaryPool.TryDequeue(out errorCalcs))
|
||||
{
|
||||
errorCalcs = new Dictionary<string, object>();
|
||||
}
|
||||
errorCalcs.Clear();
|
||||
errorCalcs.Add("error", "Invalid stop ticks");
|
||||
errorCalcs.Add("stop_ticks", intent.StopTicks);
|
||||
|
||||
return new SizingResult(0, 0, SizingMethod.FixedDollarRisk, errorCalcs);
|
||||
var errorResult = new SizingResult(0, 0, SizingMethod.FixedDollarRisk, errorCalcs);
|
||||
|
||||
return errorResult;
|
||||
}
|
||||
|
||||
// Calculate optimal contracts for target risk
|
||||
@@ -258,7 +361,12 @@ namespace NT8.Core.Sizing
|
||||
_logger.LogDebug("Fixed risk fallback sizing: {0} ${1:F2}→{2:F2}→{3} contracts, ${4:F2} actual risk",
|
||||
intent.Symbol, targetRisk, optimalContracts, contracts, actualRisk);
|
||||
|
||||
var calculations = new Dictionary<string, object>();
|
||||
Dictionary<string, object> calculations;
|
||||
if (!_dictionaryPool.TryDequeue(out calculations))
|
||||
{
|
||||
calculations = new Dictionary<string, object>();
|
||||
}
|
||||
calculations.Clear();
|
||||
calculations.Add("target_risk", targetRisk);
|
||||
calculations.Add("stop_ticks", intent.StopTicks);
|
||||
calculations.Add("tick_value", tickValue);
|
||||
@@ -269,12 +377,14 @@ namespace NT8.Core.Sizing
|
||||
calculations.Add("min_contracts", config.MinContracts);
|
||||
calculations.Add("max_contracts", config.MaxContracts);
|
||||
|
||||
return new SizingResult(
|
||||
var result = new SizingResult(
|
||||
contracts: contracts,
|
||||
riskAmount: actualRisk,
|
||||
method: SizingMethod.FixedDollarRisk,
|
||||
calculations: calculations
|
||||
);
|
||||
|
||||
return result;
|
||||
}
|
||||
|
||||
private static double CalculateOptimalFValue(List<TradeResult> tradeHistory)
|
||||
@@ -425,6 +535,14 @@ namespace NT8.Core.Sizing
|
||||
);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Get current performance metrics snapshot
|
||||
/// </summary>
|
||||
public SizingMetricsSnapshot GetMetricsSnapshot()
|
||||
{
|
||||
return _metrics.GetSnapshot();
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Validate sizing configuration parameters
|
||||
/// </summary>
|
||||
@@ -494,4 +612,97 @@ namespace NT8.Core.Sizing
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Performance metrics for sizing operations
|
||||
/// </summary>
|
||||
public class SizingMetrics
|
||||
{
|
||||
// Operation counters
|
||||
public long TotalOperations { get; private set; }
|
||||
public long OptimalFOperations { get; private set; }
|
||||
public long KellyCriterionOperations { get; private set; }
|
||||
public long VolatilityAdjustedOperations { get; private set; }
|
||||
public long FallbackOperations { get; private set; }
|
||||
|
||||
// Timing metrics
|
||||
public long TotalProcessingTimeMs { get; private set; }
|
||||
public long MaxProcessingTimeMs { get; private set; }
|
||||
public long MinProcessingTimeMs { get; private set; }
|
||||
|
||||
// Thread-safe counters
|
||||
private readonly object _lock = new object();
|
||||
|
||||
public SizingMetrics()
|
||||
{
|
||||
MinProcessingTimeMs = long.MaxValue;
|
||||
}
|
||||
|
||||
public void RecordOperation(SizingMethod method, long processingTimeMs)
|
||||
{
|
||||
lock (_lock)
|
||||
{
|
||||
TotalOperations++;
|
||||
TotalProcessingTimeMs += processingTimeMs;
|
||||
|
||||
// Update min/max timing
|
||||
if (processingTimeMs > MaxProcessingTimeMs)
|
||||
MaxProcessingTimeMs = processingTimeMs;
|
||||
if (processingTimeMs < MinProcessingTimeMs)
|
||||
MinProcessingTimeMs = processingTimeMs;
|
||||
|
||||
// Update method-specific counters
|
||||
switch (method)
|
||||
{
|
||||
case SizingMethod.OptimalF:
|
||||
OptimalFOperations++;
|
||||
break;
|
||||
case SizingMethod.KellyCriterion:
|
||||
KellyCriterionOperations++;
|
||||
break;
|
||||
case SizingMethod.VolatilityAdjusted:
|
||||
VolatilityAdjustedOperations++;
|
||||
break;
|
||||
case SizingMethod.FixedDollarRisk:
|
||||
FallbackOperations++;
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
public SizingMetricsSnapshot GetSnapshot()
|
||||
{
|
||||
lock (_lock)
|
||||
{
|
||||
return new SizingMetricsSnapshot
|
||||
{
|
||||
TotalOperations = TotalOperations,
|
||||
OptimalFOperations = OptimalFOperations,
|
||||
KellyCriterionOperations = KellyCriterionOperations,
|
||||
VolatilityAdjustedOperations = VolatilityAdjustedOperations,
|
||||
FallbackOperations = FallbackOperations,
|
||||
TotalProcessingTimeMs = TotalProcessingTimeMs,
|
||||
MaxProcessingTimeMs = MaxProcessingTimeMs,
|
||||
MinProcessingTimeMs = MinProcessingTimeMs,
|
||||
AverageProcessingTimeMs = TotalOperations > 0 ? (double)TotalProcessingTimeMs / TotalOperations : 0
|
||||
};
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Snapshot of sizing metrics
|
||||
/// </summary>
|
||||
public class SizingMetricsSnapshot
|
||||
{
|
||||
public long TotalOperations { get; set; }
|
||||
public long OptimalFOperations { get; set; }
|
||||
public long KellyCriterionOperations { get; set; }
|
||||
public long VolatilityAdjustedOperations { get; set; }
|
||||
public long FallbackOperations { get; set; }
|
||||
public long TotalProcessingTimeMs { get; set; }
|
||||
public long MaxProcessingTimeMs { get; set; }
|
||||
public long MinProcessingTimeMs { get; set; }
|
||||
public double AverageProcessingTimeMs { get; set; }
|
||||
}
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user