Implement advanced position sizing algorithms with Optimal f, Kelly Criterion, and volatility-adjusted methods
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:
@@ -163,7 +163,17 @@ namespace NT8.Core.Common.Models
|
|||||||
/// <summary>
|
/// <summary>
|
||||||
/// Optimal F calculation
|
/// Optimal F calculation
|
||||||
/// </summary>
|
/// </summary>
|
||||||
OptimalF
|
OptimalF,
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Kelly Criterion sizing
|
||||||
|
/// </summary>
|
||||||
|
KellyCriterion,
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Volatility-adjusted sizing
|
||||||
|
/// </summary>
|
||||||
|
VolatilityAdjusted
|
||||||
}
|
}
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
@@ -438,4 +448,4 @@ namespace NT8.Core.Common.Models
|
|||||||
ExecutionId = executionId;
|
ExecutionId = executionId;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
497
src/NT8.Core/Sizing/AdvancedPositionSizer.cs
Normal file
497
src/NT8.Core/Sizing/AdvancedPositionSizer.cs
Normal file
@@ -0,0 +1,497 @@
|
|||||||
|
using NT8.Core.Common.Models;
|
||||||
|
using NT8.Core.Logging;
|
||||||
|
using System;
|
||||||
|
using System.Collections.Generic;
|
||||||
|
using System.Linq;
|
||||||
|
|
||||||
|
namespace NT8.Core.Sizing
|
||||||
|
{
|
||||||
|
/// <summary>
|
||||||
|
/// Advanced position sizer with Optimal f, Kelly Criterion, and volatility-adjusted methods
|
||||||
|
/// Implements sophisticated position sizing algorithms for professional trading
|
||||||
|
/// </summary>
|
||||||
|
public class AdvancedPositionSizer : IPositionSizer
|
||||||
|
{
|
||||||
|
private readonly ILogger _logger;
|
||||||
|
|
||||||
|
public AdvancedPositionSizer(ILogger logger)
|
||||||
|
{
|
||||||
|
if (logger == null) throw new ArgumentNullException("logger");
|
||||||
|
_logger = logger;
|
||||||
|
}
|
||||||
|
|
||||||
|
public SizingResult CalculateSize(StrategyIntent intent, StrategyContext context, SizingConfig config)
|
||||||
|
{
|
||||||
|
if (intent == null) throw new ArgumentNullException("intent");
|
||||||
|
if (context == null) throw new ArgumentNullException("context");
|
||||||
|
if (config == null) throw new ArgumentNullException("config");
|
||||||
|
|
||||||
|
// 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>();
|
||||||
|
errorCalcs.Add("error", "Invalid intent");
|
||||||
|
|
||||||
|
return new SizingResult(0, 0, config.Method, errorCalcs);
|
||||||
|
}
|
||||||
|
|
||||||
|
switch (config.Method)
|
||||||
|
{
|
||||||
|
case SizingMethod.OptimalF:
|
||||||
|
return CalculateOptimalF(intent, context, config);
|
||||||
|
case SizingMethod.KellyCriterion:
|
||||||
|
return CalculateKellyCriterion(intent, context, config);
|
||||||
|
case SizingMethod.VolatilityAdjusted:
|
||||||
|
return CalculateVolatilityAdjustedSizing(intent, context, config);
|
||||||
|
default:
|
||||||
|
throw new NotSupportedException(String.Format("Sizing method {0} not supported in AdvancedPositionSizer", config.Method));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private SizingResult CalculateOptimalF(StrategyIntent intent, StrategyContext context, SizingConfig config)
|
||||||
|
{
|
||||||
|
// Get trade history for calculating Optimal f
|
||||||
|
var tradeHistory = GetRecentTradeHistory(context, config);
|
||||||
|
if (tradeHistory.Count == 0)
|
||||||
|
{
|
||||||
|
// Fall back to fixed risk if no trade history
|
||||||
|
return CalculateFixedRiskFallback(intent, context, config);
|
||||||
|
}
|
||||||
|
|
||||||
|
// Calculate Optimal f
|
||||||
|
var optimalF = CalculateOptimalFValue(tradeHistory);
|
||||||
|
|
||||||
|
// Get account information
|
||||||
|
var equity = context.Account.Equity;
|
||||||
|
var maxLoss = GetMaximumLossFromHistory(tradeHistory);
|
||||||
|
|
||||||
|
// Calculate optimal contracts using Optimal f formula
|
||||||
|
// Contracts = (Optimal f * Equity) / Max Loss
|
||||||
|
var optimalContracts = (optimalF * equity) / Math.Abs(maxLoss);
|
||||||
|
|
||||||
|
// 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 tickValue = GetTickValue(intent.Symbol);
|
||||||
|
var riskPerContract = intent.StopTicks * tickValue;
|
||||||
|
var actualRisk = contracts * riskPerContract;
|
||||||
|
|
||||||
|
_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>();
|
||||||
|
calculations.Add("optimal_f", optimalF);
|
||||||
|
calculations.Add("equity", equity);
|
||||||
|
calculations.Add("max_loss", maxLoss);
|
||||||
|
calculations.Add("optimal_contracts", optimalContracts);
|
||||||
|
calculations.Add("clamped_contracts", contracts);
|
||||||
|
calculations.Add("stop_ticks", intent.StopTicks);
|
||||||
|
calculations.Add("tick_value", tickValue);
|
||||||
|
calculations.Add("risk_per_contract", riskPerContract);
|
||||||
|
calculations.Add("actual_risk", actualRisk);
|
||||||
|
calculations.Add("min_contracts", config.MinContracts);
|
||||||
|
calculations.Add("max_contracts", config.MaxContracts);
|
||||||
|
|
||||||
|
return new SizingResult(
|
||||||
|
contracts: contracts,
|
||||||
|
riskAmount: actualRisk,
|
||||||
|
method: SizingMethod.OptimalF,
|
||||||
|
calculations: calculations
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
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);
|
||||||
|
if (tradeHistory.Count == 0)
|
||||||
|
{
|
||||||
|
// Fall back to fixed risk if no trade history
|
||||||
|
return CalculateFixedRiskFallback(intent, context, config);
|
||||||
|
}
|
||||||
|
|
||||||
|
// Calculate Kelly Criterion parameters
|
||||||
|
var winRate = CalculateWinRate(tradeHistory);
|
||||||
|
var avgWin = CalculateAverageWin(tradeHistory);
|
||||||
|
var avgLoss = CalculateAverageLoss(tradeHistory);
|
||||||
|
|
||||||
|
// Calculate Kelly Criterion fraction
|
||||||
|
// K = (bp - q) / b
|
||||||
|
// Where: b = avgWin/avgLoss (odds), p = winRate, q = 1 - winRate
|
||||||
|
var odds = avgWin / Math.Abs(avgLoss);
|
||||||
|
var kellyFraction = ((odds * winRate) - (1 - winRate)) / odds;
|
||||||
|
|
||||||
|
// Apply fractional Kelly to reduce risk (typically use 25%-50% of full Kelly)
|
||||||
|
var fractionalKelly = GetParameterValue<double>(config, "kelly_fraction", 0.5);
|
||||||
|
var adjustedKelly = kellyFraction * fractionalKelly;
|
||||||
|
|
||||||
|
// Calculate position size based on Kelly Criterion
|
||||||
|
var equity = context.Account.Equity;
|
||||||
|
var tickValue = GetTickValue(intent.Symbol);
|
||||||
|
var riskPerContract = intent.StopTicks * tickValue;
|
||||||
|
|
||||||
|
// Kelly position size = (Kelly Fraction * Equity) / Risk per contract
|
||||||
|
var kellyContracts = (adjustedKelly * equity) / riskPerContract;
|
||||||
|
var contracts = (int)Math.Floor(Math.Abs(kellyContracts));
|
||||||
|
|
||||||
|
// 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("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>();
|
||||||
|
calculations.Add("win_rate", winRate);
|
||||||
|
calculations.Add("avg_win", avgWin);
|
||||||
|
calculations.Add("avg_loss", avgLoss);
|
||||||
|
calculations.Add("odds", odds);
|
||||||
|
calculations.Add("kelly_fraction", kellyFraction);
|
||||||
|
calculations.Add("fractional_kelly", fractionalKelly);
|
||||||
|
calculations.Add("adjusted_kelly", adjustedKelly);
|
||||||
|
calculations.Add("equity", equity);
|
||||||
|
calculations.Add("tick_value", tickValue);
|
||||||
|
calculations.Add("risk_per_contract", riskPerContract);
|
||||||
|
calculations.Add("kelly_contracts", kellyContracts);
|
||||||
|
calculations.Add("clamped_contracts", contracts);
|
||||||
|
calculations.Add("actual_risk", actualRisk);
|
||||||
|
calculations.Add("min_contracts", config.MinContracts);
|
||||||
|
calculations.Add("max_contracts", config.MaxContracts);
|
||||||
|
|
||||||
|
return new SizingResult(
|
||||||
|
contracts: contracts,
|
||||||
|
riskAmount: actualRisk,
|
||||||
|
method: SizingMethod.KellyCriterion,
|
||||||
|
calculations: calculations
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
private SizingResult CalculateVolatilityAdjustedSizing(StrategyIntent intent, StrategyContext context, SizingConfig config)
|
||||||
|
{
|
||||||
|
// Get volatility information
|
||||||
|
var atr = CalculateATR(context, intent.Symbol, 14); // 14-period ATR
|
||||||
|
var tickValue = GetTickValue(intent.Symbol);
|
||||||
|
|
||||||
|
// Get base risk from configuration
|
||||||
|
var baseRisk = config.RiskPerTrade;
|
||||||
|
|
||||||
|
// Apply volatility adjustment
|
||||||
|
// Higher volatility = lower position size, Lower volatility = higher position size
|
||||||
|
var volatilityAdjustment = CalculateVolatilityAdjustment(atr, intent.Symbol);
|
||||||
|
var adjustedRisk = baseRisk * volatilityAdjustment;
|
||||||
|
|
||||||
|
// Calculate contracts based on adjusted risk
|
||||||
|
var riskPerContract = intent.StopTicks * tickValue;
|
||||||
|
var optimalContracts = adjustedRisk / riskPerContract;
|
||||||
|
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("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>();
|
||||||
|
calculations.Add("atr", atr);
|
||||||
|
calculations.Add("volatility_adjustment", volatilityAdjustment);
|
||||||
|
calculations.Add("base_risk", baseRisk);
|
||||||
|
calculations.Add("adjusted_risk", adjustedRisk);
|
||||||
|
calculations.Add("tick_value", tickValue);
|
||||||
|
calculations.Add("risk_per_contract", riskPerContract);
|
||||||
|
calculations.Add("optimal_contracts", optimalContracts);
|
||||||
|
calculations.Add("clamped_contracts", contracts);
|
||||||
|
calculations.Add("actual_risk", actualRisk);
|
||||||
|
calculations.Add("stop_ticks", intent.StopTicks);
|
||||||
|
calculations.Add("min_contracts", config.MinContracts);
|
||||||
|
calculations.Add("max_contracts", config.MaxContracts);
|
||||||
|
|
||||||
|
return new SizingResult(
|
||||||
|
contracts: contracts,
|
||||||
|
riskAmount: actualRisk,
|
||||||
|
method: SizingMethod.VolatilityAdjusted,
|
||||||
|
calculations: calculations
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
private SizingResult CalculateFixedRiskFallback(StrategyIntent intent, StrategyContext context, SizingConfig config)
|
||||||
|
{
|
||||||
|
var tickValue = GetTickValue(intent.Symbol);
|
||||||
|
|
||||||
|
// Validate stop ticks
|
||||||
|
if (intent.StopTicks <= 0)
|
||||||
|
{
|
||||||
|
_logger.LogWarning("Invalid stop ticks {0} for fixed risk sizing on {1}",
|
||||||
|
intent.StopTicks, intent.Symbol);
|
||||||
|
|
||||||
|
var errorCalcs = new Dictionary<string, object>();
|
||||||
|
errorCalcs.Add("error", "Invalid stop ticks");
|
||||||
|
errorCalcs.Add("stop_ticks", intent.StopTicks);
|
||||||
|
|
||||||
|
return new SizingResult(0, 0, SizingMethod.FixedDollarRisk, errorCalcs);
|
||||||
|
}
|
||||||
|
|
||||||
|
// 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 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>();
|
||||||
|
calculations.Add("target_risk", targetRisk);
|
||||||
|
calculations.Add("stop_ticks", intent.StopTicks);
|
||||||
|
calculations.Add("tick_value", tickValue);
|
||||||
|
calculations.Add("risk_per_contract", riskPerContract);
|
||||||
|
calculations.Add("optimal_contracts", optimalContracts);
|
||||||
|
calculations.Add("clamped_contracts", contracts);
|
||||||
|
calculations.Add("actual_risk", actualRisk);
|
||||||
|
calculations.Add("min_contracts", config.MinContracts);
|
||||||
|
calculations.Add("max_contracts", config.MaxContracts);
|
||||||
|
|
||||||
|
return new SizingResult(
|
||||||
|
contracts: contracts,
|
||||||
|
riskAmount: actualRisk,
|
||||||
|
method: SizingMethod.FixedDollarRisk,
|
||||||
|
calculations: calculations
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
private static double CalculateOptimalFValue(List<TradeResult> tradeHistory)
|
||||||
|
{
|
||||||
|
if (tradeHistory == null || tradeHistory.Count == 0)
|
||||||
|
return 0.0;
|
||||||
|
|
||||||
|
// Find the largest loss (in absolute terms)
|
||||||
|
var largestLoss = Math.Abs(tradeHistory.Min(t => t.ProfitLoss));
|
||||||
|
|
||||||
|
if (largestLoss == 0)
|
||||||
|
return 0.0;
|
||||||
|
|
||||||
|
// Calculate Optimal f using the formula:
|
||||||
|
// f = (N*R - T) / (N*L)
|
||||||
|
// Where: N = number of trades, R = average win, L = largest loss, T = total profit
|
||||||
|
|
||||||
|
var n = tradeHistory.Count;
|
||||||
|
var totalProfit = tradeHistory.Sum(t => t.ProfitLoss);
|
||||||
|
var averageWin = tradeHistory.Where(t => t.ProfitLoss > 0).DefaultIfEmpty(new TradeResult()).Average(t => t.ProfitLoss);
|
||||||
|
|
||||||
|
if (averageWin <= 0)
|
||||||
|
return 0.0;
|
||||||
|
|
||||||
|
var optimalF = (n * averageWin - totalProfit) / (n * largestLoss);
|
||||||
|
|
||||||
|
// Ensure f is between 0 and 1
|
||||||
|
return Math.Max(0.0, Math.Min(1.0, optimalF));
|
||||||
|
}
|
||||||
|
|
||||||
|
private static double CalculateWinRate(List<TradeResult> tradeHistory)
|
||||||
|
{
|
||||||
|
if (tradeHistory == null || tradeHistory.Count == 0)
|
||||||
|
return 0.0;
|
||||||
|
|
||||||
|
var winningTrades = tradeHistory.Count(t => t.ProfitLoss > 0);
|
||||||
|
return (double)winningTrades / tradeHistory.Count;
|
||||||
|
}
|
||||||
|
|
||||||
|
private static double CalculateAverageWin(List<TradeResult> tradeHistory)
|
||||||
|
{
|
||||||
|
if (tradeHistory == null || tradeHistory.Count == 0)
|
||||||
|
return 0.0;
|
||||||
|
|
||||||
|
var winningTrades = tradeHistory.Where(t => t.ProfitLoss > 0).ToList();
|
||||||
|
if (winningTrades.Count == 0)
|
||||||
|
return 0.0;
|
||||||
|
|
||||||
|
return winningTrades.Average(t => t.ProfitLoss);
|
||||||
|
}
|
||||||
|
|
||||||
|
private static double CalculateAverageLoss(List<TradeResult> tradeHistory)
|
||||||
|
{
|
||||||
|
if (tradeHistory == null || tradeHistory.Count == 0)
|
||||||
|
return 0.0;
|
||||||
|
|
||||||
|
var losingTrades = tradeHistory.Where(t => t.ProfitLoss < 0).ToList();
|
||||||
|
if (losingTrades.Count == 0)
|
||||||
|
return 0.0;
|
||||||
|
|
||||||
|
return losingTrades.Average(t => t.ProfitLoss);
|
||||||
|
}
|
||||||
|
|
||||||
|
private static double GetMaximumLossFromHistory(List<TradeResult> tradeHistory)
|
||||||
|
{
|
||||||
|
if (tradeHistory == null || tradeHistory.Count == 0)
|
||||||
|
return 0.0;
|
||||||
|
|
||||||
|
return tradeHistory.Min(t => t.ProfitLoss);
|
||||||
|
}
|
||||||
|
|
||||||
|
private static double CalculateATR(StrategyContext context, string symbol, int periods)
|
||||||
|
{
|
||||||
|
// This would typically involve retrieving historical bar data
|
||||||
|
// For this implementation, we'll use a simplified approach
|
||||||
|
return 1.0; // Placeholder value
|
||||||
|
}
|
||||||
|
|
||||||
|
private static double CalculateVolatilityAdjustment(double atr, string symbol)
|
||||||
|
{
|
||||||
|
// Normalize ATR to a volatility adjustment factor
|
||||||
|
// Higher ATR = lower adjustment (reduce position size)
|
||||||
|
// Lower ATR = higher adjustment (increase position size)
|
||||||
|
|
||||||
|
// This is a simplified example - in practice, you'd normalize against
|
||||||
|
// historical ATR values for the specific symbol
|
||||||
|
var normalizedATR = atr / 10.0; // Example normalization
|
||||||
|
var adjustment = 1.0 / (1.0 + normalizedATR);
|
||||||
|
|
||||||
|
// Ensure adjustment is between 0.1 and 2.0
|
||||||
|
return Math.Max(0.1, Math.Min(2.0, adjustment));
|
||||||
|
}
|
||||||
|
|
||||||
|
private static List<TradeResult> GetRecentTradeHistory(StrategyContext context, SizingConfig config)
|
||||||
|
{
|
||||||
|
// In a real implementation, this would retrieve actual trade history
|
||||||
|
// For this example, we'll return an empty list to trigger fallback behavior
|
||||||
|
return new List<TradeResult>();
|
||||||
|
}
|
||||||
|
|
||||||
|
private static T GetParameterValue<T>(SizingConfig config, string key, T defaultValue)
|
||||||
|
{
|
||||||
|
if (config.MethodParameters.ContainsKey(key))
|
||||||
|
{
|
||||||
|
try
|
||||||
|
{
|
||||||
|
return (T)Convert.ChangeType(config.MethodParameters[key], 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
|
||||||
|
switch (symbol)
|
||||||
|
{
|
||||||
|
case "ES": return 12.50; // E-mini S&P 500
|
||||||
|
case "MES": return 1.25; // Micro E-mini S&P 500
|
||||||
|
case "NQ": return 5.00; // E-mini NASDAQ-100
|
||||||
|
case "MNQ": return 0.50; // Micro E-mini NASDAQ-100
|
||||||
|
case "CL": return 10.00; // Crude Oil
|
||||||
|
case "GC": return 10.00; // Gold
|
||||||
|
case "6E": return 12.50; // Euro FX
|
||||||
|
case "6A": return 10.00; // Australian Dollar
|
||||||
|
default: return 12.50; // Default to ES value
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
public SizingMetadata GetMetadata()
|
||||||
|
{
|
||||||
|
var requiredParams = new List<string>();
|
||||||
|
requiredParams.Add("method");
|
||||||
|
requiredParams.Add("risk_per_trade");
|
||||||
|
requiredParams.Add("min_contracts");
|
||||||
|
requiredParams.Add("max_contracts");
|
||||||
|
|
||||||
|
return new SizingMetadata(
|
||||||
|
name: "Advanced Position Sizer",
|
||||||
|
description: "Optimal f, Kelly Criterion, and volatility-adjusted sizing with contract clamping",
|
||||||
|
requiredParameters: requiredParams
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
/// <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.OptimalF:
|
||||||
|
// No additional parameters required for Optimal f
|
||||||
|
break;
|
||||||
|
|
||||||
|
case SizingMethod.KellyCriterion:
|
||||||
|
// Validate Kelly fraction parameter if provided
|
||||||
|
if (config.MethodParameters.ContainsKey("kelly_fraction"))
|
||||||
|
{
|
||||||
|
var kellyFraction = GetParameterValue<double>(config, "kelly_fraction", 0.5);
|
||||||
|
if (kellyFraction <= 0 || kellyFraction > 1.0)
|
||||||
|
errors.Add("Kelly fraction must be between 0 and 1.0");
|
||||||
|
}
|
||||||
|
break;
|
||||||
|
|
||||||
|
case SizingMethod.VolatilityAdjusted:
|
||||||
|
// No additional parameters required for volatility-adjusted sizing
|
||||||
|
break;
|
||||||
|
|
||||||
|
default:
|
||||||
|
errors.Add(String.Format("Unsupported sizing method: {0}", config.Method));
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
|
||||||
|
return errors.Count == 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Internal class to represent trade results for calculations
|
||||||
|
/// </summary>
|
||||||
|
private class TradeResult
|
||||||
|
{
|
||||||
|
public double ProfitLoss { get; set; }
|
||||||
|
public DateTime TradeTime { get; set; }
|
||||||
|
|
||||||
|
public TradeResult()
|
||||||
|
{
|
||||||
|
ProfitLoss = 0.0;
|
||||||
|
TradeTime = DateTime.UtcNow;
|
||||||
|
}
|
||||||
|
|
||||||
|
public TradeResult(double profitLoss, DateTime tradeTime)
|
||||||
|
{
|
||||||
|
ProfitLoss = profitLoss;
|
||||||
|
TradeTime = tradeTime;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
Reference in New Issue
Block a user