using System; using System.Collections.Generic; using NT8.Core.Common.Models; using NT8.Core.Intelligence; using NT8.Core.Logging; namespace NT8.Core.Sizing { /// /// Applies confluence grade and risk mode multipliers on top of base sizing output. /// public class GradeBasedSizer { private readonly ILogger _logger; private readonly GradeFilter _gradeFilter; /// /// Creates a grade-based sizer. /// /// Logger instance. /// Grade filter instance. public GradeBasedSizer(ILogger logger, GradeFilter gradeFilter) { if (logger == null) throw new ArgumentNullException("logger"); if (gradeFilter == null) throw new ArgumentNullException("gradeFilter"); _logger = logger; _gradeFilter = gradeFilter; } /// /// Calculates final size from base sizing plus grade and mode adjustments. /// /// Strategy intent. /// Strategy context. /// Confluence score with grade. /// Current risk mode. /// Base sizing configuration. /// Base position sizer used to compute initial contracts. /// Current risk mode configuration. /// Final sizing result including grade/mode metadata. public SizingResult CalculateGradeBasedSize( StrategyIntent intent, StrategyContext context, ConfluenceScore confluenceScore, RiskMode riskMode, SizingConfig baseConfig, IPositionSizer baseSizer, RiskModeConfig modeConfig) { if (intent == null) throw new ArgumentNullException("intent"); if (context == null) throw new ArgumentNullException("context"); if (confluenceScore == null) throw new ArgumentNullException("confluenceScore"); if (baseConfig == null) throw new ArgumentNullException("baseConfig"); if (baseSizer == null) throw new ArgumentNullException("baseSizer"); if (modeConfig == null) throw new ArgumentNullException("modeConfig"); try { if (!_gradeFilter.ShouldAcceptTrade(confluenceScore.Grade, riskMode)) { var reject = _gradeFilter.GetRejectionReason(confluenceScore.Grade, riskMode); var rejectCalcs = new Dictionary(); rejectCalcs.Add("rejected", true); rejectCalcs.Add("rejection_reason", reject); rejectCalcs.Add("grade", confluenceScore.Grade.ToString()); rejectCalcs.Add("risk_mode", riskMode.ToString()); _logger.LogInformation("Grade-based sizing rejected trade: {0}", reject); return new SizingResult(0, 0.0, baseConfig.Method, rejectCalcs); } var baseResult = baseSizer.CalculateSize(intent, context, baseConfig); var gradeMultiplier = _gradeFilter.GetSizeMultiplier(confluenceScore.Grade, riskMode); var modeMultiplier = modeConfig.SizeMultiplier; var combinedMultiplier = CombineMultipliers(gradeMultiplier, modeMultiplier); var adjustedContractsRaw = baseResult.Contracts * combinedMultiplier; var adjustedContracts = ApplyConstraints( (int)Math.Floor(adjustedContractsRaw), baseConfig.MinContracts, baseConfig.MaxContracts); var riskPerContract = baseResult.Contracts > 0 ? baseResult.RiskAmount / baseResult.Contracts : 0.0; var finalRisk = adjustedContracts * riskPerContract; var calculations = new Dictionary(); calculations.Add("base_contracts", baseResult.Contracts); calculations.Add("base_risk", baseResult.RiskAmount); calculations.Add("grade", confluenceScore.Grade.ToString()); calculations.Add("risk_mode", riskMode.ToString()); calculations.Add("grade_multiplier", gradeMultiplier); calculations.Add("mode_multiplier", modeMultiplier); calculations.Add("combined_multiplier", combinedMultiplier); calculations.Add("adjusted_contracts_raw", adjustedContractsRaw); calculations.Add("adjusted_contracts", adjustedContracts); calculations.Add("risk_per_contract", riskPerContract); calculations.Add("final_risk", finalRisk); return new SizingResult(adjustedContracts, finalRisk, baseResult.Method, calculations); } catch (Exception ex) { _logger.LogError("CalculateGradeBasedSize failed: {0}", ex.Message); throw; } } /// /// Combines grade and mode multipliers. /// /// Grade-based multiplier. /// Mode-based multiplier. /// Combined multiplier. public double CombineMultipliers(double gradeMultiplier, double modeMultiplier) { if (gradeMultiplier < 0.0) throw new ArgumentException("gradeMultiplier must be non-negative", "gradeMultiplier"); if (modeMultiplier < 0.0) throw new ArgumentException("modeMultiplier must be non-negative", "modeMultiplier"); return gradeMultiplier * modeMultiplier; } /// /// Applies min/max contract constraints. /// /// Calculated contracts. /// Minimum allowed contracts. /// Maximum allowed contracts. /// Constrained contracts. public int ApplyConstraints(int calculatedSize, int min, int max) { if (min < 0) throw new ArgumentException("min must be non-negative", "min"); if (max < min) throw new ArgumentException("max must be greater than or equal to min", "max"); if (calculatedSize < min) return min; if (calculatedSize > max) return max; return calculatedSize; } } }