Overall Statistics
Total Orders
9047
Average Win
0.04%
Average Loss
-0.04%
Compounding Annual Return
28.882%
Drawdown
42.300%
Expectancy
0.151
Start Equity
1000000.0
End Equity
1217091.59
Net Profit
21.709%
Sharpe Ratio
0.573
Sortino Ratio
0.879
Probabilistic Sharpe Ratio
31.638%
Loss Rate
43%
Win Rate
57%
Profit-Loss Ratio
1.03
Alpha
0
Beta
0
Annual Standard Deviation
0.54
Annual Variance
0.291
Information Ratio
0.675
Tracking Error
0.54
Treynor Ratio
0
Total Fees
$26898.38
Estimated Strategy Capacity
$2900000.00
Lowest Capacity Asset
AMD R735QTJ8XC9X
Portfolio Turnover
277.70%
Drawdown Recovery
189
#region imports
    using System;
    using System.Collections;
    using System.Collections.Generic;
    using System.Linq;
    using System.Globalization;
    using System.Drawing;
    using QuantConnect;
    using QuantConnect.Algorithm.Framework;
    using QuantConnect.Algorithm.Framework.Selection;
    using QuantConnect.Algorithm.Framework.Alphas;
    using QuantConnect.Algorithm.Framework.Portfolio;
    using QuantConnect.Algorithm.Framework.Portfolio.SignalExports;
    using QuantConnect.Algorithm.Framework.Execution;
    using QuantConnect.Algorithm.Framework.Risk;
    using QuantConnect.Algorithm.Selection;
    using QuantConnect.Api;
    using QuantConnect.Parameters;
    using QuantConnect.Benchmarks;
    using QuantConnect.Brokerages;
    using QuantConnect.Commands;
    using QuantConnect.Configuration;
    using QuantConnect.Util;
    using QuantConnect.Interfaces;
    using QuantConnect.Algorithm;
    using QuantConnect.Indicators;
    using QuantConnect.Data;
    using QuantConnect.Data.Auxiliary;
    using QuantConnect.Data.Consolidators;
    using QuantConnect.Data.Custom;
    using QuantConnect.Data.Custom.IconicTypes;
    using QuantConnect.DataSource;
    using QuantConnect.Data.Fundamental;
    using QuantConnect.Data.Market;
    using QuantConnect.Data.Shortable;
    using QuantConnect.Data.UniverseSelection;
    using QuantConnect.Notifications;
    using QuantConnect.Orders;
    using QuantConnect.Orders.Fees;
    using QuantConnect.Orders.Fills;
    using QuantConnect.Orders.OptionExercise;
    using QuantConnect.Orders.Slippage;
    using QuantConnect.Orders.TimeInForces;
    using QuantConnect.Python;
    using QuantConnect.Scheduling;
    using QuantConnect.Securities;
    using QuantConnect.Securities.Equity;
    using QuantConnect.Securities.Future;
    using QuantConnect.Securities.Option;
    using QuantConnect.Securities.Positions;
    using QuantConnect.Securities.Forex;
    using QuantConnect.Securities.Crypto;
    using QuantConnect.Securities.CryptoFuture;
    using QuantConnect.Securities.IndexOption;
    using QuantConnect.Securities.Interfaces;
    using QuantConnect.Securities.Volatility;
    using QuantConnect.Storage;
    using QuantConnect.Statistics;
    using QCAlgorithmFramework = QuantConnect.Algorithm.QCAlgorithm;
    using QCAlgorithmFrameworkBridge = QuantConnect.Algorithm.QCAlgorithm;
    using Calendar = QuantConnect.Data.Consolidators.Calendar;
#endregion

namespace CoreAlgo.Architecture.Core.Attributes
{
    /// <summary>
    /// Attribute to mark properties as strategy parameters
    /// </summary>
    [AttributeUsage(AttributeTargets.Property, AllowMultiple = false)]
    public class StrategyParameterAttribute : Attribute
    {
        /// <summary>
        /// Gets or sets the parameter name for QC GetParameter()
        /// </summary>
        public string Name { get; set; }

        /// <summary>
        /// Gets or sets the parameter description
        /// </summary>
        public string Description { get; set; }

        /// <summary>
        /// Gets or sets the default value
        /// </summary>
        public object DefaultValue { get; set; }

        /// <summary>
        /// Gets or sets whether the parameter is required
        /// </summary>
        public bool IsRequired { get; set; }

        /// <summary>
        /// Gets or sets the minimum value (for numeric types)
        /// </summary>
        public object MinValue { get; set; }

        /// <summary>
        /// Gets or sets the maximum value (for numeric types)
        /// </summary>
        public object MaxValue { get; set; }

        /// <summary>
        /// Gets or sets the parameter group for organization
        /// </summary>
        public string Group { get; set; }

        /// <summary>
        /// Gets or sets the display order
        /// </summary>
        public int Order { get; set; }

        /// <summary>
        /// Creates a new instance of StrategyParameterAttribute
        /// </summary>
        public StrategyParameterAttribute()
        {
        }

        /// <summary>
        /// Creates a new instance of StrategyParameterAttribute with name and default value
        /// </summary>
        public StrategyParameterAttribute(string name, object defaultValue = null)
        {
            Name = name;
            DefaultValue = defaultValue;
        }
    }
}
using System.Collections.Generic;

namespace CoreAlgo.Architecture.Core.Configuration
{
    /// <summary>
    /// Embedded configuration constants that are guaranteed to sync with QuantConnect
    /// </summary>
    public static class EmbeddedConfiguration
    {
        /// <summary>
        /// Development environment configuration as JSON string
        /// </summary>
        public const string DevelopmentConfig = @"{
  ""environment"": ""development"",
  ""algorithm"": {
    ""startDate"": ""2023-12-25"",
    ""endDate"": ""2024-12-25"",
    ""startingCash"": 100000,
    ""dataNormalizationMode"": ""Raw""
  },
  ""strategies"": {
    ""default"": {
      ""maxActivePositions"": 1,
      ""maxOpenPositions"": 2,
      ""targetPremiumPct"": 0.01,
      ""scheduleStartTime"": ""09:30:00"",
      ""scheduleStopTime"": ""16:00:00"",
      ""scheduleFrequency"": ""00:05:00""
    },
    ""SPXic"": {
      ""ticker"": ""SPX"",
      ""maxActivePositions"": 10,
      ""dte"": 0,
      ""putWingSize"": 10,
      ""callWingSize"": 10,
      ""minPremium"": 0.9,
      ""maxPremium"": 1.2
    }
  },
  ""riskManagement"": {
    ""maxDrawdownPercent"": 0.2,
    ""maxLeverageRatio"": 2.0,
    ""stopLossPercent"": 0.05
  },
  ""logging"": {
    ""level"": ""Debug"",
    ""enableFileLogging"": true,
    ""logFilePath"": ""logs/corealgo-dev.log""
  },
  ""backtesting"": {
    ""slippageModel"": ""ConstantSlippage"",
    ""feeModel"": ""InteractiveBrokersFeeModel"",
    ""fillModel"": ""ImmediateFillModel""
  }
}";

        /// <summary>
        /// Production environment configuration as JSON string
        /// </summary>
        public const string ProductionConfig = @"{
  ""environment"": ""production"",
  ""algorithm"": {
    ""startDate"": ""2024-01-01"",
    ""endDate"": ""2024-12-31"",
    ""startingCash"": 1000000,
    ""dataNormalizationMode"": ""Adjusted""
  },
  ""strategies"": {
    ""default"": {
      ""maxActivePositions"": 5,
      ""maxOpenPositions"": 10,
      ""targetPremiumPct"": 0.015,
      ""scheduleStartTime"": ""09:45:00"",
      ""scheduleStopTime"": ""15:30:00"",
      ""scheduleFrequency"": ""00:15:00""
    },
    ""SPXic"": {
      ""ticker"": ""SPX"",
      ""maxActivePositions"": 20,
      ""dte"": 0,
      ""putWingSize"": 15,
      ""callWingSize"": 15,
      ""minPremium"": 0.8,
      ""maxPremium"": 1.5
    }
  },
  ""riskManagement"": {
    ""maxDrawdownPercent"": 0.15,
    ""maxLeverageRatio"": 1.5,
    ""stopLossPercent"": 0.03
  },
  ""logging"": {
    ""level"": ""Information"",
    ""enableFileLogging"": true,
    ""logFilePath"": ""logs/corealgo-prod.log""
  },
  ""backtesting"": {
    ""slippageModel"": ""AlphaStreamsSlippage"",
    ""feeModel"": ""AlphaStreamsFeeModel"", 
    ""fillModel"": ""PartialFillModel""
  },
  ""notifications"": {
    ""email"": {
      ""enabled"": true,
      ""recipients"": [""trading@example.com""],
      ""sendOnError"": true,
      ""sendOnTrade"": true
    }
  }
}";

        /// <summary>
        /// Gets configuration by environment name
        /// </summary>
        /// <param name="environment">Environment name (development, production)</param>
        /// <returns>JSON configuration string</returns>
        public static string GetConfiguration(string environment)
        {
            return environment?.ToLowerInvariant() switch
            {
                "development" or "dev" => DevelopmentConfig,
                "production" or "prod" => ProductionConfig,
                _ => DevelopmentConfig // Default to development
            };
        }

        /// <summary>
        /// Gets all available environment names
        /// </summary>
        /// <returns>List of environment names</returns>
        public static List<string> GetAvailableEnvironments()
        {
            return new List<string> { "development", "production" };
        }
    }
}
using System;
using System.Collections.Generic;
using System.Linq;
using QuantConnect;
using QuantConnect.Algorithm;
using QuantConnect.Data;
using QuantConnect.Securities;
using QuantConnect.Securities.Option;
using CoreAlgo.Architecture.Core.Models;

namespace CoreAlgo.Architecture.Core.Configuration
{
    /// <summary>
    /// Lightweight entry restriction checks that work with StrategyConfig parameters.
    /// QC-First approach - uses QC's native market data and portfolio state.
    /// </summary>
    public class EntryRestrictions
    {
        private readonly StrategyConfig _config;
        private readonly QCAlgorithm _algorithm;

        public EntryRestrictions(StrategyConfig config, QCAlgorithm algorithm)
        {
            _config = config ?? throw new ArgumentNullException(nameof(config));
            _algorithm = algorithm ?? throw new ArgumentNullException(nameof(algorithm));
        }

        /// <summary>
        /// Check if all entry restrictions are satisfied for a potential trade.
        /// Returns true if trade is allowed, false otherwise.
        /// </summary>
        public bool CanEnterPosition(Symbol symbol, Slice slice, out string reason)
        {
            reason = string.Empty;

            var shouldLog = _config.LogEntryRestrictions;
            if (shouldLog)
            {
                _algorithm.Debug($"[DEBUG EntryRestrictions] Checking restrictions at {slice.Time}");
            }

            // 1. Trading Hours Check
            var withinHours = IsWithinTradingHours(slice.Time);
            if (shouldLog)
            {
                _algorithm.Debug($"  [1/5] Trading Hours: {withinHours} | Current: {slice.Time.TimeOfDay} | Window: {_config.TradingStartTime}-{_config.TradingEndTime}");
            }
            if (!withinHours)
            {
                reason = $"Outside trading hours ({_config.TradingStartTime}-{_config.TradingEndTime})";
                return false;
            }

            // 2. Max Positions Check (skip if MaxPositions <= 0, which means no limit)
            var hasCapacity = true;
            var currentPositionCount = 0;
            if (_config.MaxPositions > 0)
            {
                hasCapacity = HasCapacityForNewPosition();
                currentPositionCount = _algorithm.Portfolio.Where(kvp => kvp.Value.Invested).Count();
                if (shouldLog)
                {
                    _algorithm.Debug($"  [2/5] Max Positions: {hasCapacity} | Current: {currentPositionCount}/{_config.MaxPositions}");
                }
                if (!hasCapacity)
                {
                    reason = $"Max positions reached ({_config.MaxPositions})";
                    return false;
                }
            }
            else if (shouldLog)
            {
                _algorithm.Debug($"  [2/5] Max Positions: DISABLED (MaxPositions={_config.MaxPositions})");
            }

            // 3. Overlap Prevention Check (respects config)
            if (_config.EnableOverlapPrevention && _config.MaxPositionsPerUnderlying > 0)
            {
                var existingCount = CountExistingPositions(symbol);
                var limit = _config.MaxPositionsPerUnderlying;
                
                if (shouldLog)
                {
                    _algorithm.Debug($"  [3/5] Overlap Prevention: Enabled | Existing: {existingCount}/{limit} | Mode={_config.OverlapPreventionMode}");
                }

                if (existingCount >= limit)
                {
                    var mode = (_config.OverlapPreventionMode ?? "Block").ToUpperInvariant();
                    var msg = $"Underlying limit reached ({existingCount}/{limit}) for {symbol.Underlying ?? symbol}";
                    
                    if (mode == "BLOCK")
                    {
                        reason = msg;
                        return false;
                    }
                    else if (mode == "WARN")
                    {
                        _algorithm.Debug($"[ENTRY WARN] {msg}");
                    }
                    else // LOG mode
                    {
                        _algorithm.Debug($"[ENTRY LOG] {msg}");
                    }
                }
            }
            else if (shouldLog)
            {
                _algorithm.Debug($"  [3/5] Overlap Prevention: DISABLED - allowing multiple entries");
            }

            // 4. Available Capital Check
            var hasSufficientCapital = HasSufficientCapital();
            if (shouldLog)
            {
                var portfolioValue = _algorithm.Portfolio.TotalPortfolioValue;
                var requiredCapital = portfolioValue * _config.AllocationPerPosition;
                var availableCash = _algorithm.Portfolio.Cash;
                _algorithm.Debug($"  [4/5] Capital: {hasSufficientCapital} | Available: ${availableCash:F2} | Required: ${requiredCapital:F2} (Allocation: {_config.AllocationPerPosition:P0})");
            }
            if (!hasSufficientCapital)
            {
                reason = "Insufficient capital for new position";
                return false;
            }

            // 5. Volatility Check (if configured)
            if (_config.MinImpliedVolatility > 0)
            {
                var meetsVol = MeetsVolatilityRequirement(symbol, slice);
                if (shouldLog)
                {
                    _algorithm.Debug($"  [5/5] Volatility: {meetsVol} | Min IV: {_config.MinImpliedVolatility:P0}");
                }
                if (!meetsVol)
                {
                    reason = $"Implied volatility below minimum ({_config.MinImpliedVolatility:P0})";
                    return false;
                }
            }
            else if (shouldLog)
            {
                _algorithm.Debug($"  [5/5] Volatility: DISABLED (MinIV={_config.MinImpliedVolatility:P0})");
            }

            // All checks passed
            if (shouldLog)
            {
                _algorithm.Debug($"[DEBUG EntryRestrictions] ✓ ALL CHECKS PASSED");
            }
            return true;
        }

        /// <summary>
        /// Check if entry is allowed for options based on delta requirements.
        /// </summary>
        public bool CanEnterOptionPosition(dynamic contract, out string reason)
        {
            reason = string.Empty;

            // Check if Greeks are available
            if (contract.Greeks == null)
            {
                reason = "Greeks not available for option contract";
                return false;
            }

            var delta = Math.Abs(contract.Greeks.Delta);

            // Check delta is within configured range
            if (delta < _config.EntryDeltaMin || delta > _config.EntryDeltaMax)
            {
                reason = $"Delta {delta:F2} outside range [{_config.EntryDeltaMin:F2}-{_config.EntryDeltaMax:F2}]";
                return false;
            }

            return true;
        }

        /// <summary>
        /// Check if current time is within configured trading hours.
        /// If both TradingStartTime and TradingEndTime are 00:00:00, the window is disabled (always returns true).
        /// Handles cross-midnight windows (e.g., 22:00–02:00).
        /// </summary>
        private bool IsWithinTradingHours(DateTime currentTime)
        {
            var timeOfDay = currentTime.TimeOfDay;
            
            // Special case: 00:00–00:00 means "no restriction" (always allow)
            if (_config.TradingStartTime == TimeSpan.Zero && _config.TradingEndTime == TimeSpan.Zero)
            {
                return true;
            }
            
            // Handle cross-midnight windows (e.g., 22:00–02:00)
            if (_config.TradingEndTime < _config.TradingStartTime)
            {
                // Allow if time >= start OR time <= end
                return timeOfDay >= _config.TradingStartTime || timeOfDay <= _config.TradingEndTime;
            }
            
            // Normal window (e.g., 09:30–15:30)
            return timeOfDay >= _config.TradingStartTime && timeOfDay <= _config.TradingEndTime;
        }

        /// <summary>
        /// Check if we have capacity for a new position based on MaxPositions.
        /// Note: This should only be called when MaxPositions > 0 (checked in CanEnterPosition)
        /// </summary>
        private bool HasCapacityForNewPosition()
        {
            var currentPositions = _algorithm.Portfolio
                .Where(kvp => kvp.Value.Invested)
                .Count();
            
            // MaxPositions should be > 0 when this is called
            // If MaxPositions is 0 or negative, caller should skip this check
            return currentPositions < _config.MaxPositions;
        }

        /// <summary>
        /// Count existing positions for the given symbol or its underlying.
        /// For options, counts all positions on the same underlying.
        /// For equities, counts positions on that specific symbol.
        /// </summary>
        private int CountExistingPositions(Symbol symbol)
        {
            // For options, check positions on the underlying
            if (symbol.SecurityType == SecurityType.Option)
            {
                var underlying = symbol.Underlying;
                return _algorithm.Portfolio.Count(kvp => 
                    kvp.Value.Invested && 
                    (kvp.Key == underlying || (kvp.Key.SecurityType == SecurityType.Option && kvp.Key.Underlying == underlying)));
            }

            // For equities/futures, count direct positions on this symbol
            return _algorithm.Portfolio.Count(kvp => kvp.Key == symbol && kvp.Value.Invested);
        }

        /// <summary>
        /// Check if we have sufficient capital for a new position.
        /// </summary>
        private bool HasSufficientCapital()
        {
            var portfolioValue = _algorithm.Portfolio.TotalPortfolioValue;
            var requiredCapital = portfolioValue * _config.AllocationPerPosition;
            var availableCash = _algorithm.Portfolio.Cash;

            // Need at least the allocation amount in cash
            return availableCash >= requiredCapital;
        }

        /// <summary>
        /// Check if the symbol meets minimum volatility requirements.
        /// This is primarily for options strategies.
        /// </summary>
        private bool MeetsVolatilityRequirement(Symbol symbol, Slice slice)
        {
            // For options, IV check would be done in the strategy template
            // where the option chain data is available
            // For now, return true to allow entry restrictions to focus on position/capital limits
            return true;
        }

        /// <summary>
        /// Get a summary of current restriction status.
        /// Useful for logging and debugging.
        /// </summary>
        public Dictionary<string, object> GetRestrictionStatus()
        {
            var currentTime = _algorithm.Time;
            var activePositions = _algorithm.Portfolio.Where(kvp => kvp.Value.Invested).Count();
            var portfolioValue = _algorithm.Portfolio.TotalPortfolioValue;
            var availableCash = _algorithm.Portfolio.Cash;

            return new Dictionary<string, object>
            {
                ["CurrentTime"] = currentTime,
                ["TradingHoursActive"] = IsWithinTradingHours(currentTime),
                ["ActivePositions"] = activePositions,
                ["MaxPositions"] = _config.MaxPositions,
                ["AvailableSlots"] = _config.MaxPositions - activePositions,
                ["PortfolioValue"] = portfolioValue,
                ["AvailableCash"] = availableCash,
                ["AllocationPerPosition"] = _config.AllocationPerPosition,
                ["RequiredCapitalPerPosition"] = portfolioValue * _config.AllocationPerPosition
            };
        }
    }
}
using System;
using System.Collections.Generic;
using System.Linq;
using QuantConnect;
using QuantConnect.Algorithm;
using QuantConnect.Data;
using QuantConnect.Securities;
using QuantConnect.Securities.Option;
using CoreAlgo.Architecture.Core.Models;

namespace CoreAlgo.Architecture.Core.Configuration
{
    /// <summary>
    /// Lightweight exit restriction checks that work with StrategyConfig parameters.
    /// QC-First approach - uses QC's native portfolio metrics and market data.
    /// </summary>
    public class ExitRestrictions
    {
        private readonly StrategyConfig _config;
        private readonly QCAlgorithm _algorithm;
        private readonly Dictionary<Symbol, DateTime> _positionEntryTimes;

        public ExitRestrictions(StrategyConfig config, QCAlgorithm algorithm)
        {
            _config = config ?? throw new ArgumentNullException(nameof(config));
            _algorithm = algorithm ?? throw new ArgumentNullException(nameof(algorithm));
            _positionEntryTimes = new Dictionary<Symbol, DateTime>();
        }

        /// <summary>
        /// Record when a position was entered for time-based exit rules.
        /// </summary>
        public void RecordPositionEntry(Symbol symbol, DateTime entryTime)
        {
            _positionEntryTimes[symbol] = entryTime;
        }

        /// <summary>
        /// Check if a position should be exited based on configured rules.
        /// Returns true if position should be closed, false otherwise.
        /// </summary>
        public bool ShouldExitPosition(Symbol symbol, Slice slice, out string reason)
        {
            reason = string.Empty;

            var holding = _algorithm.Portfolio[symbol];
            if (!holding.Invested)
            {
                reason = "No position to exit";
                return false;
            }

            // 1. Profit Target Check
            if (IsProfitTargetReached(holding))
            {
                reason = $"Profit target reached ({holding.UnrealizedProfitPercent:P2} >= {_config.ProfitTarget:P2})";
                return true;
            }

            // 2. Stop Loss Check
            if (IsStopLossTriggered(holding))
            {
                reason = $"Stop loss triggered ({holding.UnrealizedProfitPercent:P2} <= {_config.StopLoss:P2})";
                return true;
            }

            // 3. Time-Based Exit Check
            if (IsMaxTimeReached(symbol, slice.Time))
            {
                var daysInTrade = GetDaysInTrade(symbol, slice.Time);
                reason = $"Max time in trade reached ({daysInTrade:F1} days >= {_config.MaxDaysInTrade} days)";
                return true;
            }

            // 4. Trading Hours Exit (optional - exit outside hours)
            if (!IsWithinTradingHours(slice.Time))
            {
                // Optional: some strategies may want to exit outside trading hours
                // For now, we don't force exit, but strategies can override
            }

            // No exit conditions met
            return false;
        }

        /// <summary>
        /// Check if an option position should be exited based on delta.
        /// </summary>
        public bool ShouldExitOptionPosition(Symbol symbol, dynamic contract, out string reason)
        {
            reason = string.Empty;

            // First check standard exit rules
            if (ShouldExitPosition(symbol, _algorithm.CurrentSlice, out reason))
            {
                return true;
            }

            // Check option-specific exit rules
            if (contract.Greeks != null)
            {
                var delta = Math.Abs(contract.Greeks.Delta);
                
                // Exit if delta drops below threshold
                if (delta <= _config.ExitDelta)
                {
                    reason = $"Delta exit triggered ({delta:F3} <= {_config.ExitDelta:F3})";
                    return true;
                }
            }

            // Check days to expiration
            var daysToExpiry = (contract.Expiry - _algorithm.Time).TotalDays;
            if (daysToExpiry <= 1) // Exit if expiring tomorrow
            {
                reason = $"Near expiration ({daysToExpiry:F1} days)";
                return true;
            }

            return false;
        }

        /// <summary>
        /// Check if profit target has been reached.
        /// Returns false if profit target is disabled (0).
        /// </summary>
        private bool IsProfitTargetReached(SecurityHolding holding)
        {
            // Skip check if profit target is disabled
            if (_config.ProfitTarget == 0) return false;
            
            return holding.UnrealizedProfitPercent >= _config.ProfitTarget;
        }

        /// <summary>
        /// Check if stop loss has been triggered.
        /// Returns false if stop loss is disabled (0).
        /// </summary>
        private bool IsStopLossTriggered(SecurityHolding holding)
        {
            // Skip check if stop loss is disabled
            if (_config.StopLoss == 0) return false;
            
            return holding.UnrealizedProfitPercent <= _config.StopLoss;
        }

        /// <summary>
        /// Check if position has been held for maximum allowed time.
        /// </summary>
        private bool IsMaxTimeReached(Symbol symbol, DateTime currentTime)
        {
            if (!_positionEntryTimes.ContainsKey(symbol))
                return false;

            var daysInTrade = (currentTime - _positionEntryTimes[symbol]).TotalDays;
            return daysInTrade >= _config.MaxDaysInTrade;
        }

        /// <summary>
        /// Get the number of days a position has been held.
        /// </summary>
        private double GetDaysInTrade(Symbol symbol, DateTime currentTime)
        {
            if (!_positionEntryTimes.ContainsKey(symbol))
                return 0;

            return (currentTime - _positionEntryTimes[symbol]).TotalDays;
        }

        /// <summary>
        /// Check if current time is within trading hours.
        /// </summary>
        private bool IsWithinTradingHours(DateTime currentTime)
        {
            var timeOfDay = currentTime.TimeOfDay;
            return timeOfDay >= _config.TradingStartTime && timeOfDay <= _config.TradingEndTime;
        }

        /// <summary>
        /// Get exit urgency level (for prioritizing exits).
        /// Higher values mean more urgent exit.
        /// </summary>
        public double GetExitUrgency(Symbol symbol)
        {
            var holding = _algorithm.Portfolio[symbol];
            if (!holding.Invested)
                return 0;

            var urgency = 0.0;

            // Stop loss is most urgent (if enabled)
            if (_config.StopLoss != 0 && holding.UnrealizedProfitPercent <= _config.StopLoss)
            {
                urgency = 1.0;
            }
            // Profit target is high priority (if enabled)
            else if (_config.ProfitTarget != 0 && holding.UnrealizedProfitPercent >= _config.ProfitTarget)
            {
                urgency = 0.8;
            }
            // Time-based exit increases urgency as we approach max days
            else if (_positionEntryTimes.ContainsKey(symbol))
            {
                var daysInTrade = GetDaysInTrade(symbol, _algorithm.Time);
                var timeUrgency = Math.Min(daysInTrade / _config.MaxDaysInTrade, 1.0);
                urgency = Math.Max(urgency, timeUrgency * 0.6);
            }

            return urgency;
        }

        /// <summary>
        /// Get a summary of exit conditions for all positions.
        /// Useful for logging and strategy decisions.
        /// </summary>
        public List<PositionExitStatus> GetAllPositionExitStatus()
        {
            var results = new List<PositionExitStatus>();

            foreach (var kvp in _algorithm.Portfolio.Where(p => p.Value.Invested))
            {
                var symbol = kvp.Key;
                var holding = kvp.Value;
                var shouldExit = ShouldExitPosition(symbol, _algorithm.CurrentSlice, out var reason);
                var urgency = GetExitUrgency(symbol);
                var daysHeld = _positionEntryTimes.ContainsKey(symbol) 
                    ? GetDaysInTrade(symbol, _algorithm.Time) 
                    : 0;

                results.Add(new PositionExitStatus
                {
                    Symbol = symbol,
                    UnrealizedProfitPercent = holding.UnrealizedProfitPercent,
                    DaysHeld = daysHeld,
                    ShouldExit = shouldExit,
                    ExitReason = reason,
                    ExitUrgency = urgency
                });
            }

            return results.OrderByDescending(r => r.ExitUrgency).ToList();
        }

        /// <summary>
        /// Clear entry time for a symbol after position is closed.
        /// </summary>
        public void ClearPositionEntry(Symbol symbol)
        {
            _positionEntryTimes.Remove(symbol);
        }
    }

    /// <summary>
    /// Status information for position exit decisions.
    /// </summary>
    public class PositionExitStatus
    {
        public Symbol Symbol { get; set; }
        public decimal UnrealizedProfitPercent { get; set; }
        public double DaysHeld { get; set; }
        public bool ShouldExit { get; set; }
        public string ExitReason { get; set; }
        public double ExitUrgency { get; set; }
    }
}
using System;
using System.Collections.Generic;
using System.Linq;
using QuantConnect.Orders;
using QuantConnect.Scheduling;

namespace CoreAlgo.Architecture.Core.Execution
{
    /// <summary>
    /// Tracks combo orders as single atomic units for progressive net pricing.
    /// Unlike single-leg orders, combo orders are managed through their collective net price.
    /// </summary>
    public class ComboOrderTracker
    {
        /// <summary>
        /// List of order tickets returned by ComboLimitOrder (one per leg)
        /// </summary>
        public List<OrderTicket> ComboTickets { get; }

        /// <summary>
        /// The legs that define this combo order structure
        /// </summary>
        public List<Leg> Legs { get; }

        /// <summary>
        /// Current net limit price for the combo order
        /// </summary>
        public decimal CurrentNetPrice { get; private set; }

        /// <summary>
        /// Last market quote used for pricing calculations
        /// </summary>
        public ComboQuote LastQuote { get; private set; }

        /// <summary>
        /// Overall direction of the combo order (Buy = net debit, Sell = net credit)
        /// </summary>
        public OrderDirection ComboDirection { get; }

        /// <summary>
        /// Smart pricing mode being used for this combo
        /// </summary>
        public SmartPricingMode PricingMode { get; }

        /// <summary>
        /// Current attempt number for progressive pricing (1-based)
        /// </summary>
        public int AttemptNumber { get; private set; }

        /// <summary>
        /// When this combo order was first placed
        /// </summary>
        public DateTime StartTime { get; }

        /// <summary>
        /// Scheduled event for the next pricing update (if any)
        /// </summary>
        public ScheduledEvent ScheduledEvent { get; set; }

        /// <summary>
        /// Whether all legs of the combo have been filled
        /// </summary>
        public bool IsCompletelyFilled => ComboTickets.All(ticket => 
            ticket.Status == OrderStatus.Filled);

        /// <summary>
        /// Whether any leg has been partially filled
        /// </summary>
        public bool HasPartialFills => ComboTickets.Any(ticket => 
            ticket.Status == OrderStatus.PartiallyFilled || 
            (ticket.Status == OrderStatus.Filled && ticket.QuantityFilled != ticket.Quantity));

        /// <summary>
        /// Whether the combo order is still active (not filled, cancelled, or invalid)
        /// </summary>
        public bool IsActive => ComboTickets.Any(ticket => 
            ticket.Status == OrderStatus.Submitted || 
            ticket.Status == OrderStatus.PartiallyFilled);

        /// <summary>
        /// Gets the primary order ticket (first leg) for identification purposes
        /// </summary>
        public OrderTicket PrimaryTicket => ComboTickets.FirstOrDefault();

        /// <summary>
        /// Gets the primary order ID for logging and tracking
        /// </summary>
        public int PrimaryOrderId => PrimaryTicket?.OrderId ?? -1;

        /// <summary>
        /// Creates a new combo order tracker
        /// </summary>
        /// <param name="comboTickets">Order tickets returned by ComboLimitOrder</param>
        /// <param name="legs">Legs that define the combo structure</param>
        /// <param name="initialQuote">Initial market quote used for pricing</param>
        /// <param name="comboDirection">Overall direction of the combo order</param>
        /// <param name="pricingMode">Smart pricing mode to use</param>
        /// <param name="initialNetPrice">Initial net limit price</param>
        public ComboOrderTracker(List<OrderTicket> comboTickets, List<Leg> legs, ComboQuote initialQuote,
            OrderDirection comboDirection, SmartPricingMode pricingMode, decimal initialNetPrice)
        {
            ComboTickets = new List<OrderTicket>(comboTickets ?? throw new ArgumentNullException(nameof(comboTickets)));
            Legs = new List<Leg>(legs ?? throw new ArgumentNullException(nameof(legs)));
            LastQuote = initialQuote ?? throw new ArgumentNullException(nameof(initialQuote));
            ComboDirection = comboDirection;
            PricingMode = pricingMode;
            CurrentNetPrice = initialNetPrice;
            AttemptNumber = 1;
            StartTime = DateTime.UtcNow;

            if (ComboTickets.Count == 0)
                throw new ArgumentException("Combo tickets cannot be empty", nameof(comboTickets));
            
            if (Legs.Count == 0)
                throw new ArgumentException("Legs cannot be empty", nameof(legs));
        }

        /// <summary>
        /// Updates the net price and quote information for the next pricing attempt
        /// </summary>
        /// <param name="newNetPrice">New net limit price</param>
        /// <param name="newQuote">Updated market quote</param>
        public void UpdateNetPrice(decimal newNetPrice, ComboQuote newQuote)
        {
            CurrentNetPrice = newNetPrice;
            LastQuote = newQuote ?? throw new ArgumentNullException(nameof(newQuote));
            AttemptNumber++;
        }

        /// <summary>
        /// Records a partial fill event for tracking purposes
        /// </summary>
        /// <param name="orderEvent">Order event representing the partial fill</param>
        public void UpdatePartialFill(OrderEvent orderEvent)
        {
            // For combo orders, we mainly track this for logging
            // The actual fill logic is handled by QuantConnect's combo order system
            // We could enhance this later to track per-leg fill status if needed
        }

        /// <summary>
        /// Gets summary information about this combo order for logging
        /// </summary>
        /// <returns>Formatted string with combo order details</returns>
        public string GetSummary()
        {
            var status = IsCompletelyFilled ? "FILLED" : 
                        HasPartialFills ? "PARTIAL" : 
                        IsActive ? "ACTIVE" : "INACTIVE";

            var runtime = DateTime.UtcNow - StartTime;

            return $"Combo Order {PrimaryOrderId}: {Legs.Count} legs, " +
                   $"NetPrice=${CurrentNetPrice:F2}, Attempt={AttemptNumber}, " +
                   $"Status={status}, Runtime={runtime.TotalSeconds:F0}s, " +
                   $"Mode={PricingMode}";
        }

        /// <summary>
        /// Gets detailed status of all leg tickets
        /// </summary>
        /// <returns>String with status of each leg</returns>
        public string GetLegStatus()
        {
            var legStatuses = ComboTickets.Select((ticket, index) => 
                $"Leg{index + 1}[{ticket.OrderId}]: {ticket.Status} " +
                $"({ticket.QuantityFilled}/{ticket.Quantity})");
            
            return string.Join(", ", legStatuses);
        }

        /// <summary>
        /// Calculates total runtime since combo order was placed
        /// </summary>
        /// <returns>Time elapsed since order placement</returns>
        public TimeSpan GetRuntime()
        {
            return DateTime.UtcNow - StartTime;
        }

        /// <summary>
        /// Determines if this combo order should continue with progressive pricing
        /// </summary>
        /// <param name="maxAttempts">Maximum number of attempts allowed</param>
        /// <param name="maxRuntime">Maximum runtime before giving up</param>
        /// <returns>True if progressive pricing should continue</returns>
        public bool ShouldContinuePricing(int maxAttempts, TimeSpan maxRuntime)
        {
            if (!IsActive)
                return false;

            if (AttemptNumber >= maxAttempts)
                return false;

            if (GetRuntime() >= maxRuntime)
                return false;

            return true;
        }
    }
}
using System;
using System.Collections.Generic;
using QuantConnect.Orders;

namespace CoreAlgo.Architecture.Core.Execution
{
    /// <summary>
    /// Pricing engine for combo orders that calculates net limit prices and progressive pricing
    /// for multi-leg options strategies. Applies smart pricing logic to the entire combo as a unit.
    /// </summary>
    public class ComboPricingEngine
    {
        private readonly SmartPricingMode _mode;
        private readonly decimal _maxNetSpreadWidth;

        /// <summary>
        /// Creates a new combo pricing engine with the specified mode and constraints
        /// </summary>
        /// <param name="mode">Smart pricing mode (Normal, Fast, Patient)</param>
        /// <param name="maxNetSpreadWidth">Maximum acceptable net spread width</param>
        public ComboPricingEngine(SmartPricingMode mode, decimal maxNetSpreadWidth = 5.0m)
        {
            _mode = mode;
            _maxNetSpreadWidth = maxNetSpreadWidth;
        }

        /// <summary>
        /// Calculates the initial net limit price for a combo order based on current market conditions
        /// </summary>
        /// <param name="legs">List of legs in the combo order</param>
        /// <param name="comboQuote">Current market quote for the combo</param>
        /// <param name="orderDirection">Overall direction of the combo (Buy = paying net debit, Sell = receiving net credit)</param>
        /// <returns>Initial net limit price, or null if combo should not be priced intelligently</returns>
        public decimal? CalculateInitialComboPrice(List<Leg> legs, ComboQuote comboQuote, OrderDirection orderDirection)
        {
            if (legs == null || legs.Count == 0 || comboQuote == null || !comboQuote.IsValid)
                return null;

            // Skip smart pricing if net spread is too wide
            if (comboQuote.NetSpread > _maxNetSpreadWidth)
                return null;

            // Start at net mid-price for both buy and sell combos
            // This gives us the best initial price while still being realistic
            return comboQuote.NetMid;
        }

        /// <summary>
        /// Calculates the next progressive price step for an existing combo order
        /// </summary>
        /// <param name="currentNetPrice">Current net limit price of the combo order</param>
        /// <param name="comboQuote">Current market quote for the combo</param>
        /// <param name="orderDirection">Overall direction of the combo</param>
        /// <param name="attemptNumber">Current attempt number (1-based)</param>
        /// <returns>Next progressive net price, or null if no more steps available</returns>
        public decimal? CalculateNextComboPrice(decimal currentNetPrice, ComboQuote comboQuote, 
            OrderDirection orderDirection, int attemptNumber)
        {
            if (comboQuote == null || !comboQuote.IsValid)
                return null;

            var maxAttempts = GetMaxAttempts();
            if (attemptNumber >= maxAttempts)
                return null;

            // Calculate how far to progress toward the target price
            var progressionRatio = CalculateProgressionRatio(attemptNumber, maxAttempts);
            
            decimal targetPrice;
            decimal startPrice = comboQuote.NetMid;

            if (orderDirection == OrderDirection.Buy)
            {
                // For buy combos (net debit): progress from mid toward ask
                // We're willing to pay more to get filled
                targetPrice = comboQuote.NetAsk;
            }
            else
            {
                // For sell combos (net credit): progress from mid toward bid  
                // We're willing to accept less to get filled
                targetPrice = comboQuote.NetBid;
            }

            // Calculate next price using linear interpolation
            var nextPrice = startPrice + (targetPrice - startPrice) * progressionRatio;

            // Ensure we don't go backwards or exceed target
            if (orderDirection == OrderDirection.Buy)
            {
                nextPrice = Math.Max(nextPrice, currentNetPrice);
                nextPrice = Math.Min(nextPrice, targetPrice);
            }
            else
            {
                nextPrice = Math.Min(nextPrice, currentNetPrice);
                nextPrice = Math.Max(nextPrice, targetPrice);
            }

            // Round to nearest cent for practical execution
            return Math.Round(nextPrice, 2);
        }

        /// <summary>
        /// Gets the pricing interval between attempts based on the current mode
        /// </summary>
        /// <returns>Time interval between pricing updates</returns>
        public TimeSpan GetPricingInterval()
        {
            return _mode switch
            {
                SmartPricingMode.Fast => TimeSpan.FromSeconds(5),      // 3 steps over 15 seconds
                SmartPricingMode.Normal => TimeSpan.FromSeconds(10),   // 4 steps over 40 seconds
                SmartPricingMode.Patient => TimeSpan.FromSeconds(20),  // 5 steps over 100 seconds
                _ => TimeSpan.FromSeconds(10)
            };
        }

        /// <summary>
        /// Gets the maximum number of pricing attempts for the current mode
        /// </summary>
        /// <returns>Maximum number of attempts</returns>
        public int GetMaxAttempts()
        {
            return _mode switch
            {
                SmartPricingMode.Fast => 3,
                SmartPricingMode.Normal => 4,
                SmartPricingMode.Patient => 5,
                _ => 4
            };
        }

        /// <summary>
        /// Validates if the combo pricing engine should attempt to improve the price
        /// </summary>
        /// <param name="comboQuote">Current combo market quote</param>
        /// <param name="orderDirection">Overall combo direction</param>
        /// <returns>True if smart combo pricing should be attempted</returns>
        public bool ShouldAttemptComboPricing(ComboQuote comboQuote, OrderDirection orderDirection)
        {
            if (comboQuote == null || !comboQuote.IsValid)
                return false;

            // Skip if net spread is too wide (slippage would be excessive)
            if (comboQuote.NetSpread > _maxNetSpreadWidth)
                return false;

            // Skip if net spread is too narrow (little room for improvement)
            if (comboQuote.NetSpread < 0.10m)
                return false;

            return true;
        }

        /// <summary>
        /// Determines the overall direction of a combo order based on its legs
        /// </summary>
        /// <param name="legs">List of legs in the combo</param>
        /// <returns>Buy if net debit expected, Sell if net credit expected</returns>
        public static OrderDirection DetermineComboDirection(List<Leg> legs)
        {
            if (legs == null || legs.Count == 0)
                return OrderDirection.Buy; // Default

            // Simple heuristic: if we have more short legs than long legs, it's likely a credit spread
            // This works for most common strategies (Iron Condor, Credit Spreads, etc.)
            int longLegs = 0;
            int shortLegs = 0;

            foreach (var leg in legs)
            {
                if (leg.Quantity > 0)
                    longLegs += Math.Abs(leg.Quantity);
                else if (leg.Quantity < 0)
                    shortLegs += Math.Abs(leg.Quantity);
            }

            // If more short than long, assume it's a credit spread (we receive money)
            return shortLegs > longLegs ? OrderDirection.Sell : OrderDirection.Buy;
        }

        /// <summary>
        /// Calculates the progression ratio for moving from mid-price toward target
        /// </summary>
        /// <param name="attemptNumber">Current attempt (1-based)</param>
        /// <param name="maxAttempts">Maximum number of attempts</param>
        /// <returns>Ratio from 0.0 (start) to 1.0 (target)</returns>
        private decimal CalculateProgressionRatio(int attemptNumber, int maxAttempts)
        {
            if (attemptNumber <= 0 || maxAttempts <= 1)
                return 0m;

            // Linear progression: attempt 1 = 25%, attempt 2 = 50%, etc.
            var ratio = (decimal)attemptNumber / maxAttempts;
            
            // Cap at 90% to avoid hitting exact bid/ask (leave room for market movement)
            return Math.Min(ratio, 0.90m);
        }
    }
}
using System;
using System.Collections.Generic;
using System.Linq;
using QuantConnect;
using QuantConnect.Orders;
using QuantConnect.Securities;

namespace CoreAlgo.Architecture.Core.Execution
{
    /// <summary>
    /// Represents net bid/ask pricing for a combo order based on individual leg quotes.
    /// Used for calculating intelligent limit prices for multi-leg options strategies.
    /// </summary>
    public class ComboQuote
    {
        /// <summary>
        /// Net bid price for the combo order (sum of leg bids adjusted for direction)
        /// </summary>
        public decimal NetBid { get; }

        /// <summary>
        /// Net ask price for the combo order (sum of leg asks adjusted for direction)
        /// </summary>
        public decimal NetAsk { get; }

        /// <summary>
        /// Mid-point between net bid and net ask
        /// </summary>
        public decimal NetMid => (NetBid + NetAsk) / 2;

        /// <summary>
        /// Net spread width (ask - bid)
        /// </summary>
        public decimal NetSpread => NetAsk - NetBid;

        /// <summary>
        /// Individual leg quotes used to calculate net pricing
        /// </summary>
        public Dictionary<Symbol, Quote> LegQuotes { get; }

        /// <summary>
        /// Timestamp when this quote was calculated
        /// </summary>
        public DateTime Timestamp { get; }

        /// <summary>
        /// Whether all legs have valid quotes (bid > 0 and ask > bid)
        /// </summary>
        public bool IsValid => LegQuotes.Values.All(q => q.Bid > 0 && q.Ask > q.Bid) && NetSpread >= 0;

        /// <summary>
        /// Creates a new ComboQuote from individual leg quotes and their quantities
        /// </summary>
        /// <param name="legs">List of legs with symbols and quantities</param>
        /// <param name="legQuotes">Quotes for each leg symbol</param>
        /// <param name="timestamp">When this quote was calculated</param>
        public ComboQuote(List<Leg> legs, Dictionary<Symbol, Quote> legQuotes, DateTime timestamp)
        {
            if (legs == null || legs.Count == 0)
                throw new ArgumentException("Legs cannot be null or empty", nameof(legs));
            
            if (legQuotes == null)
                throw new ArgumentNullException(nameof(legQuotes));

            LegQuotes = new Dictionary<Symbol, Quote>(legQuotes);
            Timestamp = timestamp;

            // Calculate net bid and ask based on leg direction
            decimal netBid = 0;
            decimal netAsk = 0;

            foreach (var leg in legs)
            {
                if (!legQuotes.TryGetValue(leg.Symbol, out var quote))
                {
                    throw new ArgumentException($"Missing quote for leg symbol {leg.Symbol}", nameof(legQuotes));
                }

                // For buy legs (positive quantity): we pay the ask and receive the bid
                // For sell legs (negative quantity): we receive the bid and pay the ask
                if (leg.Quantity > 0)
                {
                    // Buying this leg: pay ask price, receive bid price
                    netAsk += quote.Ask * Math.Abs(leg.Quantity);
                    netBid += quote.Bid * Math.Abs(leg.Quantity);
                }
                else if (leg.Quantity < 0)
                {
                    // Selling this leg: receive bid price, pay ask price
                    netBid -= quote.Ask * Math.Abs(leg.Quantity);  // We receive when selling (positive for us)
                    netAsk -= quote.Bid * Math.Abs(leg.Quantity);  // We pay when selling (less negative = better)
                }
            }

            NetBid = netBid;
            NetAsk = netAsk;
        }

        /// <summary>
        /// Creates a ComboQuote from a list of legs and securities (fetches current quotes)
        /// </summary>
        /// <param name="legs">List of legs with symbols and quantities</param>
        /// <param name="securities">Securities collection to get current quotes from</param>
        /// <returns>ComboQuote with current market data, or null if quotes unavailable</returns>
        public static ComboQuote FromSecurities(List<Leg> legs, SecurityManager securities)
        {
            if (legs == null || legs.Count == 0)
                return null;

            try
            {
                var legQuotes = new Dictionary<Symbol, Quote>();
                var timestamp = DateTime.UtcNow;

                foreach (var leg in legs)
                {
                    var security = securities[leg.Symbol];
                    var quote = GetCurrentQuote(security);
                    
                    if (quote == null)
                        return null; // Missing quote for any leg invalidates the entire combo quote

                    legQuotes[leg.Symbol] = quote;
                }

                return new ComboQuote(legs, legQuotes, timestamp);
            }
            catch
            {
                return null;
            }
        }

        /// <summary>
        /// Gets the current market quote for a security (similar to SmartPricingExecutionModel logic)
        /// </summary>
        private static Quote GetCurrentQuote(Security security)
        {
            try
            {
                // Try to get the most recent quote using Cache.GetData for QuoteBar
                var quoteBar = security.Cache.GetData<QuantConnect.Data.Market.QuoteBar>();
                if (quoteBar != null && quoteBar.Bid.Close > 0 && quoteBar.Ask.Close > 0)
                {
                    return new Quote(quoteBar.Bid.Close, quoteBar.Ask.Close);
                }

                // Fall back to using last price if quote is not available
                var price = security.Price;
                if (price > 0)
                {
                    // Estimate spread as 0.5% of price (conservative estimate for options)
                    var estimatedSpread = price * 0.005m;
                    return new Quote(price - estimatedSpread / 2, price + estimatedSpread / 2);
                }

                return null;
            }
            catch
            {
                return null;
            }
        }

        /// <summary>
        /// Returns a string representation of the combo quote
        /// </summary>
        public override string ToString()
        {
            return $"ComboQuote: NetBid={NetBid:F2}, NetAsk={NetAsk:F2}, NetMid={NetMid:F2}, NetSpread={NetSpread:F2}, Valid={IsValid}";
        }

        /// <summary>
        /// Determines if this combo quote is stale based on age threshold
        /// </summary>
        /// <param name="maxAge">Maximum age before considering stale</param>
        /// <returns>True if the quote is older than maxAge</returns>
        public bool IsStale(TimeSpan maxAge)
        {
            return DateTime.UtcNow - Timestamp > maxAge;
        }
    }
}
using System;

namespace CoreAlgo.Architecture.Core.Execution
{
    /// <summary>
    /// Fast SmartPricing strategy: 3 steps over 15 seconds (5-second intervals)
    /// 
    /// This aggressive approach is suitable for high-volume trading or when quick fills
    /// are more important than optimal pricing. Moves quickly toward market prices.
    /// 
    /// Timing: Step every 5 seconds for up to 15 seconds total
    /// Progression: Mid → 50% → 80% → Ask/Bid
    /// </summary>
    public class FastPricingStrategy : PricingStrategy
    {
        /// <summary>
        /// Fast pricing mode identifier
        /// </summary>
        public override SmartPricingMode Mode => SmartPricingMode.Fast;

        /// <summary>
        /// 3 progressive pricing steps (fewer steps for faster execution)
        /// </summary>
        protected override int StepCount => 3;

        /// <summary>
        /// 5-second intervals between steps (faster progression)
        /// </summary>
        protected override TimeSpan StepInterval => TimeSpan.FromSeconds(5);

        /// <summary>
        /// Aggressive pricing calculation for Fast mode with rapid progression
        /// </summary>
        public override decimal? CalculateNextPrice(decimal currentPrice, Quote quote, OrderDirection orderDirection, int attemptNumber)
        {
            if (quote == null)
                throw new ArgumentNullException(nameof(quote));

            if (attemptNumber > StepCount)
                return null;

            // Fast progression: aggressive moves toward market
            decimal targetPrice;
            var halfSpread = quote.Spread / 2;

            switch (attemptNumber)
            {
                case 1:
                    // Start at mid-spread (already set in initial order)
                    return null;

                case 2:
                    // Move 50% toward market price (more aggressive than Normal)
                    targetPrice = orderDirection == OrderDirection.Buy
                        ? quote.Price + (halfSpread * 0.50m)
                        : quote.Price - (halfSpread * 0.50m);
                    break;

                case 3:
                    // Move 80% toward market price
                    targetPrice = orderDirection == OrderDirection.Buy
                        ? quote.Price + (halfSpread * 0.80m)
                        : quote.Price - (halfSpread * 0.80m);
                    break;

                default:
                    // Final attempt: go to market (ask/bid)
                    targetPrice = orderDirection == OrderDirection.Buy ? quote.Ask : quote.Bid;
                    break;
            }

            // Ensure we don't exceed market boundaries
            if (orderDirection == OrderDirection.Buy)
            {
                targetPrice = Math.Min(targetPrice, quote.Ask);
            }
            else
            {
                targetPrice = Math.Max(targetPrice, quote.Bid);
            }

            // Fast mode uses smaller minimum change threshold for quicker updates
            var priceChange = Math.Abs(targetPrice - currentPrice);
            var minChange = quote.Spread * 0.03m; // Minimum 3% of spread movement

            return priceChange >= minChange ? targetPrice : null;
        }

        /// <summary>
        /// Fast mode should be more selective about when to use SmartPricing
        /// since it moves quickly toward market prices
        /// </summary>
        public override bool ShouldAttemptPricing(Quote quote, OrderDirection orderDirection)
        {
            if (!base.ShouldAttemptPricing(quote, orderDirection))
                return false;

            // Fast mode works best with reasonable spreads where quick progression makes sense
            // Minimum spread of $0.15 for options (higher than Normal)
            if (quote.Spread < 0.15m)
                return false;

            // Maximum spread of 3% of mid-price (more restrictive than Normal)
            if (quote.Spread > quote.Price * 0.03m)
                return false;

            // Don't use fast mode for very low-priced options (under $1)
            if (quote.Price < 1.0m)
                return false;

            return true;
        }
    }
}
using System;
using System.Collections.Generic;
using QuantConnect.Orders;
using QuantConnect.Data.Market;

namespace CoreAlgo.Architecture.Core.Execution
{
    /// <summary>
    /// Interface for SmartPricing engine that provides progressive pricing capabilities
    /// for improving options spread fill rates using QuantConnect's native execution framework.
    /// </summary>
    public interface ISmartPricingEngine
    {
        /// <summary>
        /// Gets the current pricing mode configuration
        /// </summary>
        SmartPricingMode Mode { get; }

        /// <summary>
        /// Calculates the initial limit price for a new order based on current market conditions
        /// </summary>
        /// <param name="quote">Current bid/ask quote for the security</param>
        /// <param name="orderDirection">Direction of the order (Buy/Sell)</param>
        /// <returns>Initial limit price starting at mid-spread</returns>
        decimal CalculateInitialPrice(Quote quote, OrderDirection orderDirection);

        /// <summary>
        /// Calculates the next progressive price step for an existing order
        /// </summary>
        /// <param name="currentPrice">Current limit price of the order</param>
        /// <param name="quote">Current bid/ask quote for the security</param>
        /// <param name="orderDirection">Direction of the order (Buy/Sell)</param>
        /// <param name="attemptNumber">Current attempt number (1-based)</param>
        /// <returns>Next progressive price, or null if no more steps available</returns>
        decimal? CalculateNextPrice(decimal currentPrice, Quote quote, OrderDirection orderDirection, int attemptNumber);

        /// <summary>
        /// Gets the time interval between pricing attempts for the current mode
        /// </summary>
        /// <returns>Time interval in seconds</returns>
        TimeSpan GetPricingInterval();

        /// <summary>
        /// Gets the maximum number of pricing attempts for the current mode
        /// </summary>
        /// <returns>Maximum number of attempts</returns>
        int GetMaxAttempts();

        /// <summary>
        /// Validates if the pricing engine should attempt to improve the price
        /// </summary>
        /// <param name="quote">Current market quote</param>
        /// <param name="orderDirection">Order direction</param>
        /// <returns>True if pricing improvement should be attempted</returns>
        bool ShouldAttemptPricing(Quote quote, OrderDirection orderDirection);

        /// <summary>
        /// Calculates the initial net limit price for a combo order based on current market conditions
        /// </summary>
        /// <param name="legs">List of legs in the combo order</param>
        /// <param name="comboQuote">Current market quote for the combo</param>
        /// <param name="orderDirection">Overall direction of the combo (Buy = paying net debit, Sell = receiving net credit)</param>
        /// <returns>Initial net limit price, or null if combo should not be priced intelligently</returns>
        decimal? CalculateInitialComboPrice(List<Leg> legs, ComboQuote comboQuote, OrderDirection orderDirection);

        /// <summary>
        /// Calculates the next progressive price step for an existing combo order
        /// </summary>
        /// <param name="currentNetPrice">Current net limit price of the combo order</param>
        /// <param name="comboQuote">Current market quote for the combo</param>
        /// <param name="orderDirection">Overall direction of the combo</param>
        /// <param name="attemptNumber">Current attempt number (1-based)</param>
        /// <returns>Next progressive net price, or null if no more steps available</returns>
        decimal? CalculateNextComboPrice(decimal currentNetPrice, ComboQuote comboQuote, OrderDirection orderDirection, int attemptNumber);

        /// <summary>
        /// Validates if the combo pricing engine should attempt to improve the price
        /// </summary>
        /// <param name="comboQuote">Current combo market quote</param>
        /// <param name="orderDirection">Overall combo direction</param>
        /// <returns>True if smart combo pricing should be attempted</returns>
        bool ShouldAttemptComboPricing(ComboQuote comboQuote, OrderDirection orderDirection);
    }

    /// <summary>
    /// Order direction for SmartPricing calculations
    /// </summary>
    public enum OrderDirection
    {
        /// <summary>
        /// Buy order (long position)
        /// </summary>
        Buy,

        /// <summary>
        /// Sell order (short position)
        /// </summary>
        Sell
    }

    /// <summary>
    /// Smart pricing modes with different aggressiveness levels
    /// </summary>
    public enum SmartPricingMode
    {
        /// <summary>
        /// SmartPricing disabled - use standard execution
        /// </summary>
        Off,

        /// <summary>
        /// Normal mode: 4 steps over 40 seconds (10s intervals)
        /// </summary>
        Normal,

        /// <summary>
        /// Fast mode: 3 steps over 15 seconds (5s intervals)
        /// </summary>
        Fast,

        /// <summary>
        /// Patient mode: 5 steps over 100 seconds (20s intervals)
        /// </summary>
        Patient
    }
}
using System;

namespace CoreAlgo.Architecture.Core.Execution
{
    /// <summary>
    /// Normal SmartPricing strategy: 4 steps over 40 seconds (10-second intervals)
    /// 
    /// This is the default balanced approach that provides good execution improvement
    /// without being too aggressive or too patient.
    /// 
    /// Timing: Step every 10 seconds for up to 40 seconds total
    /// Progression: Mid → 25% → 50% → 75% → Ask/Bid
    /// </summary>
    public class NormalPricingStrategy : PricingStrategy
    {
        /// <summary>
        /// Normal pricing mode identifier
        /// </summary>
        public override SmartPricingMode Mode => SmartPricingMode.Normal;

        /// <summary>
        /// 4 progressive pricing steps
        /// </summary>
        protected override int StepCount => 4;

        /// <summary>
        /// 10-second intervals between steps
        /// </summary>
        protected override TimeSpan StepInterval => TimeSpan.FromSeconds(10);

        /// <summary>
        /// Enhanced pricing calculation for Normal mode with optimized progression
        /// </summary>
        public override decimal? CalculateNextPrice(decimal currentPrice, Quote quote, OrderDirection orderDirection, int attemptNumber)
        {
            if (quote == null)
                throw new ArgumentNullException(nameof(quote));

            if (attemptNumber > StepCount)
                return null;

            // Normal progression: more conservative steps toward market
            decimal targetPrice;
            var halfSpread = quote.Spread / 2;

            switch (attemptNumber)
            {
                case 1:
                    // Start at mid-spread (already set in initial order)
                    return null;

                case 2:
                    // Move 25% toward market price
                    targetPrice = orderDirection == OrderDirection.Buy
                        ? quote.Price + (halfSpread * 0.25m)
                        : quote.Price - (halfSpread * 0.25m);
                    break;

                case 3:
                    // Move 50% toward market price
                    targetPrice = orderDirection == OrderDirection.Buy
                        ? quote.Price + (halfSpread * 0.50m)
                        : quote.Price - (halfSpread * 0.50m);
                    break;

                case 4:
                    // Move 75% toward market price
                    targetPrice = orderDirection == OrderDirection.Buy
                        ? quote.Price + (halfSpread * 0.75m)
                        : quote.Price - (halfSpread * 0.75m);
                    break;

                default:
                    // Final attempt: go to market (ask/bid)
                    targetPrice = orderDirection == OrderDirection.Buy ? quote.Ask : quote.Bid;
                    break;
            }

            // Ensure we don't exceed market boundaries
            if (orderDirection == OrderDirection.Buy)
            {
                targetPrice = Math.Min(targetPrice, quote.Ask);
            }
            else
            {
                targetPrice = Math.Max(targetPrice, quote.Bid);
            }

            // Only update if meaningful price change
            var priceChange = Math.Abs(targetPrice - currentPrice);
            var minChange = quote.Spread * 0.05m; // Minimum 5% of spread movement

            return priceChange >= minChange ? targetPrice : null;
        }

        /// <summary>
        /// Normal mode should attempt pricing for most reasonable market conditions
        /// </summary>
        public override bool ShouldAttemptPricing(Quote quote, OrderDirection orderDirection)
        {
            if (!base.ShouldAttemptPricing(quote, orderDirection))
                return false;

            // Normal mode is suitable for most options with reasonable spreads
            // Minimum spread of $0.10 for options
            if (quote.Spread < 0.10m)
                return false;

            // Maximum spread of 5% of mid-price
            if (quote.Spread > quote.Price * 0.05m)
                return false;

            return true;
        }
    }
}
using System;

namespace CoreAlgo.Architecture.Core.Execution
{
    /// <summary>
    /// Patient SmartPricing strategy: 5 steps over 100 seconds (20-second intervals)
    /// 
    /// This conservative approach is suitable for less liquid options or when getting
    /// the best possible price is more important than speed. Takes time to find optimal fills.
    /// 
    /// Timing: Step every 20 seconds for up to 100 seconds total
    /// Progression: Mid → 15% → 30% → 50% → 70% → Ask/Bid
    /// </summary>
    public class PatientPricingStrategy : PricingStrategy
    {
        /// <summary>
        /// Patient pricing mode identifier
        /// </summary>
        public override SmartPricingMode Mode => SmartPricingMode.Patient;

        /// <summary>
        /// 5 progressive pricing steps (more steps for patient execution)
        /// </summary>
        protected override int StepCount => 5;

        /// <summary>
        /// 20-second intervals between steps (slower progression)
        /// </summary>
        protected override TimeSpan StepInterval => TimeSpan.FromSeconds(20);

        /// <summary>
        /// Conservative pricing calculation for Patient mode with gradual progression
        /// </summary>
        public override decimal? CalculateNextPrice(decimal currentPrice, Quote quote, OrderDirection orderDirection, int attemptNumber)
        {
            if (quote == null)
                throw new ArgumentNullException(nameof(quote));

            if (attemptNumber > StepCount)
                return null;

            // Patient progression: gradual moves toward market
            decimal targetPrice;
            var halfSpread = quote.Spread / 2;

            switch (attemptNumber)
            {
                case 1:
                    // Start at mid-spread (already set in initial order)
                    return null;

                case 2:
                    // Move 15% toward market price (very conservative)
                    targetPrice = orderDirection == OrderDirection.Buy
                        ? quote.Price + (halfSpread * 0.15m)
                        : quote.Price - (halfSpread * 0.15m);
                    break;

                case 3:
                    // Move 30% toward market price
                    targetPrice = orderDirection == OrderDirection.Buy
                        ? quote.Price + (halfSpread * 0.30m)
                        : quote.Price - (halfSpread * 0.30m);
                    break;

                case 4:
                    // Move 50% toward market price
                    targetPrice = orderDirection == OrderDirection.Buy
                        ? quote.Price + (halfSpread * 0.50m)
                        : quote.Price - (halfSpread * 0.50m);
                    break;

                case 5:
                    // Move 70% toward market price
                    targetPrice = orderDirection == OrderDirection.Buy
                        ? quote.Price + (halfSpread * 0.70m)
                        : quote.Price - (halfSpread * 0.70m);
                    break;

                default:
                    // Final attempt: go to market (ask/bid)
                    targetPrice = orderDirection == OrderDirection.Buy ? quote.Ask : quote.Bid;
                    break;
            }

            // Ensure we don't exceed market boundaries
            if (orderDirection == OrderDirection.Buy)
            {
                targetPrice = Math.Min(targetPrice, quote.Ask);
            }
            else
            {
                targetPrice = Math.Max(targetPrice, quote.Bid);
            }

            // Patient mode uses higher minimum change threshold for more meaningful updates
            var priceChange = Math.Abs(targetPrice - currentPrice);
            var minChange = quote.Spread * 0.08m; // Minimum 8% of spread movement

            return priceChange >= minChange ? targetPrice : null;
        }

        /// <summary>
        /// Patient mode should work with wider spreads and less liquid options
        /// </summary>
        public override bool ShouldAttemptPricing(Quote quote, OrderDirection orderDirection)
        {
            if (!base.ShouldAttemptPricing(quote, orderDirection))
                return false;

            // Patient mode is designed for wider spreads where gradual progression helps
            // Minimum spread of $0.20 for options
            if (quote.Spread < 0.20m)
                return false;

            // Maximum spread of 8% of mid-price (more tolerant than other modes)
            if (quote.Spread > quote.Price * 0.08m)
                return false;

            // Patient mode works well for higher-priced options where small improvements matter
            // No minimum price restriction (unlike Fast mode)

            return true;
        }

    }
}
using System;
using System.Collections.Generic;
using QuantConnect.Orders;

namespace CoreAlgo.Architecture.Core.Execution
{
    /// <summary>
    /// Base class for SmartPricing strategies that implement progressive pricing logic
    /// </summary>
    public abstract class PricingStrategy : ISmartPricingEngine
    {
        /// <summary>
        /// Gets the pricing mode for this strategy
        /// </summary>
        public abstract SmartPricingMode Mode { get; }

        /// <summary>
        /// Gets the number of pricing steps for this strategy
        /// </summary>
        protected abstract int StepCount { get; }

        /// <summary>
        /// Gets the time interval between pricing steps
        /// </summary>
        protected abstract TimeSpan StepInterval { get; }

        /// <summary>
        /// Calculates the initial limit price at mid-spread
        /// </summary>
        public virtual decimal CalculateInitialPrice(Quote quote, OrderDirection orderDirection)
        {
            if (quote == null) 
                throw new ArgumentNullException(nameof(quote));

            // Start at mid-spread for better execution probability
            return quote.Price;
        }

        /// <summary>
        /// Calculates the next progressive price step moving toward ask/bid
        /// </summary>
        public virtual decimal? CalculateNextPrice(decimal currentPrice, Quote quote, OrderDirection orderDirection, int attemptNumber)
        {
            if (quote == null)
                throw new ArgumentNullException(nameof(quote));

            if (attemptNumber > StepCount)
                return null; // No more steps available

            // Calculate progression percentage based on attempt number
            var progressionPct = (decimal)attemptNumber / StepCount;
            
            // Move from mid-spread toward ask (buy) or bid (sell)
            decimal targetPrice;
            if (orderDirection == OrderDirection.Buy)
            {
                // Progress from mid toward ask
                targetPrice = quote.Price + (quote.Spread / 2 * progressionPct);
                // Don't exceed ask price
                targetPrice = Math.Min(targetPrice, quote.Ask);
            }
            else
            {
                // Progress from mid toward bid
                targetPrice = quote.Price - (quote.Spread / 2 * progressionPct);
                // Don't go below bid price
                targetPrice = Math.Max(targetPrice, quote.Bid);
            }

            // Only update if price has meaningfully changed
            var priceChange = Math.Abs(targetPrice - currentPrice);
            var minChange = quote.Spread * 0.1m; // Minimum 10% of spread movement

            return priceChange >= minChange ? targetPrice : null;
        }

        /// <summary>
        /// Gets the time interval between pricing attempts
        /// </summary>
        public virtual TimeSpan GetPricingInterval()
        {
            return StepInterval;
        }

        /// <summary>
        /// Gets the maximum number of pricing attempts
        /// </summary>
        public virtual int GetMaxAttempts()
        {
            return StepCount;
        }

        /// <summary>
        /// Validates if pricing should be attempted based on market conditions
        /// </summary>
        public virtual bool ShouldAttemptPricing(Quote quote, OrderDirection orderDirection)
        {
            if (quote == null)
                return false;

            // Don't attempt pricing if spread is too narrow (less than $0.05)
            if (quote.Spread < 0.05m)
                return false;

            // Don't attempt pricing if spread is too wide (more than 10% of mid-price)
            if (quote.Spread > quote.Price * 0.10m)
                return false;

            // Valid for pricing
            return true;
        }

        /// <summary>
        /// Calculates the initial net limit price for a combo order based on current market conditions
        /// </summary>
        /// <param name="legs">List of legs in the combo order</param>
        /// <param name="comboQuote">Current market quote for the combo</param>
        /// <param name="orderDirection">Overall direction of the combo (Buy = paying net debit, Sell = receiving net credit)</param>
        /// <returns>Initial net limit price, or null if combo should not be priced intelligently</returns>
        public virtual decimal? CalculateInitialComboPrice(List<Leg> legs, ComboQuote comboQuote, OrderDirection orderDirection)
        {
            if (legs == null || legs.Count == 0 || comboQuote == null || !comboQuote.IsValid)
                return null;

            // Use the embedded ComboPricingEngine logic for consistency
            var comboPricingEngine = new ComboPricingEngine(Mode, 5.0m); // Default max spread width
            return comboPricingEngine.CalculateInitialComboPrice(legs, comboQuote, orderDirection);
        }

        /// <summary>
        /// Calculates the next progressive price step for an existing combo order
        /// </summary>
        /// <param name="currentNetPrice">Current net limit price of the combo order</param>
        /// <param name="comboQuote">Current market quote for the combo</param>
        /// <param name="orderDirection">Overall direction of the combo</param>
        /// <param name="attemptNumber">Current attempt number (1-based)</param>
        /// <returns>Next progressive net price, or null if no more steps available</returns>
        public virtual decimal? CalculateNextComboPrice(decimal currentNetPrice, ComboQuote comboQuote, OrderDirection orderDirection, int attemptNumber)
        {
            if (comboQuote == null || !comboQuote.IsValid)
                return null;

            // Use the embedded ComboPricingEngine logic for consistency
            var comboPricingEngine = new ComboPricingEngine(Mode, 5.0m); // Default max spread width
            return comboPricingEngine.CalculateNextComboPrice(currentNetPrice, comboQuote, orderDirection, attemptNumber);
        }

        /// <summary>
        /// Validates if the combo pricing engine should attempt to improve the price
        /// </summary>
        /// <param name="comboQuote">Current combo market quote</param>
        /// <param name="orderDirection">Overall combo direction</param>
        /// <returns>True if smart combo pricing should be attempted</returns>
        public virtual bool ShouldAttemptComboPricing(ComboQuote comboQuote, OrderDirection orderDirection)
        {
            if (comboQuote == null || !comboQuote.IsValid)
                return false;

            // Use the embedded ComboPricingEngine logic for consistency
            var comboPricingEngine = new ComboPricingEngine(Mode, 5.0m); // Default max spread width
            return comboPricingEngine.ShouldAttemptComboPricing(comboQuote, orderDirection);
        }

        /// <summary>
        /// Calculates the price improvement benefit for a given attempt
        /// </summary>
        protected decimal CalculatePriceImprovement(Quote quote, OrderDirection orderDirection, int attemptNumber)
        {
            var aggressivePrice = orderDirection == OrderDirection.Buy ? quote.Ask : quote.Bid;
            var midPrice = quote.Price;
            
            // Calculate how much better mid-price is compared to aggressive price
            var maxImprovement = Math.Abs(aggressivePrice - midPrice);
            
            // Progressive improvement - less improvement with each step
            var progressionPct = (decimal)(attemptNumber - 1) / StepCount;
            var currentImprovement = maxImprovement * (1 - progressionPct);
            
            return currentImprovement;
        }
    }
}
using System;
using QuantConnect.Orders;
using QuantConnect.Scheduling;

namespace CoreAlgo.Architecture.Core.Execution
{
    /// <summary>
    /// Tracks the state of an order undergoing SmartPricing progression
    /// </summary>
    public class SmartOrderTracker
    {
        /// <summary>
        /// The QuantConnect OrderTicket being tracked
        /// </summary>
        public OrderTicket OrderTicket { get; }

        /// <summary>
        /// Direction of the order (Buy/Sell)
        /// </summary>
        public OrderDirection OrderDirection { get; }

        /// <summary>
        /// SmartPricing mode used for this order
        /// </summary>
        public SmartPricingMode PricingMode { get; }

        /// <summary>
        /// Current limit price of the order
        /// </summary>
        public decimal CurrentPrice { get; set; }

        /// <summary>
        /// Current attempt number (1-based)
        /// </summary>
        public int AttemptNumber { get; set; }

        /// <summary>
        /// Time when the order was first placed
        /// </summary>
        public DateTime StartTime { get; }

        /// <summary>
        /// Initial market quote when order was placed
        /// </summary>
        public Quote InitialQuote { get; }

        /// <summary>
        /// Most recent market quote
        /// </summary>
        public Quote CurrentQuote { get; private set; }

        /// <summary>
        /// Scheduled event for the next price update
        /// </summary>
        public ScheduledEvent ScheduledEvent { get; set; }

        /// <summary>
        /// Total quantity that has been filled
        /// </summary>
        public decimal FilledQuantity { get; private set; }

        /// <summary>
        /// Remaining quantity to be filled
        /// </summary>
        public decimal RemainingQuantity => Math.Abs(OrderTicket.Quantity) - FilledQuantity;

        /// <summary>
        /// Whether the order has been partially filled
        /// </summary>
        public bool IsPartiallyFilled => FilledQuantity > 0 && FilledQuantity < Math.Abs(OrderTicket.Quantity);

        /// <summary>
        /// History of price attempts for analysis
        /// </summary>
        public PricingAttempt[] PriceHistory { get; private set; }

        public SmartOrderTracker(OrderTicket orderTicket, Quote initialQuote, OrderDirection direction, SmartPricingMode mode, decimal initialPrice)
        {
            OrderTicket = orderTicket ?? throw new ArgumentNullException(nameof(orderTicket));
            InitialQuote = initialQuote ?? throw new ArgumentNullException(nameof(initialQuote));
            OrderDirection = direction;
            PricingMode = mode;
            
            CurrentPrice = initialPrice;
            CurrentQuote = initialQuote;
            AttemptNumber = 1;
            StartTime = DateTime.UtcNow;
            FilledQuantity = 0;

            // Initialize price history
            PriceHistory = new PricingAttempt[GetMaxAttempts(mode)];
            PriceHistory[0] = new PricingAttempt(1, CurrentPrice, initialQuote, StartTime);
        }

        /// <summary>
        /// Updates the order price and tracking information
        /// </summary>
        public void UpdatePrice(decimal newPrice, Quote newQuote)
        {
            CurrentPrice = newPrice;
            CurrentQuote = newQuote;
            AttemptNumber++;

            // Record this attempt in history
            if (AttemptNumber <= PriceHistory.Length)
            {
                PriceHistory[AttemptNumber - 1] = new PricingAttempt(AttemptNumber, newPrice, newQuote, DateTime.UtcNow);
            }
        }

        /// <summary>
        /// Updates tracking information when a partial fill occurs
        /// </summary>
        public void UpdatePartialFill(OrderEvent orderEvent)
        {
            if (orderEvent.Status == OrderStatus.PartiallyFilled)
            {
                FilledQuantity += Math.Abs(orderEvent.FillQuantity);
            }
        }

        /// <summary>
        /// Gets performance metrics for this order's pricing progression
        /// </summary>
        public SmartPricingMetrics GetMetrics()
        {
            var elapsed = DateTime.UtcNow - StartTime;
            var initialSpread = InitialQuote.Spread;
            var currentSpread = CurrentQuote?.Spread ?? initialSpread;
            
            // Calculate price improvement relative to initial aggressive price
            var aggressivePrice = OrderDirection == OrderDirection.Buy ? InitialQuote.Ask : InitialQuote.Bid;
            var priceImprovement = OrderDirection == OrderDirection.Buy ? 
                aggressivePrice - CurrentPrice : CurrentPrice - aggressivePrice;

            return new SmartPricingMetrics
            {
                OrderId = OrderTicket.OrderId,
                Symbol = OrderTicket.Symbol.Value,
                Direction = OrderDirection,
                Mode = PricingMode,
                AttemptNumber = AttemptNumber,
                TotalAttempts = GetMaxAttempts(PricingMode),
                ElapsedTime = elapsed,
                InitialPrice = PriceHistory[0].Price,
                CurrentPrice = CurrentPrice,
                PriceImprovement = priceImprovement,
                InitialSpread = initialSpread,
                CurrentSpread = currentSpread,
                FilledQuantity = FilledQuantity,
                RemainingQuantity = RemainingQuantity,
                IsPartiallyFilled = IsPartiallyFilled,
                IsCompleted = OrderTicket.Status == OrderStatus.Filled || OrderTicket.Status == OrderStatus.Canceled
            };
        }

        /// <summary>
        /// Gets the limit price from an order ticket
        /// </summary>
        private static decimal GetLimitPrice(OrderTicket orderTicket)
        {
            // For now, assume the initial price was set correctly
            // In practice, this will be set properly by the execution model
            return 0; // Will be overridden by actual limit price from the order
        }

        /// <summary>
        /// Gets the maximum attempts for a pricing mode
        /// </summary>
        private static int GetMaxAttempts(SmartPricingMode mode)
        {
            return mode switch
            {
                SmartPricingMode.Fast => 3,
                SmartPricingMode.Normal => 4,
                SmartPricingMode.Patient => 5,
                _ => 1
            };
        }
    }

    /// <summary>
    /// Represents a single pricing attempt in the progression
    /// </summary>
    public class PricingAttempt
    {
        public int AttemptNumber { get; }
        public decimal Price { get; }
        public Quote MarketQuote { get; }
        public DateTime Timestamp { get; }

        public PricingAttempt(int attemptNumber, decimal price, Quote marketQuote, DateTime timestamp)
        {
            AttemptNumber = attemptNumber;
            Price = price;
            MarketQuote = marketQuote;
            Timestamp = timestamp;
        }
    }

    /// <summary>
    /// Performance metrics for SmartPricing orders
    /// </summary>
    public class SmartPricingMetrics
    {
        public int OrderId { get; set; }
        public string Symbol { get; set; }
        public OrderDirection Direction { get; set; }
        public SmartPricingMode Mode { get; set; }
        public int AttemptNumber { get; set; }
        public int TotalAttempts { get; set; }
        public TimeSpan ElapsedTime { get; set; }
        public decimal InitialPrice { get; set; }
        public decimal CurrentPrice { get; set; }
        public decimal PriceImprovement { get; set; }
        public decimal InitialSpread { get; set; }
        public decimal CurrentSpread { get; set; }
        public decimal FilledQuantity { get; set; }
        public decimal RemainingQuantity { get; set; }
        public bool IsPartiallyFilled { get; set; }
        public bool IsCompleted { get; set; }
    }
}
using System;

namespace CoreAlgo.Architecture.Core.Execution
{
    /// <summary>
    /// Factory for creating SmartPricing engines based on configuration
    /// </summary>
    public static class SmartPricingEngineFactory
    {
        /// <summary>
        /// Creates a SmartPricing engine for the specified mode
        /// </summary>
        /// <param name="mode">The pricing mode to use</param>
        /// <returns>Configured pricing engine instance</returns>
        public static ISmartPricingEngine Create(SmartPricingMode mode)
        {
            return mode switch
            {
                SmartPricingMode.Fast => new FastPricingStrategy(),
                SmartPricingMode.Normal => new NormalPricingStrategy(),
                SmartPricingMode.Patient => new PatientPricingStrategy(),
                SmartPricingMode.Off => throw new InvalidOperationException("Cannot create engine for SmartPricingMode.Off"),
                _ => throw new ArgumentException($"Unknown SmartPricing mode: {mode}", nameof(mode))
            };
        }

        /// <summary>
        /// Creates a SmartPricing engine from a string configuration value
        /// </summary>
        /// <param name="modeString">String representation of the mode ("Normal", "Fast", "Patient", "Off")</param>
        /// <returns>Configured pricing engine instance</returns>
        public static ISmartPricingEngine Create(string modeString)
        {
            if (string.IsNullOrWhiteSpace(modeString))
                return Create(SmartPricingMode.Normal); // Default mode

            var mode = ParseMode(modeString);
            return Create(mode);
        }

        /// <summary>
        /// Parses a string into a SmartPricingMode enum value
        /// </summary>
        /// <param name="modeString">String to parse</param>
        /// <returns>Parsed SmartPricingMode</returns>
        public static SmartPricingMode ParseMode(string modeString)
        {
            if (string.IsNullOrWhiteSpace(modeString))
                return SmartPricingMode.Normal;

            return modeString.ToUpperInvariant() switch
            {
                "FAST" => SmartPricingMode.Fast,
                "NORMAL" => SmartPricingMode.Normal,
                "PATIENT" => SmartPricingMode.Patient,
                "OFF" => SmartPricingMode.Off,
                "DISABLED" => SmartPricingMode.Off,
                "FALSE" => SmartPricingMode.Off,
                _ => SmartPricingMode.Normal // Default fallback
            };
        }

        /// <summary>
        /// Gets a descriptive summary of a pricing mode
        /// </summary>
        /// <param name="mode">The pricing mode</param>
        /// <returns>Human-readable description</returns>
        public static string GetModeDescription(SmartPricingMode mode)
        {
            return mode switch
            {
                SmartPricingMode.Off => "Disabled - uses standard execution",
                SmartPricingMode.Fast => "Fast - 3 steps over 15 seconds (aggressive)",
                SmartPricingMode.Normal => "Normal - 4 steps over 40 seconds (balanced)",
                SmartPricingMode.Patient => "Patient - 5 steps over 100 seconds (conservative)",
                _ => "Unknown mode"
            };
        }

        /// <summary>
        /// Validates if a mode string is supported
        /// </summary>
        /// <param name="modeString">String to validate</param>
        /// <returns>True if the mode is supported</returns>
        public static bool IsValidMode(string modeString)
        {
            if (string.IsNullOrWhiteSpace(modeString))
                return true; // Default is valid

            var upper = modeString.ToUpperInvariant();
            return upper == "FAST" || upper == "NORMAL" || upper == "PATIENT" || 
                   upper == "OFF" || upper == "DISABLED" || upper == "FALSE";
        }
    }
}
using System;
using System.Collections.Generic;
using System.Linq;
using QuantConnect;
using QuantConnect.Algorithm;
using QuantConnect.Algorithm.Framework.Execution;
using QuantConnect.Algorithm.Framework.Portfolio;
using QuantConnect.Data;
using QuantConnect.Data.Market;
using QuantConnect.Orders;
using QuantConnect.Scheduling;
using QuantConnect.Securities;
using CoreAlgo.Architecture.Core.Interfaces;

namespace CoreAlgo.Architecture.Core.Execution
{
    /// <summary>
    /// QC-First SmartPricing Execution Model that extends QuantConnect's IExecutionModel
    /// to improve fill rates on options spreads through intelligent limit order progression.
    /// 
    /// This execution model starts orders at mid-spread and progressively moves toward
    /// ask (for buys) or bid (for sells) over time to improve fill rates while maintaining
    /// good execution prices.
    /// </summary>
    public class SmartPricingExecutionModel : ExecutionModel
    {
        private readonly IAlgorithmContext _context;
        private readonly ISmartPricingEngine _pricingEngine;
        private readonly Dictionary<int, SmartOrderTracker> _activeOrders;
        private readonly HashSet<ScheduledEvent> _scheduledEvents;

        /// <summary>
        /// Initializes a new instance of the SmartPricingExecutionModel
        /// </summary>
        /// <param name="context">Algorithm context for logging and market data access</param>
        /// <param name="pricingEngine">Pricing engine for progressive pricing logic</param>
        public SmartPricingExecutionModel(IAlgorithmContext context, ISmartPricingEngine pricingEngine)
        {
            _context = context ?? throw new ArgumentNullException(nameof(context));
            _pricingEngine = pricingEngine ?? throw new ArgumentNullException(nameof(pricingEngine));
            _activeOrders = new Dictionary<int, SmartOrderTracker>();
            _scheduledEvents = new HashSet<ScheduledEvent>();
        }

        /// <summary>
        /// Executes market orders immediately and starts progressive pricing for limit orders
        /// </summary>
        /// <param name="algorithm">The algorithm instance</param>
        /// <param name="targets">Portfolio targets to execute</param>
        public override void Execute(QCAlgorithm algorithm, IPortfolioTarget[] targets)
        {
            try
            {
                // Check if SmartPricing is enabled
                if (_pricingEngine.Mode == SmartPricingMode.Off)
                {
                    // Use standard execution model when SmartPricing is disabled
                    base.Execute(algorithm, targets);
                    return;
                }

                ((dynamic)_context.Logger).Debug($"SmartPricing: Processing {targets.Length} portfolio targets in {_pricingEngine.Mode} mode");

                foreach (var target in targets)
                {
                    var security = algorithm.Securities[target.Symbol];
                    var quantity = target.Quantity - security.Holdings.Quantity;

                    if (quantity == 0)
                        continue;

                    // Get current market quote
                    var quote = GetCurrentQuote(security);
                    if (quote == null || !_pricingEngine.ShouldAttemptPricing(quote, quantity > 0 ? OrderDirection.Buy : OrderDirection.Sell))
                    {
                        // Fall back to market order if we can't get quote or shouldn't use smart pricing
                        algorithm.MarketOrder(target.Symbol, quantity, tag: "SmartPricing:Fallback");
                        continue;
                    }

                    // Calculate initial smart price
                    var orderDirection = quantity > 0 ? OrderDirection.Buy : OrderDirection.Sell;
                    var initialPrice = _pricingEngine.CalculateInitialPrice(quote, orderDirection);

                    // Place initial limit order
                    var orderTicket = algorithm.LimitOrder(target.Symbol, quantity, initialPrice, 
                        tag: $"SmartPricing:{_pricingEngine.Mode}:Initial");

                    if (orderTicket != null)
                    {
                        // Track this order for progressive pricing
                        var tracker = new SmartOrderTracker(orderTicket, quote, orderDirection, _pricingEngine.Mode, initialPrice);
                        _activeOrders[orderTicket.OrderId] = tracker;

                        // Schedule first pricing update
                        ScheduleNextPricingUpdate(algorithm, tracker);

                        ((dynamic)_context.Logger).Debug($"SmartPricing: Started {orderDirection} order {orderTicket.OrderId} at ${initialPrice:F2} " +
                                                        $"(Mid: ${quote.Price:F2}, Spread: ${quote.Ask - quote.Bid:F2})");
                    }
                }
            }
            catch (Exception ex)
            {
                ((dynamic)_context.Logger).Error($"SmartPricing execution error: {ex.Message}");
                // Fall back to standard execution on error
                base.Execute(algorithm, targets);
            }
        }

        /// <summary>
        /// Handles order events to track fills and update order state
        /// </summary>
        /// <param name="algorithm">The algorithm instance</param>
        /// <param name="orderEvent">The order event</param>
        public override void OnOrderEvent(QCAlgorithm algorithm, OrderEvent orderEvent)
        {

            if (!_activeOrders.TryGetValue(orderEvent.OrderId, out var tracker))
                return;

            switch (orderEvent.Status)
            {
                case OrderStatus.Filled:
                    ((dynamic)_context.Logger).Debug($"SmartPricing: Order {orderEvent.OrderId} filled at ${orderEvent.FillPrice:F2} " +
                                                    $"after {tracker.AttemptNumber} attempts");
                    CleanupOrder(tracker);
                    break;

                case OrderStatus.PartiallyFilled:
                    ((dynamic)_context.Logger).Debug($"SmartPricing: Order {orderEvent.OrderId} partially filled " +
                                                    $"({orderEvent.FillQuantity}/{tracker.OrderTicket.Quantity})");
                    tracker.UpdatePartialFill(orderEvent);
                    break;

                case OrderStatus.Canceled:
                case OrderStatus.Invalid:
                    ((dynamic)_context.Logger).Warning($"SmartPricing: Order {orderEvent.OrderId} {orderEvent.Status}");
                    CleanupOrder(tracker);
                    break;
            }
        }

        /// <summary>
        /// Schedules the next pricing update for an order
        /// </summary>
        private void ScheduleNextPricingUpdate(QCAlgorithm algorithm, SmartOrderTracker tracker)
        {
            var interval = _pricingEngine.GetPricingInterval();
            var updateTime = algorithm.Time.Add(interval);

            var scheduledEvent = algorithm.Schedule.On(algorithm.DateRules.On(updateTime.Date),
                algorithm.TimeRules.At(updateTime.Hour, updateTime.Minute, updateTime.Second),
                () => UpdateOrderPrice(algorithm, tracker));

            _scheduledEvents.Add(scheduledEvent);
            tracker.ScheduledEvent = scheduledEvent;
        }

        /// <summary>
        /// Updates the price of an active order using progressive pricing
        /// </summary>
        private void UpdateOrderPrice(QCAlgorithm algorithm, SmartOrderTracker tracker)
        {
            try
            {
                // Remove the scheduled event
                if (tracker.ScheduledEvent != null)
                {
                    _scheduledEvents.Remove(tracker.ScheduledEvent);
                    tracker.ScheduledEvent = null;
                }

                // Check if order is still active
                if (!_activeOrders.ContainsKey(tracker.OrderTicket.OrderId) || 
                    tracker.OrderTicket.Status == OrderStatus.Filled)
                {
                    return;
                }

                // Get current market quote
                var security = algorithm.Securities[tracker.OrderTicket.Symbol];
                var currentQuote = GetCurrentQuote(security);
                
                if (currentQuote == null)
                {
                    ((dynamic)_context.Logger).Warning($"SmartPricing: No quote available for {tracker.OrderTicket.Symbol}, canceling order {tracker.OrderTicket.OrderId}");
                    tracker.OrderTicket.Cancel("No quote available");
                    CleanupOrder(tracker);
                    return;
                }

                // Calculate next price
                var nextPrice = _pricingEngine.CalculateNextPrice(
                    tracker.CurrentPrice, currentQuote, tracker.OrderDirection, tracker.AttemptNumber + 1);

                if (nextPrice.HasValue)
                {
                    // Update order price
                    var updateResult = tracker.OrderTicket.Update(new UpdateOrderFields { LimitPrice = nextPrice.Value });
                    
                    if (updateResult.IsSuccess)
                    {
                        tracker.UpdatePrice(nextPrice.Value, currentQuote);
                        
                        ((dynamic)_context.Logger).Debug($"SmartPricing: Updated order {tracker.OrderTicket.OrderId} " +
                                                        $"to ${nextPrice.Value:F2} (attempt {tracker.AttemptNumber})");

                        // Schedule next update if we haven't reached max attempts
                        if (tracker.AttemptNumber < _pricingEngine.GetMaxAttempts())
                        {
                            ScheduleNextPricingUpdate(algorithm, tracker);
                        }
                        else
                        {
                            ((dynamic)_context.Logger).Debug($"SmartPricing: Order {tracker.OrderTicket.OrderId} reached max attempts, keeping final price");
                        }
                    }
                    else
                    {
                        ((dynamic)_context.Logger).Warning($"SmartPricing: Failed to update order {tracker.OrderTicket.OrderId}: {updateResult.ErrorMessage}");
                    }
                }
                else
                {
                    // No more price improvements available, keep current order
                    ((dynamic)_context.Logger).Debug($"SmartPricing: No more price improvements for order {tracker.OrderTicket.OrderId}");
                }
            }
            catch (Exception ex)
            {
                ((dynamic)_context.Logger).Error($"SmartPricing: Error updating order {tracker.OrderTicket.OrderId}: {ex.Message}");
            }
        }

        /// <summary>
        /// Gets the current market quote for a security
        /// </summary>
        private Quote GetCurrentQuote(Security security)
        {
            try
            {
                // Try to get the most recent quote - use Cache.GetData for QuoteBar
                var quoteBar = security.Cache.GetData<QuoteBar>();
                if (quoteBar != null && quoteBar.Bid.Close > 0 && quoteBar.Ask.Close > 0)
                {
                    return new Quote(quoteBar.Bid.Close, quoteBar.Ask.Close);
                }

                // Fall back to using last price if quote is not available
                var price = security.Price;
                if (price > 0)
                {
                    // Estimate spread as 0.5% of price (conservative estimate)
                    var estimatedSpread = price * 0.005m;
                    return new Quote(price - estimatedSpread / 2, price + estimatedSpread / 2);
                }

                return null;
            }
            catch (Exception ex)
            {
                ((dynamic)_context.Logger).Error($"SmartPricing: Error getting quote for {security.Symbol}: {ex.Message}");
                return null;
            }
        }

        /// <summary>
        /// Cleans up resources for a completed order
        /// </summary>
        private void CleanupOrder(SmartOrderTracker tracker)
        {
            _activeOrders.Remove(tracker.OrderTicket.OrderId);
            
            if (tracker.ScheduledEvent != null)
            {
                _scheduledEvents.Remove(tracker.ScheduledEvent);
            }
        }
    }

    /// <summary>
    /// Represents a market quote with bid and ask prices
    /// </summary>
    public class Quote
    {
        public decimal Bid { get; }
        public decimal Ask { get; }
        public decimal Price => (Bid + Ask) / 2;
        public decimal Spread => Ask - Bid;

        public Quote(decimal bid, decimal ask)
        {
            Bid = bid;
            Ask = ask;
        }
    }
}
using System;
using System.Collections.Generic;
using QuantConnect;
using QuantConnect.Algorithm;
using QuantConnect.Orders;
using QuantConnect.Securities;

namespace CoreAlgo.Architecture.Core.Helpers
{
    /// <summary>
    /// Static utility class for handling option assignments using QuantConnect's native Portfolio system.
    /// Provides immediate liquidation of assigned shares to prevent margin crises.
    /// </summary>
    public static class AssignmentHandler
    {
        // Track processed assignments to prevent duplicate liquidations
        private static readonly HashSet<string> _processedAssignments = new HashSet<string>();
        /// <summary>
        /// Handle option assignment by immediately liquidating assigned underlying shares.
        /// Uses assignment event quantity and contract multiplier to compute shares (QC-native approach).
        /// Special handling for cash-settled index options (SPX).
        /// </summary>
        /// <param name="algorithm">The QC algorithm instance</param>
        /// <param name="assignmentEvent">The assignment order event from QC</param>
        public static void HandleAssignment(QCAlgorithm algorithm, OrderEvent assignmentEvent)
        {
            try
            {
                algorithm.Log($"ASSIGNMENT DETECTED: {assignmentEvent.Symbol} at {algorithm.Time}");
                
                // Idempotency guard: prevent duplicate processing of same assignment
                var assignmentKey = $"{assignmentEvent.Symbol}_{assignmentEvent.OrderId}_{algorithm.Time}";
                if (_processedAssignments.Contains(assignmentKey))
                {
                    algorithm.Log($"ASSIGNMENT ALREADY PROCESSED: {assignmentEvent.Symbol} (skipping duplicate)");
                    return;
                }
                _processedAssignments.Add(assignmentKey);
                
                // Get the assigned option symbol
                var assignedSymbol = assignmentEvent.Symbol;
                
                // Check if this is a cash-settled index option (SPX)
                if (IsCashSettledIndexOption(assignedSymbol))
                {
                    algorithm.Log($"INDEX OPTION ASSIGNMENT: {assignedSymbol} (Cash-settled - no equity position created)");
                    HandleCashSettledAssignment(algorithm, assignmentEvent);
                    return;
                }
                
                // For option assignments, get the underlying symbol
                var underlyingSymbol = GetUnderlyingSymbol(assignedSymbol);
                
                if (underlyingSymbol == null)
                {
                    algorithm.Error($"Could not determine underlying symbol for assignment: {assignedSymbol}");
                    return;
                }
                
                // QC-NATIVE APPROACH: Compute shares from assignment event directly
                // This avoids relying on Portfolio holdings which may not be populated at midnight (assignment time)
                if (!algorithm.Securities.ContainsKey(assignedSymbol))
                {
                    algorithm.Error($"Option contract not in securities: {assignedSymbol}");
                    return;
                }
                
                var optionSec = algorithm.Securities[assignedSymbol];
                var contractMultiplier = optionSec.SymbolProperties.ContractMultiplier;
                var contracts = Math.Abs(assignmentEvent.FillQuantity);
                
                // CRITICAL FIX: Correct sign logic based on option right
                // Short CALL assignment creates SHORT stock position → BUY to flatten
                // Short PUT assignment creates LONG stock position → SELL to flatten
                int sharesToFlatten;
                
                // Try to use actual portfolio holdings if available (most accurate)
                if (algorithm.Portfolio.ContainsKey(underlyingSymbol) && 
                    algorithm.Portfolio[underlyingSymbol].Quantity != 0)
                {
                    // Flatten existing position by negating current holdings
                    sharesToFlatten = -(int)algorithm.Portfolio[underlyingSymbol].Quantity;
                    algorithm.Log($"Using portfolio holdings: {underlyingSymbol.Value} has {algorithm.Portfolio[underlyingSymbol].Quantity} shares");
                }
                else
                {
                    // Fallback: compute from option right
                    var optionRight = assignedSymbol.ID.OptionRight;
                    if (optionRight == OptionRight.Call)
                    {
                        // Short call assignment → we delivered shares → now short stock → BUY to cover
                        sharesToFlatten = (int)(contracts * contractMultiplier);
                    }
                    else // OptionRight.Put
                    {
                        // Short put assignment → we bought shares → now long stock → SELL to flatten
                        sharesToFlatten = -(int)(contracts * contractMultiplier);
                    }
                    algorithm.Log($"Using computed position for {optionRight}: {contracts} contracts → {sharesToFlatten} shares");
                }
                
                algorithm.Log($"Assignment Details: {underlyingSymbol.Value} - Contracts: {contracts}, " +
                             $"Multiplier: {contractMultiplier}, Shares to flatten: {sharesToFlatten}");
                
                // Execute immediate liquidation using computed quantity
                var orderTicket = algorithm.MarketOrder(underlyingSymbol, sharesToFlatten, tag: "Assignment Auto-Liquidation");
                
                algorithm.Log($"LIQUIDATING ASSIGNMENT: {underlyingSymbol.Value} - " +
                             $"Quantity: {sharesToFlatten}, Order ID: {orderTicket.OrderId}");
                
                // Log assignment impact for tracking
                LogAssignmentImpact(algorithm, assignmentEvent, underlyingSymbol, contractMultiplier);
            }
            catch (Exception ex)
            {
                algorithm.Error($"Error handling assignment: {ex.Message}");
                algorithm.Error($"Stack trace: {ex.StackTrace}");
            }
        }
        
        /// <summary>
        /// Check if the symbol represents a cash-settled index option (SPX, SPXW).
        /// These options settle in cash and don't create underlying equity positions.
        /// </summary>
        private static bool IsCashSettledIndexOption(Symbol symbol)
        {
            if (symbol?.SecurityType == SecurityType.IndexOption)
            {
                return true;
            }
            
            // SPXW weekly options are cash-settled but might not have IndexOption type
            var symbolStr = symbol?.Value?.ToUpperInvariant() ?? "";
            if (symbolStr.StartsWith("SPXW") || symbolStr.StartsWith("SPX"))
                return true;
            
            // Other known cash-settled index options
            if (symbolStr.StartsWith("VIX") || symbolStr.StartsWith("NDX") || symbolStr.StartsWith("RUT"))
                return true;
            
            return false;
        }
        
        /// <summary>
        /// Get the underlying symbol from an option symbol.
        /// Handles both equity options (SPY) and index options (SPXW -> SPX).
        /// </summary>
        private static Symbol GetUnderlyingSymbol(Symbol optionSymbol)
        {
            if (optionSymbol.SecurityType == SecurityType.Option || optionSymbol.SecurityType == SecurityType.IndexOption)
            {
                return optionSymbol.Underlying;
            }
            
            // If it's already an equity symbol (direct assignment), return as-is
            if (optionSymbol.SecurityType == SecurityType.Equity || optionSymbol.SecurityType == SecurityType.Index)
            {
                return optionSymbol;
            }
            
            return null;
        }
        
        /// <summary>
        /// Log assignment impact for performance tracking and analysis.
        /// Uses event-based calculation when holdings aren't available (e.g., at midnight assignment time).
        /// </summary>
        private static void LogAssignmentImpact(QCAlgorithm algorithm, OrderEvent assignmentEvent, 
            Symbol underlyingSymbol, decimal contractMultiplier)
        {
            try
            {
                var holding = algorithm.Portfolio[underlyingSymbol];
                decimal assignmentValue;
                
                // If holdings are populated, use them; otherwise compute from event
                if (holding.Invested && holding.HoldingsValue != 0)
                {
                    assignmentValue = Math.Abs(holding.HoldingsValue);
                }
                else
                {
                    // Compute from assignment event: contracts × multiplier × fill price
                    var contracts = Math.Abs(assignmentEvent.FillQuantity);
                    var underlyingPrice = algorithm.Securities.ContainsKey(underlyingSymbol) 
                        ? algorithm.Securities[underlyingSymbol].Price 
                        : assignmentEvent.FillPrice;
                    
                    assignmentValue = contracts * contractMultiplier * underlyingPrice;
                }
                
                var portfolioImpact = algorithm.Portfolio.TotalPortfolioValue > 0 
                    ? assignmentValue / algorithm.Portfolio.TotalPortfolioValue * 100 
                    : 0;
                
                algorithm.Log($"ASSIGNMENT IMPACT: {underlyingSymbol.Value} - " +
                             $"Value: ${assignmentValue:F0}, Portfolio Impact: {portfolioImpact:F1}%, " +
                             $"Free Margin: ${algorithm.Portfolio.MarginRemaining:F0}");
            }
            catch (Exception ex)
            {
                algorithm.Debug($"Error logging assignment impact: {ex.Message}");
            }
        }
        
        /// <summary>
        /// Handle cash-settled index option assignments (SPX).
        /// These options settle in cash and don't create underlying positions.
        /// </summary>
        /// <param name="algorithm">The QC algorithm instance</param>
        /// <param name="assignmentEvent">The assignment order event from QC</param>
        private static void HandleCashSettledAssignment(QCAlgorithm algorithm, OrderEvent assignmentEvent)
        {
            try
            {
                // For cash-settled options, calculate the settlement value
                var optionSymbol = assignmentEvent.Symbol;
                var underlyingSymbol = GetUnderlyingSymbol(optionSymbol);
                
                if (underlyingSymbol == null)
                {
                    algorithm.Error($"Could not determine underlying symbol for cash-settled assignment: {optionSymbol}");
                    return;
                }
                
                // Get current underlying price for settlement calculation
                var underlyingPrice = algorithm.Securities.ContainsKey(underlyingSymbol) 
                    ? algorithm.Securities[underlyingSymbol].Price 
                    : 0m;
                
                // Log the cash settlement details
                algorithm.Log($"CASH SETTLEMENT: {optionSymbol} based on {underlyingSymbol.Value} price: ${underlyingPrice:F2}");
                algorithm.Log($"Settlement Value: ${assignmentEvent.FillPrice:F2} per contract");
                algorithm.Log($"Total Settlement: ${assignmentEvent.FillPrice * Math.Abs(assignmentEvent.FillQuantity):F2}");
                
                // Log assignment impact for cash-settled options
                LogCashSettledAssignmentImpact(algorithm, assignmentEvent, underlyingSymbol, underlyingPrice);
            }
            catch (Exception ex)
            {
                algorithm.Error($"Error handling cash-settled assignment: {ex.Message}");
            }
        }
        
        /// <summary>
        /// Log assignment impact for cash-settled index options.
        /// </summary>
        private static void LogCashSettledAssignmentImpact(QCAlgorithm algorithm, OrderEvent assignmentEvent, 
            Symbol underlyingSymbol, decimal underlyingPrice)
        {
            try
            {
                var settlementValue = Math.Abs(assignmentEvent.FillPrice * assignmentEvent.FillQuantity);
                var portfolioImpact = algorithm.Portfolio.TotalPortfolioValue > 0 
                    ? settlementValue / algorithm.Portfolio.TotalPortfolioValue * 100 
                    : 0;
                
                algorithm.Log($"ASSIGNMENT IMPACT: {underlyingSymbol.Value} - " +
                             $"Value: ${settlementValue:F0}, Portfolio Impact: {portfolioImpact:F1}%, " +
                             $"Free Margin: ${algorithm.Portfolio.MarginRemaining:F0}");
            }
            catch (Exception ex)
            {
                algorithm.Debug($"Error logging cash-settled assignment impact: {ex.Message}");
            }
        }
        
        /// <summary>
        /// Check for unexpected equity positions that might indicate unhandled assignments.
        /// This can be called periodically to monitor for assignment handling issues.
        /// </summary>
        public static void MonitorUnexpectedEquityPositions(QCAlgorithm algorithm)
        {
            try
            {
                var unexpectedEquities = 0;
                
                // Use QC's Portfolio.Values to check all positions
                foreach (var holding in algorithm.Portfolio.Values)
                {
                    if (holding.Invested && 
                        holding.Symbol.SecurityType == SecurityType.Equity &&
                        holding.Quantity != 0)
                    {
                        unexpectedEquities++;
                        algorithm.Log($"WARNING: UNEXPECTED EQUITY POSITION: {holding.Symbol.Value} - " +
                                     $"Qty: {holding.Quantity}, Value: ${holding.HoldingsValue:F0}");
                    }
                }
                
                if (unexpectedEquities > 0)
                {
                    algorithm.Log($"ALERT: Found {unexpectedEquities} unexpected equity positions - " +
                                 $"Check assignment handling logic");
                }
            }
            catch (Exception ex)
            {
                algorithm.Debug($"Error monitoring equity positions: {ex.Message}");
            }
        }
    }
}
using System;

namespace CoreAlgo.Architecture.Core.Helpers
{
    /// <summary>
    /// Helper methods for volatility calculations including forward volatility.
    /// </summary>
    public static class VolatilityMath
    {
        /// <summary>
        /// Computes forward volatility between two maturities using the forward variance identity.
        /// 
        /// Formula:
        ///   T1 = nearDte / 365, T2 = farDte / 365
        ///   TV1 = nearIv^2 * T1, TV2 = farIv^2 * T2
        ///   ForwardVar = (TV2 - TV1) / (T2 - T1)
        ///   ForwardVol = sqrt(ForwardVar)
        /// 
        /// Returns false if inputs are invalid (T2 &lt;= T1, negative forward variance, etc.)
        /// </summary>
        /// <param name="nearIv">Near maturity implied volatility (as decimal, e.g., 0.45 for 45%)</param>
        /// <param name="nearDte">Near maturity days to expiration</param>
        /// <param name="farIv">Far maturity implied volatility (as decimal, e.g., 0.35 for 35%)</param>
        /// <param name="farDte">Far maturity days to expiration</param>
        /// <param name="forwardVol">Output: computed forward volatility (annualized, as decimal)</param>
        /// <returns>True if computation succeeded, false if inputs are invalid</returns>
        public static bool TryComputeForwardVolatility(
            decimal nearIv, 
            int nearDte, 
            decimal farIv, 
            int farDte, 
            out decimal forwardVol)
        {
            forwardVol = 0m;

            // Convert to years
            var T1 = nearDte / 365m;
            var T2 = farDte / 365m;

            // Validate: T2 must be > T1
            if (T2 <= T1)
                return false;

            // Compute total variances
            var tv1 = nearIv * nearIv * T1;
            var tv2 = farIv * farIv * T2;

            // Compute forward variance
            var fwdVar = (tv2 - tv1) / (T2 - T1);

            // Check for negative or zero forward variance
            if (fwdVar <= 0m)
                return false;

            // Compute forward volatility
            forwardVol = (decimal)Math.Sqrt((double)fwdVar);
            return true;
        }
    }
}

using System;
using System.Collections.Generic;
using System.Linq;
using QuantConnect;
using QuantConnect.Securities;
using CoreAlgo.Architecture.Core.Interfaces;

namespace CoreAlgo.Architecture.Core.Implementations
{
    /// <summary>
    /// Validates sufficient collateral for short option positions
    /// Prevents overselling calls against limited shares or puts against limited cash
    /// </summary>
    public class CollateralValidationRule : IPositionOverlapRule
    {
        private readonly IAlgorithmContext _context;
        private readonly object _logger;

        public string RuleName => "CollateralValidationRule";
        public string Description => "Ensures sufficient collateral for short option positions";
        public bool IsEnabled { get; set; } = true;

        public CollateralValidationRule(IAlgorithmContext context)
        {
            _context = context;
            _logger = context.Logger;
        }

        public ValidationResult Validate(
            Symbol proposedSymbol, 
            decimal quantity, 
            IEnumerable<KeyValuePair<Symbol, SecurityHolding>> existingPositions,
            string strategyTag = "")
        {
            if (!IsEnabled) 
                return ValidationResult.Success();

            try
            {
                // Only validate option positions
                if (proposedSymbol.SecurityType != SecurityType.Option)
                    return ValidationResult.Success();

                // Only validate short positions (negative quantity)
                if (quantity >= 0)
                    return ValidationResult.Success();

                // Skip validation for multi-leg strategies (spreads, Iron Condors, etc.)
                // These strategies manage their own risk through spread construction
                if (IsMultiLegStrategy(strategyTag))
                {
                    ((dynamic)_logger).Debug($"[{RuleName}] Skipping collateral validation for multi-leg strategy: {strategyTag}");
                    return ValidationResult.Success();
                }

                var underlying = proposedSymbol.Underlying;
                var optionRight = proposedSymbol.ID.OptionRight;
                var contractMultiplier = 100; // Standard option contract size

                if (optionRight == OptionRight.Call)
                {
                    return ValidateCallCollateral(underlying, quantity, existingPositions, contractMultiplier);
                }
                else if (optionRight == OptionRight.Put)
                {
                    return ValidatePutCollateral(proposedSymbol, quantity, existingPositions, contractMultiplier);
                }

                return ValidationResult.Success();
            }
            catch (System.Exception ex)
            {
                ((dynamic)_logger).Error($"[{RuleName}] Error validating collateral: {ex.Message}");
                return ValidationResult.Error($"Collateral validation error: {ex.Message}");
            }
        }

        /// <summary>
        /// Validates sufficient shares for covered call positions
        /// </summary>
        private ValidationResult ValidateCallCollateral(
            Symbol underlying,
            decimal proposedQuantity,
            IEnumerable<KeyValuePair<Symbol, SecurityHolding>> existingPositions,
            int contractMultiplier)
        {
            try
            {
                // Get current stock holdings
                var stockHolding = _context.Algorithm.Portfolio[underlying];
                var availableShares = stockHolding?.Quantity ?? 0;

                // Calculate total short calls (existing + proposed)
                var existingShortCalls = existingPositions
                    .Where(p => p.Key.SecurityType == SecurityType.Option &&
                               p.Key.Underlying == underlying &&
                               p.Key.ID.OptionRight == OptionRight.Call &&
                               p.Value.Quantity < 0)
                    .Sum(p => -p.Value.Quantity); // Convert to positive for counting

                var totalShortCalls = existingShortCalls + (-proposedQuantity);
                var sharesRequired = totalShortCalls * contractMultiplier;

                ((dynamic)_logger).Debug($"[{RuleName}] Call validation: " +
                             $"Available shares: {availableShares}, " +
                             $"Required: {sharesRequired}, " +
                             $"Existing short calls: {existingShortCalls}, " +
                             $"Proposed: {-proposedQuantity}");

                if (sharesRequired > availableShares)
                {
                    return ValidationResult.Blocked(
                        $"Insufficient shares for covered call: " +
                        $"Need {sharesRequired} shares, have {availableShares}. " +
                        $"Total short calls would be {totalShortCalls}.");
                }

                return ValidationResult.Success();
            }
            catch (System.Exception ex)
            {
                ((dynamic)_logger).Warning($"[{RuleName}] Error in call validation: {ex.Message}");
                // Allow the trade if we can't validate (conservative but functional)
                return ValidationResult.Success();
            }
        }

        /// <summary>
        /// Validates sufficient cash for cash-secured put positions
        /// </summary>
        private ValidationResult ValidatePutCollateral(
            Symbol proposedSymbol,
            decimal proposedQuantity,
            IEnumerable<KeyValuePair<Symbol, SecurityHolding>> existingPositions,
            int contractMultiplier)
        {
            try
            {
                var underlying = proposedSymbol.Underlying;
                var strike = proposedSymbol.ID.StrikePrice;

                // Get available cash
                var availableCash = _context.Algorithm.Portfolio.Cash;

                // Calculate cash required for this put
                var cashRequiredForNewPut = Math.Abs(proposedQuantity) * strike * contractMultiplier;

                // Calculate cash already committed to existing cash-secured puts
                var existingCashCommitment = existingPositions
                    .Where(p => p.Key.SecurityType == SecurityType.Option &&
                               p.Key.Underlying == underlying &&
                               p.Key.ID.OptionRight == OptionRight.Put &&
                               p.Value.Quantity < 0)
                    .Sum(p => Math.Abs(p.Value.Quantity) * p.Key.ID.StrikePrice * contractMultiplier);

                var totalCashRequired = existingCashCommitment + cashRequiredForNewPut;

                ((dynamic)_logger).Debug($"[{RuleName}] Put validation: " +
                             $"Available cash: ${availableCash:F2}, " +
                             $"Total required: ${totalCashRequired:F2}, " +
                             $"Existing commitment: ${existingCashCommitment:F2}, " +
                             $"New requirement: ${cashRequiredForNewPut:F2}");

                if (totalCashRequired > availableCash)
                {
                    return ValidationResult.Blocked(
                        $"Insufficient cash for cash-secured put: " +
                        $"Need ${totalCashRequired:F2}, have ${availableCash:F2}. " +
                        $"Put strike: ${strike}, quantity: {Math.Abs(proposedQuantity)}");
                }

                return ValidationResult.Success();
            }
            catch (System.Exception ex)
            {
                ((dynamic)_logger).Warning($"[{RuleName}] Error in put validation: {ex.Message}");
                // Allow the trade if we can't validate
                return ValidationResult.Success();
            }
        }

        /// <summary>
        /// Gets total buying power reduction for all positions on an underlying
        /// </summary>
        private decimal GetBuyingPowerReduction(Symbol underlying, 
            IEnumerable<KeyValuePair<Symbol, SecurityHolding>> positions)
        {
            try
            {
                return positions
                    .Where(p => GetPositionUnderlying(p.Key) == underlying)
                    .Sum(p => Math.Abs(p.Value.HoldingsValue));
            }
            catch
            {
                return 0m;
            }
        }

        /// <summary>
        /// Helper to get underlying symbol from any security type
        /// </summary>
        private Symbol GetPositionUnderlying(Symbol symbol)
        {
            return symbol.SecurityType == SecurityType.Option ? symbol.Underlying : symbol;
        }

        /// <summary>
        /// Determines if this is a multi-leg strategy based on current validation context
        /// Multi-leg strategies manage their own risk through spread construction
        /// </summary>
        private bool IsMultiLegStrategy(string strategyTag)
        {
            // Check for common multi-leg indicators in tag (keep generic terms only)
            if (!string.IsNullOrEmpty(strategyTag))
            {
                var tag = strategyTag.ToUpperInvariant();
                if (tag.Contains("COMBO") || tag.Contains("MULTI") || tag.Contains("SPREAD"))
                    return true;
            }
            
            // Try to determine from strategy configuration if available
            try
            {
                var algorithm = _context.Algorithm;
                if (algorithm != null)
                {
                    var comboLegCount = algorithm.GetParameter("ComboOrderLegCount");
                    if (int.TryParse(comboLegCount, out var legCount) && legCount > 1)
                    {
                        return true;
                    }
                }
            }
            catch (Exception ex)
            {
                ((dynamic)_logger).Debug($"[MULTI-LEG DETECTION] Could not read ComboOrderLegCount: {ex.Message}");
            }
            
            // Default to false for single-leg strategies
            return false;
        }
    }
}
namespace CoreAlgo.Architecture.Core.Implementations
{
    /// <summary>
    /// Log levels for centralized logging with level checking
    /// Matches CentralAlgorithm pattern: ERROR=0, WARNING=1, INFO=2, DEBUG=3
    /// </summary>
    public enum LogLevel
    {
        /// <summary>
        /// Error messages - highest priority, always logged
        /// </summary>
        Error = 0,
        
        /// <summary>
        /// Warning messages - important issues that don't stop execution
        /// </summary>
        Warning = 1,
        
        /// <summary>
        /// Information messages - general algorithm flow and status
        /// </summary>
        Info = 2,
        
        /// <summary>
        /// Debug messages - detailed execution information, lowest priority
        /// </summary>
        Debug = 3
    }
}
#region imports
    using System;
    using System.Collections;
    using System.Collections.Generic;
    using System.Collections.Concurrent;
    using System.Linq;
    using System.Globalization;
    using System.Drawing;
    using System.Text.RegularExpressions;
    using System.Security.Cryptography;
    using System.Text;
    using QuantConnect;
    using QuantConnect.Algorithm.Framework;
    using QuantConnect.Algorithm.Framework.Selection;
    using QuantConnect.Algorithm.Framework.Alphas;
    using QuantConnect.Algorithm.Framework.Portfolio;
    using QuantConnect.Algorithm.Framework.Portfolio.SignalExports;
    using QuantConnect.Algorithm.Framework.Execution;
    using QuantConnect.Algorithm.Framework.Risk;
    using QuantConnect.Algorithm.Selection;
    using QuantConnect.Api;
    using QuantConnect.Parameters;
    using QuantConnect.Benchmarks;
    using QuantConnect.Brokerages;
    using QuantConnect.Commands;
    using QuantConnect.Configuration;
    using QuantConnect.Util;
    using QuantConnect.Interfaces;
    using QuantConnect.Algorithm;
    using QuantConnect.Indicators;
    using QuantConnect.Data;
    using QuantConnect.Data.Auxiliary;
    using QuantConnect.Data.Consolidators;
    using QuantConnect.Data.Custom;
    using QuantConnect.Data.Custom.IconicTypes;
    using QuantConnect.DataSource;
    using QuantConnect.Data.Fundamental;
    using QuantConnect.Data.Market;
    using QuantConnect.Data.Shortable;
    using QuantConnect.Data.UniverseSelection;
    using QuantConnect.Notifications;
    using QuantConnect.Orders;
    using QuantConnect.Orders.Fees;
    using QuantConnect.Orders.Fills;
    using QuantConnect.Orders.OptionExercise;
    using QuantConnect.Orders.Slippage;
    using QuantConnect.Orders.TimeInForces;
    using QuantConnect.Python;
    using QuantConnect.Scheduling;
    using QuantConnect.Securities;
    using QuantConnect.Securities.Equity;
    using QuantConnect.Securities.Future;
    using QuantConnect.Securities.Option;
    using QuantConnect.Securities.Positions;
    using QuantConnect.Securities.Forex;
    using QuantConnect.Securities.Crypto;
    using QuantConnect.Securities.CryptoFuture;
    using QuantConnect.Securities.IndexOption;
    using QuantConnect.Securities.Interfaces;
    using QuantConnect.Securities.Volatility;
    using QuantConnect.Storage;
    using QuantConnect.Statistics;
    using QCAlgorithmFramework = QuantConnect.Algorithm.QCAlgorithm;
    using QCAlgorithmFrameworkBridge = QuantConnect.Algorithm.QCAlgorithm;
    using Calendar = QuantConnect.Data.Consolidators.Calendar;
    using CoreAlgo.Architecture.Core.Interfaces;
#endregion

namespace CoreAlgo.Architecture.Core.Implementations
{
    /// <summary>
    /// Trade highlight event types for logging important trade actions
    /// </summary>
    public enum HighlightKind
    {
        Entry,
        TakeProfit,
        StopLoss,
        TrailingStop,
        PartialFill,
        Cancel
    }

    /// <summary>
    /// Non-generic base class to hold shared static collections for all logger instances
    /// </summary>
    public static class SmartLoggerStore
    {
        // Shared static storage for ALL logger instances (thread-safe)
        public static readonly ConcurrentDictionary<string, List<SmartLogMessage>> DailyMessages = 
            new ConcurrentDictionary<string, List<SmartLogMessage>>();
        public static readonly ConcurrentDictionary<string, MessageGroup> MessageGroups = 
            new ConcurrentDictionary<string, MessageGroup>();
        public static DateTime? LastProcessedDay = null;
        public static readonly object ProcessLock = new object();

        // Trade highlight metrics (ephemeral scope-based context)
        public static readonly ConcurrentDictionary<string, Dictionary<string, string>> HighlightContexts = 
            new ConcurrentDictionary<string, Dictionary<string, string>>();
        public static readonly ConcurrentQueue<SmartLogMessage> Highlights = new ConcurrentQueue<SmartLogMessage>();
        
        // Limits for highlight metrics
        private const int MaxMetricsPerScope = 12;
        private const int MaxMetricChars = 80;

        // Throttling for high-frequency messages
        private static readonly ConcurrentDictionary<string, (DateTime lastSeen, int count)> MessageThrottle = 
            new ConcurrentDictionary<string, (DateTime, int)>();
        private static readonly TimeSpan ThrottleWindow = TimeSpan.FromMinutes(1);
        private const int MaxMessagesPerWindow = 50;

        /// <summary>
        /// Get current collection counts for debugging
        /// </summary>
        public static (int dailyMessages, int messageGroups) GetCollectionCounts()
        {
            return (DailyMessages.Count, MessageGroups.Count);
        }

        /// <summary>
        /// Check if message should be throttled due to high frequency
        /// </summary>
        public static bool ShouldThrottleMessage(string baseHash, DateTime currentTime)
        {
            var throttleKey = $"throttle_{baseHash}";
            
            var info = MessageThrottle.GetOrAdd(throttleKey, _ => (currentTime, 0));
            
            // Reset count if window has expired
            if (currentTime - info.lastSeen > ThrottleWindow)
            {
                MessageThrottle.TryUpdate(throttleKey, (currentTime, 1), info);
                return false;
            }
            
            // Increment count
            var newCount = info.count + 1;
            MessageThrottle.TryUpdate(throttleKey, (currentTime, newCount), info);
            
            // Throttle if exceeded limit
            return newCount > MaxMessagesPerWindow;
        }

        /// <summary>
        /// Clean old throttle entries to prevent memory leaks
        /// </summary>
        private static void CleanOldThrottleEntries(DateTime currentDay)
        {
            var cutoff = currentDay.AddDays(-1); // Keep entries from yesterday and today only
            var keysToRemove = new List<string>();
            
            foreach (var kvp in MessageThrottle)
            {
                if (kvp.Value.lastSeen.Date < cutoff.Date)
                {
                    keysToRemove.Add(kvp.Key);
                }
            }
            
            foreach (var key in keysToRemove)
            {
                MessageThrottle.TryRemove(key, out _);
            }
        }

        /// <summary>
        /// Merge metrics into a scope's context (thread-safe)
        /// </summary>
        public static void MergeMetrics(string scopeKey, Dictionary<string, string> metrics)
        {
            if (string.IsNullOrEmpty(scopeKey) || metrics == null || metrics.Count == 0)
                return;

            var context = HighlightContexts.GetOrAdd(scopeKey, _ => new Dictionary<string, string>());
            
            lock (context)
            {
                foreach (var kvp in metrics)
                {
                    if (context.Count >= MaxMetricsPerScope)
                        break;
                    
                    var value = kvp.Value;
                    if (value.Length > MaxMetricChars)
                        value = value.Substring(0, MaxMetricChars - 3) + "...";
                    
                    context[kvp.Key] = value;
                }
            }
        }

        /// <summary>
        /// Try to get and clear metrics for a scope (thread-safe)
        /// </summary>
        public static bool TryGetAndClearMetrics(string scopeKey, out Dictionary<string, string> metrics)
        {
            metrics = null;
            if (string.IsNullOrEmpty(scopeKey))
                return false;

            if (HighlightContexts.TryRemove(scopeKey, out var context))
            {
                lock (context)
                {
                    metrics = new Dictionary<string, string>(context);
                }
                return metrics.Count > 0;
            }
            
            return false;
        }

        /// <summary>
        /// Clear metrics for a specific scope
        /// </summary>
        public static void ClearHighlightMetrics(string scopeKey)
        {
            if (!string.IsNullOrEmpty(scopeKey))
            {
                HighlightContexts.TryRemove(scopeKey, out _);
            }
        }

        /// <summary>
        /// Add a highlight message for EOD processing
        /// </summary>
        public static void AddHighlight(SmartLogMessage message)
        {
            Highlights.Enqueue(message);
        }

        /// <summary>
        /// Try to dequeue all highlights for processing
        /// </summary>
        public static List<SmartLogMessage> TryDequeueAllHighlights()
        {
            var result = new List<SmartLogMessage>();
            while (Highlights.TryDequeue(out var msg))
            {
                result.Add(msg);
            }
            return result;
        }

        /// <summary>
        /// Process and output accumulated daily logs with smart summarization
        /// </summary>
        public static void ProcessDailyLogs(QCAlgorithm algorithm, int maxHighlightsPerDay = 200)
        {   
            // Skip daily log processing entirely in live mode
            if (algorithm.LiveMode)
                return;
                
            lock (ProcessLock)
            {
                var currentDay = algorithm.Time.Date;
                
                // FIXED: Match Python centralalgorithm logic exactly
                // Always skip if already processed today (no conditions about message counts)
                if (LastProcessedDay.HasValue && LastProcessedDay.Value == currentDay)
                {
                    return; // First OnEndOfDay() call processes, subsequent calls are ignored
                }
                
                algorithm.Log($"=== DEBUG: Collection counts - Daily: {DailyMessages.Count}, Groups: {MessageGroups.Count}, Highlights: {Highlights.Count} ===");
                
                algorithm.Log("---------------------------------");
                algorithm.Log($"Daily Log Summary - {currentDay:yyyy-MM-dd}");
                algorithm.Log("---------------------------------");
                
                // Print highlights FIRST (most important trade events)
                var highlights = TryDequeueAllHighlights();
                if (highlights.Any())
                {
                    algorithm.Log("");
                    algorithm.Log($"==== IMPORTANT TRADE EVENTS ({currentDay:yyyy-MM-dd}) ====");
                    
                    var sortedHighlights = highlights.OrderBy(h => h.Timestamp).ToList();
                    var displayCount = Math.Min(sortedHighlights.Count, maxHighlightsPerDay);
                    
                    for (int i = 0; i < displayCount; i++)
                    {
                        var h = sortedHighlights[i];
                        algorithm.Log(h.Message);
                    }
                    
                    if (sortedHighlights.Count > maxHighlightsPerDay)
                    {
                        algorithm.Log($"... and {sortedHighlights.Count - maxHighlightsPerDay} more");
                    }
                    
                    // Count by type (Entry, TP, SL, TS based on message prefixes)
                    var entryCount = highlights.Count(h => h.Message.Contains("==> ENTRY"));
                    var tpCount = highlights.Count(h => h.Message.Contains("<== TP"));
                    var slCount = highlights.Count(h => h.Message.Contains("!! SL"));
                    var tsCount = highlights.Count(h => h.Message.Contains("~~> TS"));
                    
                    algorithm.Log($"Counts: Entry={entryCount}, TP={tpCount}, SL={slCount}, TS={tsCount}");
                    algorithm.Log("");
                }
                
                if (!DailyMessages.Any() && !MessageGroups.Any())
                {
                    algorithm.Log("*** NO SMART MESSAGES WERE COLLECTED ***");
                }
                else
                {
                    algorithm.Log($"Found {DailyMessages.Count} daily message groups and {MessageGroups.Count} statistical groups to process");
                }

                // Process regular messages from DailyMessages
                foreach (var kvp in DailyMessages)
                {
                    var messages = kvp.Value;
                    if (messages.Count == 0) continue;
                    
                    lock (messages)
                    {
                        var firstMsg = messages[0];
                        if (messages.Count > 1)
                        {
                            algorithm.Log($"{firstMsg.Timestamp:HH:mm:ss} {firstMsg.Level} -> " +
                                        $"{firstMsg.ClassName}.{firstMsg.FunctionName}: {firstMsg.Message} " +
                                        $"(repeated {messages.Count} times)");
                        }
                        else
                        {
                            algorithm.Log($"{firstMsg.Timestamp:HH:mm:ss} {firstMsg.Level} -> " +
                                        $"{firstMsg.ClassName}.{firstMsg.FunctionName}: {firstMsg.Message}");
                        }
                    }
                }

                // Process grouped messages with statistical analysis
                foreach (var kvp in MessageGroups)
                {
                    var group = kvp.Value;
                    algorithm.Log(group.GetSummary());
                }

                algorithm.Log("");

                // Clear processed messages
                DailyMessages.Clear();
                MessageGroups.Clear();
                
                // Clear old throttle entries and highlight contexts to prevent memory leaks
                CleanOldThrottleEntries(currentDay);
                HighlightContexts.Clear();
                
                LastProcessedDay = currentDay;
            }
        }
    }

    /// <summary>
    /// Represents a log message with smart hashing and pattern recognition capabilities
    /// </summary>
    public class SmartLogMessage
    {
        public string Level { get; set; }
        public string ClassName { get; set; }
        public string FunctionName { get; set; }
        public string Message { get; set; }
        public DateTime Timestamp { get; set; }
        
        private string _hash;
        private string _baseHash;

        public SmartLogMessage(string level, string className, string functionName, string message, DateTime timestamp)
        {
            Level = level;
            ClassName = className;
            FunctionName = functionName;
            Message = message;
            Timestamp = timestamp;
        }

        /// <summary>
        /// Exact hash for identical messages
        /// </summary>
        public string Hash
        {
            get
            {
                if (_hash == null)
                {
                    var content = $"{Level}|{ClassName}|{FunctionName}|{Message}";
                    _hash = ComputeHash(content);
                }
                return _hash;
            }
        }

        /// <summary>
        /// Pattern-based hash for messages with similar structure but different values
        /// </summary>
        public string BaseHash
        {
            get
            {
                if (_baseHash == null)
                {
                    try
                    {
                        // Normalize the message by replacing numeric values and common variables
                        var template = Regex.Replace(Message, @"[-+]?[0-9,]*\.?[0-9]+(?:[eE][-+]?[0-9]+)?", "NUM");
                        template = Regex.Replace(template, @"\s+", " ");
                        template = template.ToLowerInvariant();
                        
                        // Normalize specific patterns like symbols and states
                        template = Regex.Replace(template, @"(onendofday called for symbol\s+)(.*)", "$1SYMBOL_PLACEHOLDER");
                        template = Regex.Replace(template, @"(strategy state:\s+)(.*)", "$1STATE_PLACEHOLDER");
                        template = Regex.Replace(template, @"(strategy name:\s+)(.*)", "$1NAME_PLACEHOLDER");
                        template = Regex.Replace(template, @"(symbol\s+)([A-Z]{2,5})", "$1SYMBOL_PLACEHOLDER");
                        template = Regex.Replace(template, @"[:\[\]{}()]", "");
                        
                        var content = $"{Level.ToLowerInvariant()}|{ClassName.ToLowerInvariant()}|{FunctionName.ToLowerInvariant()}|{template}";
                        _baseHash = ComputeHash(content);
                    }
                    catch
                    {
                        // Fallback to simpler hash if normalization fails
                        var content = $"{Level}|{ClassName}|{FunctionName}";
                        _baseHash = ComputeHash(content);
                    }
                }
                return _baseHash;
            }
        }

        /// <summary>
        /// Extract numeric value from message for statistical analysis
        /// </summary>
        public double? ExtractValue()
        {
            try
            {
                var matches = Regex.Matches(Message, @"[-+]?[0-9,]*\.?[0-9]+(?:[eE][-+]?[0-9]+)?");
                if (matches.Count > 0)
                {
                    var lastMatch = matches[matches.Count - 1].Value.Replace(",", "");
                    if (double.TryParse(lastMatch, out double value))
                        return value;
                }
            }
            catch
            {
                // Ignore extraction errors
            }
            return null;
        }

        private static string ComputeHash(string input)
        {
            using (var md5 = MD5.Create())
            {
                var hash = md5.ComputeHash(Encoding.UTF8.GetBytes(input));
                return Convert.ToHexString(hash).ToLowerInvariant();
            }
        }
    }

    /// <summary>
    /// Groups messages with similar patterns for statistical analysis
    /// </summary>
    public class MessageGroup
    {
        public string BaseHash { get; private set; }
        public List<SmartLogMessage> Messages { get; private set; }
        public DateTime FirstTime { get; private set; }
        public DateTime LastTime { get; private set; }
        public string Level { get; private set; }
        public string ClassName { get; private set; }
        public string FunctionName { get; private set; }
        public string BaseMessage { get; private set; }

        public MessageGroup(SmartLogMessage firstMessage)
        {
            BaseHash = firstMessage.BaseHash;
            Messages = new List<SmartLogMessage> { firstMessage };
            FirstTime = firstMessage.Timestamp;
            LastTime = firstMessage.Timestamp;
            Level = firstMessage.Level;
            ClassName = firstMessage.ClassName;
            FunctionName = firstMessage.FunctionName;
            BaseMessage = firstMessage.Message;
        }

        public bool TryAddMessage(SmartLogMessage message)
        {
            if (message.BaseHash != BaseHash)
                return false;

            Messages.Add(message);
            LastTime = message.Timestamp;
            return true;
        }

        /// <summary>
        /// Generate statistical summary of grouped messages
        /// </summary>
        public string GetSummary()
        {
            if (Messages.Count == 1)
                return FormatMessage(Messages[0]);

            // Extract values and timestamps from sorted messages
            var sortedMessages = Messages.OrderBy(m => m.Timestamp).ToList();
            var values = new List<double>();
            var timestamps = new List<DateTime>();
            
            foreach (var msg in sortedMessages)
            {
                var value = msg.ExtractValue();
                if (value.HasValue)
                {
                    values.Add(value.Value);
                    timestamps.Add(msg.Timestamp);
                }
            }
            
            if (!values.Any())
                return FormatMessage(Messages[0]);

            // Calculate statistics
            var mean = values.Average();
            var min = values.Min();
            var max = values.Max();
            var count = values.Count;

            // Format time range
            var timeRange = $"{FirstTime:HH:mm:ss}-{LastTime:HH:mm:ss}";

            // Build summary with clear sections like Python version
            var sections = new List<string>
            {
                $"{timeRange} {Level} -> {ClassName}.{FunctionName}: {BaseMessage}",
                $"    Stats: mean={mean:F2}, min={min:F2}, max={max:F2}"
            };

            // Add trend analysis if we have enough data points
            if (count >= 2)
            {
                var trend = GetTrendAnalysis(values, timestamps);
                if (!string.IsNullOrEmpty(trend))
                    sections.Add($"    Trend:{trend}");
            }

            // Add distribution analysis if we have enough data points
            if (count >= 3)
            {
                var distribution = GetDistributionAnalysis(values);
                if (!string.IsNullOrEmpty(distribution))
                    sections.Add($"    {distribution}");
            }

            // Sample count
            sections.Add($"    Samples: {count}");

            return string.Join("\n", sections);
        }

        private string GetTrendAnalysis(List<double> values, List<DateTime> timestamps)
        {
            if (values.Count < 2) return "";

            try
            {
                // Find key changes (local maxima and minima) like Python version
                var keyChanges = new List<(DateTime time, double value)>();
                
                for (int i = 1; i < values.Count - 1; i++)
                {
                    // Look for local maxima and minima
                    if ((values[i] > values[i-1] && values[i] > values[i+1]) ||
                        (values[i] < values[i-1] && values[i] < values[i+1]))
                    {
                        keyChanges.Add((timestamps[i], values[i]));
                    }
                }

                // Limit to most significant changes
                if (keyChanges.Count > 4)
                {
                    // Sort by absolute change magnitude from first value
                    keyChanges = keyChanges.OrderByDescending(x => Math.Abs(x.value - values[0])).Take(4).ToList();
                    // Resort by time
                    keyChanges = keyChanges.OrderBy(x => x.time).ToList();
                }

                if (!keyChanges.Any()) return "";

                // Format changes with timestamps like Python
                var changes = keyChanges.Select(x => 
                    $"{(x.value > values[0] ? "↑" : "↓")}{x.value:F2}({x.time:HH:mm})").ToList();
                return " " + string.Join(" ", changes);
            }
            catch
            {
                return "";
            }
        }

        private string GetDistributionAnalysis(List<double> values)
        {
            if (values.Count < 3) return "";

            try
            {
                var minVal = values.Min();
                var maxVal = values.Max();
                
                // If all values are the same, return special format
                if (Math.Abs(minVal - maxVal) < 0.001)
                    return $"Distribution: [constant={minVal:F2}]";
                
                // Create 3 bins like Python version
                var binSize = (maxVal - minVal) / 3;
                var bins = new int[3];
                
                foreach (var value in values)
                {
                    var binIdx = (int)((value - minVal) / binSize);
                    if (binIdx == 3) binIdx = 2; // Handle edge case for max value
                    bins[binIdx]++;
                }

                // Format distribution
                var distParts = new List<string> { "Distribution:" };
                for (int i = 0; i < 3; i++)
                {
                    if (bins[i] > 0)
                    {
                        var binStart = minVal + i * binSize;
                        var binEnd = minVal + (i + 1) * binSize;
                        distParts.Add($"[{binStart:F1}-{binEnd:F1}: {bins[i]}]");
                    }
                }

                return string.Join(" ", distParts);
            }
            catch
            {
                return "";
            }
        }

        private static string FormatMessage(SmartLogMessage msg)
        {
            return $"{msg.Timestamp:HH:mm:ss} {msg.Level} -> {msg.ClassName}.{msg.FunctionName}: {msg.Message}";
        }
    }

    /// <summary>
    /// QuantConnect-specific logger implementation with smart deduplication
    /// </summary>
    public class QCLogger<T> : ILogger<T>
    {
        private readonly QCAlgorithm _algorithm;
        private readonly string _categoryName;
        private readonly int _currentLogLevel;
        private readonly bool _isLiveMode;
        private readonly bool _verboseMode;

        public QCLogger(QCAlgorithm algorithm, int logLevel = 3)
        {
            _algorithm = algorithm ?? throw new ArgumentNullException(nameof(algorithm));
            _categoryName = typeof(T).Name;
            _currentLogLevel = logLevel;
            _isLiveMode = algorithm.LiveMode;
            _verboseMode = bool.Parse(algorithm.GetParameter("VerboseMode", "false"));
        }

        public void LogInformation(string message, params object[] args)
        {
            var formattedMessage = FormatMessage(message, args);
            if (_isLiveMode)
            {
                var realTimeMessage = FormatRealTimeMessage(formattedMessage, _categoryName, GetCallingFunctionName(), LogLevel.Info);
                _algorithm.Log(realTimeMessage);
                return;
            }
            StoreSmartMessage("INFO", formattedMessage);
        }

        public void LogDebug(string message, params object[] args)
        {
            var formattedMessage = FormatMessage(message, args);
            if (_isLiveMode)
            {
                var realTimeMessage = FormatRealTimeMessage(formattedMessage, _categoryName, GetCallingFunctionName(), LogLevel.Debug);
                _algorithm.Log(realTimeMessage);
                return;
            }
            StoreSmartMessage("DEBUG", formattedMessage);
        }

        public void LogWarning(string message, params object[] args)
        {
            var formattedMessage = FormatMessage(message, args);
            if (_isLiveMode)
            {
                var realTimeMessage = FormatRealTimeMessage(formattedMessage, _categoryName, GetCallingFunctionName(), LogLevel.Warning);
                _algorithm.Log(realTimeMessage);
                return;
            }
            StoreSmartMessage("WARN", formattedMessage);
        }

        public void LogError(string message, params object[] args)
        {
            var formattedMessage = FormatMessage(message, args);
            if (_isLiveMode)
            {
                var realTimeMessage = FormatRealTimeMessage(formattedMessage, _categoryName, GetCallingFunctionName(), LogLevel.Error);
                _algorithm.Log(realTimeMessage);
                return;
            }
            StoreSmartMessage("ERROR", formattedMessage);
        }

        public void LogError(Exception exception, string message, params object[] args)
        {
            var formattedMessage = FormatMessage(message, args);
            var fullMessage = $"{formattedMessage} - Exception: {exception.Message}";
            if (_isLiveMode)
            {
                var realTimeMessage = FormatRealTimeMessage(fullMessage, _categoryName, GetCallingFunctionName(), LogLevel.Error);
                _algorithm.Log(realTimeMessage);
                var stackTraceMessage = FormatRealTimeMessage($"StackTrace: {exception.StackTrace}", _categoryName, GetCallingFunctionName(), LogLevel.Error);
                _algorithm.Log(stackTraceMessage);
                return;
            }
            StoreSmartMessage("ERROR", fullMessage);
            StoreSmartMessage("ERROR", $"StackTrace: {exception.StackTrace}");
        }

        /// <summary>
        /// Central logging method with level checking - implements context pattern like CentralAlgorithm
        /// </summary>
        /// <param name="message">Message to log</param>
        /// <param name="level">Log level for filtering</param>
        public void LogMessage(string message, LogLevel level = LogLevel.Debug)
        {
            // Level checking: only log if level is at or below current threshold
            if ((int)level > _currentLogLevel)
                return;

            // In live mode, output immediately for all levels
            if (_isLiveMode)
            {
                var realTimeMessage = FormatRealTimeMessage(message, _categoryName, GetCallingFunctionName(), level);
                _algorithm.Log(realTimeMessage);
                return;
            }

            // Route to appropriate logging method based on level
            switch (level)
            {
                case LogLevel.Error:
                    LogError(message);
                    break;
                case LogLevel.Warning:
                    LogWarning(message);
                    break;
                case LogLevel.Info:
                    LogInformation(message);
                    break;
                case LogLevel.Debug:
                    LogDebug(message);
                    break;
                default:
                    LogDebug(message);
                    break;
            }
        }

        /// <summary>
        /// Convenience method for error logging
        /// </summary>
        public void Error(string message) => LogMessage(message, LogLevel.Error);

        /// <summary>
        /// Convenience method for warning logging
        /// </summary>
        public void Warning(string message) => LogMessage(message, LogLevel.Warning);

        /// <summary>
        /// Convenience method for information logging
        /// </summary>
        public void Info(string message) => LogMessage(message, LogLevel.Info);

        /// <summary>
        /// Convenience method for debug logging
        /// </summary>
        public void Debug(string message) => LogMessage(message, LogLevel.Debug);

        /// <summary>
        /// Build a stable scope key for highlight metrics (Symbol:TradeId)
        /// </summary>
        public string BuildScope(Symbol symbol, int? tradeId = null)
        {
            return $"{symbol.Value}:{tradeId?.ToString() ?? "_"}";
        }

        /// <summary>
        /// Add a single metric to a highlight scope
        /// </summary>
        public void AddHighlightMetric(string scopeKey, string key, object value, int? decimalPlaces = null)
        {
            if (string.IsNullOrEmpty(scopeKey) || string.IsNullOrEmpty(key))
                return;

            var formattedValue = FormatMetricValue(value, decimalPlaces);
            var metrics = new Dictionary<string, string> { { key, formattedValue } };
            SmartLoggerStore.MergeMetrics(scopeKey, metrics);
        }

        /// <summary>
        /// Add multiple metrics to a highlight scope
        /// </summary>
        public void AddHighlightMetrics(string scopeKey, IDictionary<string, object> metrics, int? decimalPlaces = null)
        {
            if (string.IsNullOrEmpty(scopeKey) || metrics == null || metrics.Count == 0)
                return;

            var formatted = new Dictionary<string, string>();
            foreach (var kvp in metrics)
            {
                formatted[kvp.Key] = FormatMetricValue(kvp.Value, decimalPlaces);
            }
            
            SmartLoggerStore.MergeMetrics(scopeKey, formatted);
        }

        /// <summary>
        /// Clear metrics for a specific scope
        /// </summary>
        public void ClearHighlightMetrics(string scopeKey)
        {
            SmartLoggerStore.ClearHighlightMetrics(scopeKey);
        }

        /// <summary>
        /// Log a trade highlight event with optional metrics
        /// </summary>
        public void HighlightTradeEvent(string className, string methodName, HighlightKind kind, string oneLine, string scopeKey = null)
        {
            // Check if highlights are enabled
            var highlightEnabled = bool.Parse(_algorithm.GetParameter("HighlightTrades", "true"));
            if (!highlightEnabled)
                return;

            // Get immediate print settings
            var immediateEnabled = bool.Parse(_algorithm.GetParameter("HighlightImmediate", "true"));
            var includeMetrics = bool.Parse(_algorithm.GetParameter("HighlightIncludeStrategyMetrics", "true"));

            // Build final message with metrics suffix if available
            var finalLine = oneLine;
            if (includeMetrics && !string.IsNullOrEmpty(scopeKey) && 
                SmartLoggerStore.TryGetAndClearMetrics(scopeKey, out var metrics))
            {
                var suffix = CoreAlgo.Architecture.Core.Services.TradeHighlightFormatter.FormatMetricsSuffix(metrics);
                if (!string.IsNullOrEmpty(suffix))
                {
                    finalLine = $"{oneLine} {suffix}";
                }
            }

            // Immediate print (live mode only - backtests rely on EOD summary)
            if (_isLiveMode && immediateEnabled)
            {
                _algorithm.Log(finalLine);
            }

            // Store for EOD processing in backtests (bypasses throttle for highlights)
            if (!_isLiveMode)
            {
                var msg = new SmartLogMessage("INFO", className, methodName, finalLine, _algorithm.Time);
                SmartLoggerStore.AddHighlight(msg);
            }
        }

        /// <summary>
        /// Format a metric value for display (handles decimals, strings, booleans)
        /// </summary>
        private string FormatMetricValue(object value, int? decimalPlaces)
        {
            if (value == null)
                return "null";

            if (value is decimal d)
                return d.ToString(decimalPlaces.HasValue ? $"F{decimalPlaces.Value}" : "F2");
            
            if (value is double dbl)
                return dbl.ToString(decimalPlaces.HasValue ? $"F{decimalPlaces.Value}" : "F2");
            
            if (value is float f)
                return f.ToString(decimalPlaces.HasValue ? $"F{decimalPlaces.Value}" : "F2");
            
            if (value is int || value is long || value is short)
                return value.ToString();
            
            if (value is bool b)
                return b ? "T" : "F";
            
            // String or other - trim and sanitize
            var str = value.ToString();
            if (str.Length > 20)
                str = str.Substring(0, 17) + "...";
            
            return str;
        }

        /// <summary>
        /// Logs a message with explicit context information for better traceability
        /// Uses LogLevel-based output control: ERROR/WARNING immediate, INFO conditional, DEBUG batched
        /// </summary>
        public void LogWithContext(string message, string className, string methodName, LogLevel level = LogLevel.Info)
        {
            // Level checking: only log if level is at or below current threshold
            if ((int)level > _currentLogLevel)
                return;

            // Try dynamic context detection first (like Python logger)
            if (string.IsNullOrEmpty(className) || className == "SimpleBaseStrategy")
            {
                var (dynClass, dynMethod) = GetCallerContextDynamic();
                if (dynClass != "Unknown")
                {
                    className = dynClass;
                    methodName = dynMethod;
                }
            }

            // In live mode, output immediately for all levels and skip batching
            if (_isLiveMode)
            {
                var formattedMessage = FormatRealTimeMessage(message, className, methodName, level);
                _algorithm.Log(formattedMessage);
                return;
            }

            // LogLevel-based output decision
            bool shouldOutputImmediate = level <= LogLevel.Warning || // ERROR/WARNING always immediate
                                        (_verboseMode && level == LogLevel.Info) || // INFO if verbose
                                        (_isLiveMode && level == LogLevel.Info); // INFO if live

            if (shouldOutputImmediate)
            {
                var formattedMessage = FormatRealTimeMessage(message, className, methodName, level);
                _algorithm.Log(formattedMessage);
            }

            // Still batch DEBUG and non-verbose INFO for end-of-day summary
            if (!shouldOutputImmediate || level == LogLevel.Debug)
            {
                StoreSmartMessageWithContext(level.ToString().ToUpper(), message, className, methodName);
            }
        }

        /// <summary>
        /// Store message for smart processing with grouping for statistical analysis
        /// </summary>
        private void StoreSmartMessage(string level, string message)
        {
            // Skip storing in live mode to avoid memory accumulation
            if (_isLiveMode)
                return;
                
            var functionName = GetCallingFunctionName();
            var smartMessage = new SmartLogMessage(level, _categoryName, functionName, message, _algorithm.Time);
            
            // Apply throttling for high-frequency messages (especially DEBUG level)
            if (level == "DEBUG" && SmartLoggerStore.ShouldThrottleMessage(smartMessage.BaseHash, _algorithm.Time))
            {
                return; // Skip this message due to throttling
            }
            
            // Try to add to existing message group for messages with numeric values
            var numericValue = smartMessage.ExtractValue();
            
            if (numericValue.HasValue)
            {
                var existingGroup = SmartLoggerStore.MessageGroups.GetOrAdd(smartMessage.BaseHash, 
                    _ => new MessageGroup(smartMessage));
                
                if (existingGroup.BaseHash == smartMessage.BaseHash)
                {
                    if (existingGroup.TryAddMessage(smartMessage))
                    {
                        return; // Successfully added to group
                    }
                }
            }
            
            // Store as regular message if no numeric value or grouping failed
            var messageList = SmartLoggerStore.DailyMessages.GetOrAdd(smartMessage.Hash, _ => new List<SmartLogMessage>());
            lock (messageList)
            {
                messageList.Add(smartMessage);
            }
        }

        /// <summary>
        /// Store message for smart processing with explicit context
        /// </summary>
        private void StoreSmartMessageWithContext(string level, string message, string className, string methodName)
        {
            // Skip storing in live mode to avoid memory accumulation
            if (_isLiveMode)
                return;
                
            var smartMessage = new SmartLogMessage(level, className, methodName, message, _algorithm.Time);
            
            // Apply throttling for high-frequency messages (especially DEBUG level)
            if (level == "DEBUG" && SmartLoggerStore.ShouldThrottleMessage(smartMessage.BaseHash, _algorithm.Time))
            {
                return; // Skip this message due to throttling
            }
            
            // Try to add to existing message group for messages with numeric values
            var numericValue = smartMessage.ExtractValue();
            
            if (numericValue.HasValue)
            {
                var existingGroup = SmartLoggerStore.MessageGroups.GetOrAdd(smartMessage.BaseHash, 
                    _ => new MessageGroup(smartMessage));
                
                if (existingGroup.BaseHash == smartMessage.BaseHash)
                {
                    if (existingGroup.TryAddMessage(smartMessage))
                    {
                        return; // Successfully added to group
                    }
                }
            }
            
            // Store as regular message if no numeric value or grouping failed
            var messageList = SmartLoggerStore.DailyMessages.GetOrAdd(smartMessage.Hash, _ => new List<SmartLogMessage>());
            lock (messageList)
            {
                messageList.Add(smartMessage);
            }
        }

        /// <summary>
        /// Format message for real-time output
        /// </summary>
        private string FormatRealTimeMessage(string message, string className, string methodName, LogLevel level)
        {
            var timestamp = _algorithm.Time.ToString("HH:mm:ss");
            var realTime = DateTime.Now.ToString("HH:mm:ss.fff");
            
            // Format: "09:31:00 (15:24:33.123) [ORBTemplate.OnInitialize] INFO: Message"
            if (_isLiveMode)
            {
                return $"{timestamp} [{className}.{methodName}] {level.ToString().ToUpper()}: {message}";
            }
            else
            {
                // Include real time in backtest for debugging
                return $"{timestamp} (Real: {realTime}) [{className}.{methodName}] {level.ToString().ToUpper()}: {message}";
            }
        }


        private static string GetCallingFunctionName()
        {
            try
            {
                var stackTrace = new System.Diagnostics.StackTrace();
                // Skip current method, StoreSmartMessage, and LogXXX method
                var frame = stackTrace.GetFrame(3);
                return frame?.GetMethod()?.Name ?? "Unknown";
            }
            catch
            {
                return "Unknown";
            }
        }

        /// <summary>
        /// Better stack frame detection for caller context
        /// </summary>
        private static (string className, string methodName) GetCallerContext()
        {
            try
            {
                var stackTrace = new System.Diagnostics.StackTrace();
                
                for (int i = 1; i < Math.Min(stackTrace.FrameCount, 10); i++)
                {
                    var frame = stackTrace.GetFrame(i);
                    var method = frame?.GetMethod();
                    if (method == null) continue;
                    
                    var declaringType = method.DeclaringType;
                    if (declaringType == null) continue;
                    
                    var typeName = declaringType.Name;
                    var methodName = method.Name;
                    
                    // Skip system and wrapper methods
                    if (typeName.Contains("Logger") || 
                        typeName.Contains("QCLogger") ||
                        typeName.Contains("<") ||  // Skip compiler-generated
                        methodName.StartsWith("Smart") ||  // Skip SmartLog wrappers
                        methodName == "StoreSmartMessage" ||
                        methodName == "StoreSmartMessageWithContext" ||
                        methodName == "LogMessage" ||
                        methodName == "LogWithContext")
                    {
                        continue;
                    }
                    
                    // If it's SimpleBaseStrategy, try to get the actual derived class by looking deeper
                    if (typeName == "SimpleBaseStrategy")
                    {
                        continue;
                    }
                    
                    return (typeName, methodName);
                }
                
                return ("Unknown", "Unknown");
            }
            catch
            {
                return ("Unknown", "Unknown");
            }
        }

        /// <summary>
        /// Enhanced dynamic context detection inspired by Python logger
        /// Uses deeper stack inspection to find the actual calling strategy class
        /// </summary>
        private static (string className, string methodName) GetCallerContextDynamic()
        {
            try
            {
                var stackTrace = new System.Diagnostics.StackTrace(true);
                
                for (int i = 2; i < Math.Min(stackTrace.FrameCount, 15); i++)
                {
                    var frame = stackTrace.GetFrame(i);
                    var method = frame?.GetMethod();
                    if (method == null) continue;
                    
                    var declaringType = method.DeclaringType;
                    if (declaringType == null) continue;
                    
                    var typeName = declaringType.Name;
                    var methodName = method.Name;
                    
                    // Skip wrapper/system methods
                    if (IsWrapperMethod(typeName, methodName))
                        continue;
                        
                    // For templates, get the actual derived class (highest priority)
                    if (typeName.EndsWith("Template") && !typeName.Contains("<"))
                        return (typeName, methodName);
                        
                    // Skip SimpleBaseStrategy to find the actual strategy
                    if (typeName != "SimpleBaseStrategy" && 
                        !typeName.Contains("Logger") &&
                        !typeName.Contains("Algorithm") &&
                        !typeName.Contains("System"))
                    {
                        return (typeName, methodName);
                    }
                }
                
                return ("Unknown", "Unknown");
            }
            catch
            {
                return ("Unknown", "Unknown");
            }
        }

        /// <summary>
        /// Check if a method should be skipped during stack trace inspection
        /// </summary>
        private static bool IsWrapperMethod(string typeName, string methodName)
        {
            return typeName.Contains("Logger") ||
                   typeName.Contains("QCLogger") ||
                   typeName.Contains("<") ||  // Skip compiler-generated
                   methodName.StartsWith("Smart") ||  // Skip SmartLog wrappers
                   methodName == "StoreSmartMessage" ||
                   methodName == "StoreSmartMessageWithContext" ||
                   methodName == "LogMessage" ||
                   methodName == "LogWithContext" ||
                   methodName == "GetCallerContext" ||
                   methodName == "GetCallerContextDynamic" ||
                   typeName == "RuntimeMethodHandle" ||
                   typeName == "RuntimeType";
        }

        private string FormatMessage(string message, object[] args)
        {
            string formatted;
            try
            {
                // Replace placeholders with argument indices for string.Format compatibility
                if (args != null && args.Length > 0)
                {
                    var formattedMessage = message;
                    for (int i = 0; i < args.Length; i++)
                    {
                        formattedMessage = formattedMessage.Replace($"{{{args[i]?.GetType()?.Name ?? "arg"}}}", $"{{{i}}}");
                    }
                    formatted = string.Format(formattedMessage, args);
                }
                else
                {
                    formatted = message;
                }
            }
            catch
            {
                // Fallback to simple concatenation if formatting fails
                formatted = message;
                if (args != null && args.Length > 0)
                {
                    formatted += " [" + string.Join(", ", args) + "]";
                }
            }
            
            return formatted;
        }
    }
}
using System;
using System.Collections.Concurrent;
using System.Collections.Generic;
using System.Linq;
using System.Runtime.CompilerServices;
using QuantConnect;
using QuantConnect.Algorithm;
using QuantConnect.Data;
using QuantConnect.Data.UniverseSelection;
using QuantConnect.Securities;
using QuantConnect.Securities.Option;
using CoreAlgo.Architecture.Core.Models;
using CoreAlgo.Architecture.Core.Services;
using CoreAlgo.Architecture.Core.Helpers;
using CoreAlgo.Architecture.Core.Configuration;
using CoreAlgo.Architecture.QC.Helpers;

namespace CoreAlgo.Architecture.Core.Implementations
{
    /// <summary>
    /// Helper methods for SimpleBaseStrategy
    /// This partial class contains logging, configuration, option setup, and utility methods
    /// </summary>
    public partial class SimpleBaseStrategy
    {
        // ============================================================================
        // BASIC LOGGING AND UTILITY HELPERS
        // ============================================================================

        // QC Helper Methods - delegate to Algorithm instance
        protected void Log(string message) => ((dynamic)Logger).Info(message);
        protected void Error(string message) => ((dynamic)Logger).Error(message);
        protected Security AddEquity(string ticker, Resolution resolution = Resolution.Minute) => Algorithm.AddEquity(ticker, resolution);
        protected Security AddOption(string underlying, Resolution resolution = Resolution.Minute) => Algorithm.AddOption(underlying, resolution);

        protected void SetStartDate(DateTime date) => Algorithm.SetStartDate(date);
        protected void SetEndDate(DateTime date) => Algorithm.SetEndDate(date);
        protected void SetCash(decimal cash) => Algorithm.SetCash(cash);

        /// <summary>
        /// Helper method for debug logging using context pattern
        /// </summary>
        protected void Debug(string message) => ((dynamic)Logger).Debug(message);

        /// <summary>
        /// Get typed configuration parameter with QC fallback
        /// </summary>
        protected T GetConfigParameter<T>(string key, T defaultValue = default(T))
        {
            try
            {
                var stringValue = Algorithm.GetParameter(key);
                if (string.IsNullOrEmpty(stringValue))
                    return defaultValue;

                return (T)Convert.ChangeType(stringValue, typeof(T));
            }
            catch
            {
                return defaultValue;
            }
        }

        // ============================================================================
        // SMART LOGGING WITH CONTEXT TRACKING
        // ============================================================================
        
        /// <summary>
        /// Helper method for information logging with improved context tracking
        /// </summary>
        protected void SmartLog(string message, 
            [CallerMemberName] string memberName = "", 
            [CallerFilePath] string sourceFilePath = "")
        {
            // Extract actual class name from file path (QuantConnect compatible)
            var className = ExtractClassNameFromPath(sourceFilePath);
            if (string.IsNullOrEmpty(className))
                className = this.GetType().Name;
                
            ((dynamic)Logger).LogWithContext(message, className, memberName, LogLevel.Info);
        }
        
        /// <summary>
        /// Helper method for warning logging with improved context tracking
        /// </summary>
        protected void SmartWarn(string message,
            [CallerMemberName] string memberName = "",
            [CallerFilePath] string sourceFilePath = "")
        {
            var className = ExtractClassNameFromPath(sourceFilePath);
            if (string.IsNullOrEmpty(className))
                className = this.GetType().Name;
                
            ((dynamic)Logger).LogWithContext(message, className, memberName, LogLevel.Warning);
        }
        
        /// <summary>
        /// Helper method for debug logging with improved context tracking
        /// </summary>
        protected void SmartDebug(string message,
            [CallerMemberName] string memberName = "",
            [CallerFilePath] string sourceFilePath = "")
        {
            var className = ExtractClassNameFromPath(sourceFilePath);
            if (string.IsNullOrEmpty(className))
                className = this.GetType().Name;
                
            ((dynamic)Logger).LogWithContext(message, className, memberName, LogLevel.Debug);
        }


        /// <summary>
        /// Helper method for error logging with improved context tracking
        /// </summary>
        protected void SmartError(string message,
            [CallerMemberName] string memberName = "",
            [CallerFilePath] string sourceFilePath = "")
        {
            var className = ExtractClassNameFromPath(sourceFilePath);
            if (string.IsNullOrEmpty(className))
                className = this.GetType().Name;
                
            ((dynamic)Logger).LogWithContext(message, className, memberName, LogLevel.Error);
        }

        /// <summary>
        /// Extract class name from file path using QuantConnect-compatible approach
        /// </summary>
        private string ExtractClassNameFromPath(string filePath)
        {
            if (string.IsNullOrEmpty(filePath))
                return "";
                
            try
            {
                // Extract filename from path manually (QuantConnect compatible)
                var lastSlash = Math.Max(filePath.LastIndexOf('/'), filePath.LastIndexOf('\\'));
                var fileName = lastSlash >= 0 ? filePath.Substring(lastSlash + 1) : filePath;
                
                // Remove .cs extension
                if (fileName.EndsWith(".cs"))
                    fileName = fileName.Substring(0, fileName.Length - 3);
                    
                return fileName;
            }
            catch
            {
                // Fallback to runtime type name
                return this.GetType().Name;
            }
        }

        // ============================================================================
        // COMMON OPTION SETUP HELPERS (Eliminates duplication across templates)
        // ============================================================================

        /// <summary>
        /// Set up underlying and options using AssetManager for consistent asset handling
        /// </summary>
        protected (Symbol underlying, Symbol options) SetupAssetWithOptions(string symbol, Resolution resolution = Resolution.Minute)
        {
            var security = AssetManager.AddAsset(this, symbol, resolution);
            var optionsSymbol = AssetManager.AddOptionsChain(this, security, resolution);
            return (security.Symbol, optionsSymbol);
        }

        /// <summary>
        /// Set up options filter with common parameters used across strategies
        /// </summary>
        protected void SetupStandardOptionFilter(Symbol optionsSymbol, int strikeRange, int minDTE, int maxDTE, 
            bool callsOnly = false, bool putsOnly = false)
        {
            SmartLog($"[DEBUG] SETTING UP OPTION FILTER for {optionsSymbol}");
            SmartLog($"   Filter Parameters:");
            SmartLog($"   - strikeRange: {strikeRange} (±{strikeRange} strikes from ATM)");
            SmartLog($"   - minDTE: {minDTE} days");
            SmartLog($"   - maxDTE: {maxDTE} days"); 
            SmartLog($"   - callsOnly: {callsOnly}");
            SmartLog($"   - putsOnly: {putsOnly}");
            
            // Verify the options symbol exists in Securities
            if (!Algorithm.Securities.ContainsKey(optionsSymbol))
            {
                SmartLog($"[ERROR] CRITICAL: optionsSymbol {optionsSymbol} NOT found in Securities collection!");
                SmartLog($"   Available securities count: {Algorithm.Securities.Count}"); 
                SmartLog($"   Sample securities: {string.Join(", ", Algorithm.Securities.Keys.Take(5))}");
                return;
            }
            
            var option = Algorithm.Securities[optionsSymbol] as Option;
            if (option == null)
            {
                SmartLog($"[ERROR] CRITICAL: Security {optionsSymbol} is not an Option type!");
                SmartLog($"   Actual type: {Algorithm.Securities[optionsSymbol].Type}");
                SmartLog($"   Security details: {Algorithm.Securities[optionsSymbol]}");
                return;
            }
            
            SmartLog($"[SUCCESS] Option security found: {option}");
            SmartLog($"   Option type: {option.Type}");
            SmartLog($"   Option resolution: {option.Subscriptions.GetHighestResolution()}");
            try
            {
                var distinctResolutions = option.Subscriptions
                    .Select(s => s.Resolution)
                    .Distinct()
                    .OrderBy(r => r)
                    .ToList();
                SmartLog($"   All subscription resolutions: {string.Join(", ", distinctResolutions)}");
                
                // Clarify option resolution diagnostics to avoid GetHighestResolution confusion
                SmartWarn($"IMPORTANT: For canonical option universes, GetHighestResolution() and subscription lists may show 'Daily' due to the OptionUniverse subscription.");
                SmartWarn($"Intraday readiness should be determined by actual presence of contracts in slice.OptionChains during runtime, not the canonical security's subscription summary.");
            }
            catch { }
            SmartLog($"   Option exchange: {option.Exchange}");

            try
            {
                if (callsOnly)
                {
                    SmartLog($"[TARGET] Applying CALLS ONLY filter");
                    option.SetFilter(filter => filter
                        .CallsOnly()
                        .Strikes(-strikeRange, strikeRange)
                        .Expiration(minDTE, maxDTE)
                        .IncludeWeeklys());
                }
                else if (putsOnly)
                {
                    SmartLog($"[TARGET] Applying PUTS ONLY filter");
                    option.SetFilter(filter => filter
                        .PutsOnly()
                        .Strikes(-strikeRange, strikeRange)
                        .Expiration(minDTE, maxDTE)
                        .IncludeWeeklys());
                }
                else
                {
                    SmartLog($"[TARGET] Applying BOTH CALLS AND PUTS filter");
                    option.SetFilter(filter => filter
                        .Strikes(-strikeRange, strikeRange)
                        .Expiration(minDTE, maxDTE)
                        .IncludeWeeklys());
                }
                
                SmartLog($"[SUCCESS] Option filter applied successfully for {optionsSymbol}");
                SmartLog($"   Filter should include strikes: {-strikeRange} to +{strikeRange} from ATM");
                SmartLog($"   Filter should include DTE: {minDTE} to {maxDTE} days");
                SmartLog($"   Option is now ready to receive data in slice.OptionChains");
            }
            catch (Exception ex)
            {
                SmartLog($"[ERROR] EXCEPTION applying option filter: {ex.Message}");
                SmartLog($"   Exception type: {ex.GetType().Name}");
                SmartLog($"   Stack trace: {ex.StackTrace}");
            }
        }

        /// <summary>
        /// Setup underlying and options with standard filter configuration
        /// Combines asset setup and filtering into one call to reduce template code
        /// </summary>
        protected (Symbol underlying, Symbol options) SetupOptionsForSymbol(string symbol, int strikeRange = 10, 
            int minDTE = 3, int maxDTE = 30, bool callsOnly = false, bool putsOnly = false, Resolution resolution = Resolution.Minute)
        {
            var (underlying, options) = SetupAssetWithOptions(symbol, resolution);
            SetupStandardOptionFilter(options, strikeRange, minDTE, maxDTE, callsOnly, putsOnly);
            return (underlying, options);
        }

        private void OnError(Exception exception, ErrorSeverity severity, bool canContinue, string context)
        {
            ErrorOccurred?.Invoke(this, new StrategyErrorEventArgs(exception, severity, canContinue, context));
        }

        /// <summary>
        /// Override for custom pre-execution logic that should always run.
        /// </summary>
        protected virtual void OnPreExecuteAlways(Slice slice)
        {
            // Intentionally left blank. Derived strategies can override to prepare non-order state
            // such as computing time windows, refreshing mapped symbols, or resetting daily tracking.
        }

        // ============================================================================
        // UNIVERSE OPTIMIZATION HELPERS (For Large Universe Strategies)
        // ============================================================================

        /// <summary>
        /// Sets up optimized universe settings for large universe strategies.
        /// Enables async selection and configures performance optimizations.
        /// </summary>
        /// <param name="resolution">Universe data resolution</param>
        /// <param name="enableAsync">Enable asynchronous universe selection (recommended for large universes)</param>
        /// <param name="extendedHours">Enable extended market hours</param>
        protected void SetupAsyncUniverse(Resolution resolution = Resolution.Minute, 
            bool enableAsync = true, bool extendedHours = false)
        {
            UniverseOptimizer.SetupOptimizedUniverse(Algorithm, resolution, enableAsync, extendedHours);
        }

        /// <summary>
        /// Batch fetch historical data for multiple symbols efficiently.
        /// Processes symbols in chunks to avoid memory issues and timeouts.
        /// </summary>
        /// <param name="symbols">Symbols to fetch data for</param>
        /// <param name="days">Number of days of history</param>
        /// <param name="resolution">Data resolution</param>
        /// <param name="batchSize">Batch size for processing (default: 500)</param>
        /// <returns>Dictionary mapping Symbol to list of volumes</returns>
        protected Dictionary<Symbol, List<decimal>> BatchFetchHistory(IEnumerable<Symbol> symbols, 
            int days, Resolution resolution = Resolution.Daily, int batchSize = 500)
        {
            return UniverseOptimizer.BatchFetchHistory(Algorithm, symbols, days, resolution, batchSize);
        }

        /// <summary>
        /// Calculate Average Daily Volume (ADV) for multiple symbols efficiently.
        /// Uses batch processing to handle large universes.
        /// </summary>
        /// <param name="symbols">Symbols to calculate ADV for</param>
        /// <param name="days">Number of days for ADV calculation</param>
        /// <returns>Dictionary mapping Symbol to ADV</returns>
        protected Dictionary<Symbol, decimal> CalculateBatchedADV(IEnumerable<Symbol> symbols, int days = 21)
        {
            return UniverseOptimizer.CalculateBatchedADV(Algorithm, symbols, days);
        }

        /// <summary>
        /// Calculate volume shock ratios for universe symbols efficiently.
        /// Compares current intraday volume to historical average.
        /// </summary>
        /// <param name="intradayVolumes">Current intraday volume data</param>
        /// <param name="symbols">Universe symbols to process</param>
        /// <param name="advDays">Days for ADV calculation</param>
        /// <returns>Dictionary mapping Symbol to shock ratio</returns>
        protected Dictionary<Symbol, decimal> CalculateVolumeShock(ConcurrentDictionary<Symbol, long> intradayVolumes,
            IEnumerable<Symbol> symbols, int advDays = 21)
        {
            return UniverseOptimizer.CalculateVolumeShock(Algorithm, intradayVolumes, symbols, advDays);
        }

        /// <summary>
        /// Clean up removed securities from tracking structures to prevent memory leaks.
        /// Call this from OnSecuritiesChanged for universe strategies.
        /// </summary>
        /// <param name="changes">Security changes from OnSecuritiesChanged</param>
        /// <param name="trackingDictionaries">Collection of dictionaries to clean up</param>
        protected void CleanupRemovedSecurities(SecurityChanges changes, params object[] trackingDictionaries)
        {
            UniverseOptimizer.CleanupRemovedSecurities(Algorithm, changes, trackingDictionaries);
        }

        /// <summary>
        /// Process large universe data in parallel for improved performance.
        /// Useful for computationally intensive selection logic.
        /// </summary>
        /// <typeparam name="TInput">Input data type</typeparam>
        /// <typeparam name="TResult">Result data type</typeparam>
        /// <param name="data">Input data collection</param>
        /// <param name="processor">Function to process each item</param>
        /// <param name="batchSize">Batch size for processing</param>
        /// <returns>Collection of results</returns>
        protected IEnumerable<TResult> ProcessUniverseInParallel<TInput, TResult>(IEnumerable<TInput> data,
            Func<TInput, TResult> processor, int batchSize = 500)
        {
            return UniverseOptimizer.ProcessInParallel(data, processor, batchSize);
        }
    }
}
using System;
using System.Linq;
using QuantConnect;
using QuantConnect.Orders;
using CoreAlgo.Architecture.Core.Models;
using CoreAlgo.Architecture.QC.Helpers;
using CoreAlgo.Architecture.Core.Configuration;
using CoreAlgo.Architecture.Core.Helpers;

namespace CoreAlgo.Architecture.Core.Implementations
{
    /// <summary>
    /// Trade tracking and assignment handling methods for SimpleBaseStrategy
    /// This partial class contains all trade tracking functionality similar to Python Position.py
    /// </summary>
    public partial class SimpleBaseStrategy
    {
        // ============================================================================
        // TRADE TRACKING HELPERS (Simple trade data collection like Python Position.py)
        // ============================================================================

        /// <summary>
        /// Track a new working order (order submitted but not filled)
        /// Captures SecurityIdentifier for symbol reconstruction
        /// </summary>
        protected void TrackWorkingOrder(OrderTicket ticket, string strategy = "")
        {
            if (ticket?.OrderId != null)
            {
                var orderId = ticket.OrderId.ToString();
                var symbol = ticket.Symbol?.Value ?? "Unknown";
                var strategyName = !string.IsNullOrEmpty(strategy) ? strategy : Name;
                
                var trade = new TradeRecord(orderId, symbol, strategyName, ticket.Tag);
                
                // Capture SecurityIdentifier for persistence
                if (ticket.Symbol != null)
                {
                    trade.SymbolIds.Add(ticket.Symbol.ID.ToString());
                }
                
                TradeTracker.AllTrades.Add(trade);
                TradeTracker.WorkingTrades.Add(trade);
                
                Debug($"Tracking working order: {orderId} for {symbol}");
                
                // Persist to ObjectStore
                TradePersistence.SaveTrades(TradeTracker);
            }
        }
        
        /// <summary>
        /// Mark an order as filled (moved from working to open)
        /// Enhances TradeRecord with symbol info from orderEvent if available
        /// </summary>
        public void TrackOrderFilled(OrderEvent orderEvent)
        {
            if (orderEvent?.OrderId != null)
            {
                var orderId = orderEvent.OrderId.ToString();
                
                // Find or create the trade record
                var trade = TradeTracker.AllTrades.FirstOrDefault(t => t.OrderId == orderId);
                if (trade != null && orderEvent.Symbol != null)
                {
                    // Enhance with Symbol ID if not already present
                    var symbolId = orderEvent.Symbol.ID.ToString();
                    if (!trade.SymbolIds.Contains(symbolId))
                    {
                        trade.SymbolIds.Add(symbolId);
                    }
                    if (string.IsNullOrEmpty(trade.Symbol) || trade.Symbol == "Unknown")
                    {
                        trade.Symbol = orderEvent.Symbol.Value;
                    }
                }
                
                TradeTracker.MarkTradeAsOpen(orderId, orderEvent.FillPrice, (int)orderEvent.FillQuantity);
                Debug($"Order filled: {orderId} at {orderEvent.FillPrice}");
                OnOrderFilled(orderEvent);
                
                // Persist to ObjectStore on fill
                TradePersistence.SaveTrades(TradeTracker);
            }
        }
        
        /// <summary>
        /// Hook for derived strategies to handle order fill events immediately (event-time logic).
        /// Default implementation is no-op.
        /// </summary>
        /// <param name="orderEvent">The order event that was filled</param>
        protected virtual void OnOrderFilled(OrderEvent orderEvent) { }
        
        /// <summary>
        /// Hook for derived strategies to receive ALL order events (fills, cancels, partials, submitted).
        /// Called after tracking system processes the event. Use for event-time logic like swap-to-TP.
        /// Default implementation is no-op.
        /// </summary>
        /// <param name="orderEvent">Any order event from QuantConnect</param>
        public virtual void OnOrderEventRouted(OrderEvent orderEvent) { }
        
        /// <summary>
        /// Mark a position as closed with P&L
        /// </summary>
        protected void TrackPositionClosed(string orderId, decimal closePrice, decimal pnl)
        {
            TradeTracker.MarkTradeAsClosed(orderId, closePrice, pnl);
            Debug($"Position closed: {orderId}, P&L: {pnl}");
            
            TradePersistence.SaveTrades(TradeTracker);
        }
        
        /// <summary>
        /// Cancel a working order
        /// </summary>
        public void TrackOrderCancelled(string orderId)
        {
            TradeTracker.CancelWorkingTrade(orderId);
            Debug($"Order cancelled: {orderId}");
            
            TradePersistence.SaveTrades(TradeTracker);
        }

        /// <summary>
        /// Handle option assignments using QuantConnect's native assignment detection.
        /// Automatically liquidates assigned underlying shares to prevent margin crises.
        /// </summary>
        /// <param name="assignmentEvent">Assignment order event from QuantConnect</param>
        public virtual void OnAssignmentOrderEvent(OrderEvent assignmentEvent)
        {
            try
            {
                SmartLog($"Assignment event received: {assignmentEvent.Symbol} at {Algorithm.Time}");
                
                // Use our QC-native assignment handler
                AssignmentHandler.HandleAssignment(Algorithm, assignmentEvent);
                
                // Allow derived strategies to add custom assignment logic
                OnAssignmentHandled(assignmentEvent);
            }
            catch (Exception ex)
            {
                SmartError($"Error in assignment handling: {ex.Message}");
                OnError(ex, ErrorSeverity.Error, true, "Assignment handling error");
            }
        }
        
        /// <summary>
        /// Called after assignment handling is complete. Override in derived strategies for custom logic.
        /// </summary>
        /// <param name="assignmentEvent">The processed assignment event</param>
        protected virtual void OnAssignmentHandled(OrderEvent assignmentEvent)
        {
            // Default implementation - derived strategies can override
            SmartLog($"Assignment handling completed for {assignmentEvent.Symbol}");
        }
    }
}
using System;
using System.Collections.Generic;
using System.Linq;
using QuantConnect;
using QuantConnect.Algorithm;
using QuantConnect.Orders;
using QuantConnect.Securities;
using CoreAlgo.Architecture.QC.Helpers;
using CoreAlgo.Architecture.Core.Configuration;
using CoreAlgo.Architecture.Core.Services;
using CoreAlgo.Architecture.Core.Models;
using CoreAlgo.Architecture.Core.Execution;

namespace CoreAlgo.Architecture.Core.Implementations
{
    /// <summary>
    /// Trading-related methods for SimpleBaseStrategy
    /// This partial class contains all order management and smart trading functionality
    /// </summary>
    public partial class SimpleBaseStrategy
    {
        // Smart order methods that use SmartOrderManager when available and enabled
        protected OrderTicket MarketOrder(Symbol symbol, decimal quantity, string tag = "")
        {
            if (ShouldUseSmartOrderManager())
            {
                return SmartOrderManager.SmartMarketOrder(symbol, quantity, tag);
            }
            return Algorithm.MarketOrder(symbol, quantity, tag: tag);
        }
        
        protected OrderTicket LimitOrder(Symbol symbol, decimal quantity, decimal limitPrice, string tag = "")
        {
            var rounded = PriceRounding.RoundLimitPrice(Algorithm.Securities, symbol, quantity, limitPrice);
            return Algorithm.LimitOrder(symbol, quantity, rounded, tag: tag);
        }

        protected OrderTicket StopMarketOrder(Symbol symbol, decimal quantity, decimal stopPrice, string tag = "")
        {
            var rounded = PriceRounding.RoundStopPrice(Algorithm.Securities, symbol, quantity, stopPrice);
            return Algorithm.StopMarketOrder(symbol, quantity, rounded, tag: tag);
        }

        protected OrderTicket StopLimitOrder(Symbol symbol, decimal quantity, decimal stopPrice, decimal limitPrice, string tag = "")
        {
            try
            {
                var (roundedStop, roundedLimit) = PriceRounding.RoundStopLimitPrices(Algorithm.Securities, symbol, quantity, stopPrice, limitPrice);
                
                // Pre-validation: ensure relationship is valid after rounding
                if (quantity < 0) // Sell stop-limit
                {
                    if (roundedLimit >= roundedStop)
                    {
                        SmartWarn($"[STOP-LIMIT INVALID] Sell: limit {roundedLimit} >= stop {roundedStop}. Falling back to StopMarketOrder.");
                        return Algorithm.StopMarketOrder(symbol, quantity, roundedStop, tag: tag);
                    }
                }
                else // Buy stop-limit
                {
                    if (roundedLimit <= roundedStop)
                    {
                        SmartWarn($"[STOP-LIMIT INVALID] Buy: limit {roundedLimit} <= stop {roundedStop}. Falling back to StopMarketOrder.");
                        return Algorithm.StopMarketOrder(symbol, quantity, roundedStop, tag: tag);
                    }
                }
                
                return Algorithm.StopLimitOrder(symbol, quantity, roundedStop, roundedLimit, tag: tag);
            }
            catch (ArgumentException ex)
            {
                SmartError($"[STOP-LIMIT ERROR] {ex.Message}. Falling back to StopMarketOrder at {stopPrice:F4}.");
                return Algorithm.StopMarketOrder(symbol, quantity, stopPrice, tag: tag);
            }
        }

        protected OrderTicket TrailingStopOrder(Symbol symbol, decimal quantity, decimal trailingAmount, bool isDollarTrailing, string tag = "")
        {
            var rounded = PriceRounding.RoundTrailingStopPrice(Algorithm.Securities, symbol, quantity, trailingAmount, isDollarTrailing);
            return Algorithm.TrailingStopOrder(symbol, quantity, rounded, isDollarTrailing, tag: tag);
        }
        
        protected List<OrderTicket> ComboMarketOrder(List<Leg> legs, int quantity, string tag = "")
		{
			if (ShouldUseSmartOrderManager())
			{
				return SmartOrderManager.SmartComboMarketOrder(legs, quantity, tag);
			}
			return Algorithm.ComboMarketOrder(legs, quantity, tag: tag);
		}
        
        // Helper method to determine if SmartOrderManager should be used
        private bool ShouldUseSmartOrderManager()
        {
            // Always route orders through SmartOrderManager when available.
            return SmartOrderManager != null;
        }
        
        // Helper method to determine if SmartPricing should be used
        private bool ShouldUseSmartPricing()
        {
            if (SmartOrderManager == null) return false;
            
            // Check if SmartPricingMode is enabled in config
            if (Config is StrategyConfig strategyConfig)
            {
                var smartPricingMode = strategyConfig.GetParameterValue("SmartPricingMode", "Off");
                return !string.Equals(smartPricingMode?.ToString(), "Off", StringComparison.OrdinalIgnoreCase);
            }
            
            return false;
        }
        
        // Helper method to check if overlap prevention is enabled
        private bool IsOverlapPreventionEnabled()
        {
            if (Config is StrategyConfig strategyConfig)
            {
                return strategyConfig.EnableOverlapPrevention;
            }
            return false;
        }
        
        // Setup SmartPricing engine based on configuration
        private void SetupSmartPricing()
        {
            if (SmartOrderManager == null) 
            {
                ((dynamic)Logger).Warning("SetupSmartPricing called but SmartOrderManager is null");
                return;
            }
            
            var smartPricingMode = "Off";
            
            // Try to get from Config first (if available)
            if (Config is StrategyConfig strategyConfig)
            {
                smartPricingMode = strategyConfig.GetParameterValue("SmartPricingMode", "Off")?.ToString() ?? "Off";
                ((dynamic)Logger).Info($"SmartPricing mode from Config: {smartPricingMode}");
            }
            else
            {
                // Fallback to direct parameter reading if Config not yet loaded
                smartPricingMode = Algorithm.GetParameter("SmartPricingMode", "Off");
                ((dynamic)Logger).Info($"SmartPricing mode from Algorithm.GetParameter: {smartPricingMode}");
            }
            
            if (!string.Equals(smartPricingMode, "Off", StringComparison.OrdinalIgnoreCase))
            {
                try
                {
                    ((dynamic)Logger).Info($"Creating SmartPricing engine for mode: {smartPricingMode}");
                    var pricingEngine = SmartPricingEngineFactory.Create(smartPricingMode);
                    SmartOrderManager.SetPricingEngine(pricingEngine);
                    ((dynamic)Logger).Info($"SmartPricing enabled with mode: {smartPricingMode}");
                }
                catch (Exception ex)
                {
                    ((dynamic)Logger).Error($"Failed to setup SmartPricing: {ex.Message}");
                }
            }
            else
            {
                ((dynamic)Logger).Info("SmartPricing disabled (mode is Off)");
            }
        }

        /// <summary>
        /// Helper method for creating option combo orders with better error handling
        /// </summary>
        protected OrderTicket SubmitComboOrder(List<Leg> legs, string tag = "")
        {
            try
            {
                // Validate margin before placing combo order
                var underlyingSymbol = legs.FirstOrDefault()?.Symbol.Underlying.Value ?? "";
                if (!string.IsNullOrEmpty(underlyingSymbol) && 
                    Algorithm.Portfolio.MarginRemaining < 0)
                {
                    SmartWarn($"Insufficient margin for combo order on {underlyingSymbol}");
                    return null;
                }
                
                var tickets = ComboMarketOrder(legs, 1, tag: tag);
                return tickets?.FirstOrDefault();
            }
            catch (Exception ex)
            {
                Error($"Failed to submit combo order: {ex.Message}");
                return null;
            }
        }

        /// <summary>
        /// Helper method for progressive pricing (simple retry logic)
        /// </summary>
        protected OrderTicket SubmitOrderWithRetry(Symbol symbol, decimal quantity, decimal? limitPrice = null, int maxRetries = 3)
        {
            // Check margin utilization before attempting order
            // Check if approaching margin call using QC's built-in calculations
            var marginUtilization = Algorithm.Portfolio.TotalMarginUsed / Algorithm.Portfolio.TotalPortfolioValue;
            if (marginUtilization > 0.7m)
            {
                SmartWarn($"Margin utilization too high, skipping order for {symbol}");
                return null;
            }
            
            for (int i = 0; i < maxRetries; i++)
            {
                try
                {
                    var ticket = limitPrice.HasValue ? 
                        LimitOrder(symbol, quantity, limitPrice.Value) :
                        MarketOrder(symbol, quantity);
                    
                    if (ticket != null)
                        return ticket;
                }
                catch (Exception ex)
                {
                    Debug($"Order attempt {i + 1} failed: {ex.Message}");
                    if (i == maxRetries - 1)
                        throw;
                }
            }
            return null;
        }
    }
}
using System;
using System.Collections.Generic;
using System.Linq;
using System.Runtime.CompilerServices;
using QuantConnect;
using QuantConnect.Algorithm;
using QuantConnect.Data;
using QuantConnect.Orders;
using QuantConnect.Securities;
using QuantConnect.Data.UniverseSelection;
using QuantConnect.Securities.Option;
using CoreAlgo.Architecture.Core.Interfaces;
using CoreAlgo.Architecture.Core.Models;
using CoreAlgo.Architecture.Core.Configuration;
using CoreAlgo.Architecture.Core.Helpers;
using CoreAlgo.Architecture.Core.Services;
using CoreAlgo.Architecture.Core.Execution;
using CoreAlgo.Architecture.QC.Helpers;
using System.Collections.Concurrent;

namespace CoreAlgo.Architecture.Core.Implementations
{
    /// <summary>
    /// Simplified base strategy that leverages QC's strengths while adding configuration and light extensions
    /// Implements context pattern for centralized logging and algorithm access
    /// </summary>
    public abstract partial class SimpleBaseStrategy : IStrategy, IAlgorithmContext
    {
        public StrategyConfig Config { get; private set; }
        protected EntryRestrictions EntryRestrictions { get; private set; }
        protected ExitRestrictions ExitRestrictions { get; private set; }
        protected StrikeRangeCalculator StrikeRangeCalculator { get; private set; }
        private StrategyState _state = StrategyState.NotInitialized;
        private Dictionary<string, object> _parameters = new Dictionary<string, object>();

        // Context pattern implementation - provides centralized access to algorithm and logger
        public QCAlgorithm Algorithm { get; private set; }
        public object Logger { get; private set; }
        
        // SmartPricing integration
        public SmartOrderManager SmartOrderManager { get; private set; }
        
        // Position overlap management
        protected PositionOverlapManager OverlapManager { get; private set; }
        
        // Trade tracking system (like Python setupbasestructure.py arrays)
        public TradeTracker TradeTracker { get; private set; } = new TradeTracker();
        
        // Trade persistence service for ObjectStore integration
        public TradePersistenceService TradePersistence { get; private set; }

        /// <inheritdoc/>
        public abstract string Name { get; }

        /// <inheritdoc/>
        public abstract string Description { get; }

        /// <inheritdoc/>
        public virtual string Version => "1.0.0";

        /// <inheritdoc/>
        public StrategyState State 
        { 
            get => _state;
            private set
            {
                if (_state != value)
                {
                    var previousState = _state;
                    _state = value;
                    StateChanged?.Invoke(this, new StrategyStateChangedEventArgs(previousState, value));
                }
            }
        }

        /// <inheritdoc/>
        public Dictionary<string, object> Parameters => _parameters;

        /// <inheritdoc/>
        public event EventHandler<StrategyStateChangedEventArgs> StateChanged;

        /// <inheritdoc/>
        public event EventHandler<StrategyErrorEventArgs> ErrorOccurred;

        /// <inheritdoc/>
        public virtual void Initialize(QCAlgorithm algorithm)
        {
            Algorithm = algorithm;
            
            // Logger will be injected via context pattern - no initialization here
            // Logger comes from Main.cs via SetContext method
            
            // Initialize calculators
            StrikeRangeCalculator = new StrikeRangeCalculator(algorithm);
            
            // Initialize SmartOrderManager if logger is available
            if (Logger != null)
            {
                // Create algorithm context adapter
                var context = new SimpleAlgorithmContext(algorithm, Logger);
                SmartOrderManager = new SmartOrderManager(algorithm, context);
                
                // Initialize trade persistence service
                TradePersistence = new TradePersistenceService(context);
                
                // Clear positions if backtest (LiveMode protection built in)
                TradePersistence.ClearPositionsIfBacktest();
                
                // Try to load previous trade state from ObjectStore
                var loadedTracker = TradePersistence.LoadTrades();
                if (loadedTracker != null)
                {
                    TradeTracker = loadedTracker;
                    ((dynamic)Logger).Info($"[{Name}] Restored {TradeTracker.AllTrades.Count} trades from ObjectStore");
                }
                
                // Note: OverlapManager will be initialized later in OnConfigured() after Config is loaded
                
                // Set up pricing engine based on configuration
                SetupSmartPricing();
            }
            
            State = StrategyState.Initializing;
            
            OnInitialize();
            
            State = StrategyState.Ready;
        }

        /// <inheritdoc/>
        public virtual void Initialize(QCAlgorithm algorithm, Dictionary<string, object> parameters)
        {
            Algorithm = algorithm;
            _parameters = parameters ?? new Dictionary<string, object>();
            
            // Logger will be injected via context pattern - no initialization here
            // Logger comes from Main.cs via SetContext method
            
            // Initialize calculators
            StrikeRangeCalculator = new StrikeRangeCalculator(algorithm);
            
            State = StrategyState.Initializing;
            
            OnInitialize();
            
            State = StrategyState.Ready;
        }

        /// <summary>
        /// Set the context logger (injected from Main.cs)
        /// This implements the true context pattern where logger is created once in Main.cs
        /// </summary>
        public void SetContext(object logger)
        {
            Logger = logger ?? throw new ArgumentNullException(nameof(logger));
        }
        
        /// <summary>
        /// Configure the strategy with typed configuration
        /// </summary>
        public virtual void Configure<T>() where T : StrategyConfig, new()
        {
            Config = new T();
            Config.LoadFromParameters(this); // Pass context instead of separate parameters
            
            // Higher-level validation before proceeding
            var validationErrors = Config.Validate();
            if (validationErrors.Length > 0)
            {
                var errorMessage = $"Configuration validation failed:\n{string.Join("\n", validationErrors)}";
                ((dynamic)Logger).Error(errorMessage);
                throw new InvalidOperationException(errorMessage);
            }
            
            ((dynamic)Logger).Debug($"Configuration validation passed for {typeof(T).Name}");
            
            // Initialize entry/exit restrictions with the loaded config
            EntryRestrictions = new EntryRestrictions(Config, Algorithm);
            ExitRestrictions = new ExitRestrictions(Config, Algorithm);
            
            OnConfigured();
        }

        /// <summary>
        /// Called during strategy initialization - override to set up securities, indicators, etc.
        /// </summary>
        public abstract void OnInitialize();

        // Core methods moved to partial class files:
        // - Trading methods: SimpleBaseStrategy.Trading.cs
        // - Tracking methods: SimpleBaseStrategy.Tracking.cs  
        // - Helper methods: SimpleBaseStrategy.Helpers.cs
        protected SecurityPortfolioManager Portfolio => Algorithm.Portfolio;
        protected SecurityManager Securities => Algorithm.Securities;
        protected DateTime Time => Algorithm.Time;
        
        /// <summary>
        /// Called after configuration is loaded
        /// </summary>
        protected virtual void OnConfigured() 
        { 
            // Initialize position overlap manager now that Config is available
            if (Config != null && Config.EnableOverlapPrevention && SmartOrderManager != null && Logger != null)
            {
                var context = new SimpleAlgorithmContext(Algorithm, Logger);
                OverlapManager = new PositionOverlapManager(context);
                SmartOrderManager.SetOverlapManager(OverlapManager);
                
                ((dynamic)Logger).Info($"[{Name}] Position overlap prevention enabled (Mode: {Config.OverlapPreventionMode})");
            }
        }
        
        /// <summary>
        /// Ensures SmartPricing is initialized if needed (called from Main.cs after full initialization)
        /// </summary>
        public void EnsureSmartPricingInitialized()
        {
            ((dynamic)Logger).Info($"EnsureSmartPricingInitialized called. SmartOrderManager null: {SmartOrderManager == null}, Logger null: {Logger == null}, Algorithm null: {Algorithm == null}");
            
            if (SmartOrderManager == null && Logger != null && Algorithm != null)
            {
                ((dynamic)Logger).Info("Creating SmartOrderManager...");
                
                // Create algorithm context adapter
                var context = new SimpleAlgorithmContext(Algorithm, Logger);
                SmartOrderManager = new SmartOrderManager(Algorithm, context);
                
                // Set up pricing engine based on configuration
                SetupSmartPricing();
                
                ((dynamic)Logger).Info("SmartOrderManager initialized after strategy setup");
            }
            else
            {
                ((dynamic)Logger).Warning($"SmartOrderManager initialization skipped - already exists: {SmartOrderManager != null}");
            }
        }

        /// <summary>
        /// Centralized validation to determine if the strategy should execute trades.
        /// Checks common conditions like trading hours, position limits, margin utilization.
        /// Templates can override OnShouldExecuteTrade for strategy-specific validations.
        /// </summary>
        /// <param name="slice">Current market data slice</param>
        /// <param name="blockReason">Reason why trading is blocked (if returning false)</param>
        /// <returns>True if strategy should proceed with trade execution, false otherwise</returns>
        protected virtual bool ShouldExecuteTrade(Slice slice, out string blockReason)
        {
            blockReason = "";
            
            // Skip validation if config not loaded yet
            if (Config == null) return true;
            
            // 1. Trading Hours Check (if configured)
            // Use TimeSpan.Zero to disable this check
            if (Config.TradingStartTime != TimeSpan.Zero || Config.TradingEndTime != TimeSpan.Zero)
            {
                var timeOfDay = slice.Time.TimeOfDay;
                if (timeOfDay < Config.TradingStartTime || timeOfDay > Config.TradingEndTime)
                {
                    blockReason = $"Outside trading hours ({Config.TradingStartTime:hh\\:mm}-{Config.TradingEndTime:hh\\:mm})";
                    return false;
                }
            }
            
            // 2. Position Limit Check (if MaxPositions > 0)
            // Use 0 or negative values to disable this check
            if (Config.MaxPositions > 0)
            {
                var activePositions = Portfolio.Where(p => p.Value.Invested).Count();
                if (activePositions >= Config.MaxPositions)
                {
                    blockReason = $"Max positions reached ({activePositions}/{Config.MaxPositions})";
                    return false;
                }
            }
            
            // 3. Margin Utilization Check (configurable threshold)
            var marginThreshold = Config.GetParameterValue("MaxMarginUtilization", 0.7m) as decimal? ?? 0.7m;
            if (marginThreshold > 0 && Portfolio.TotalPortfolioValue > 0)
            {
                var marginUtilization = Portfolio.TotalMarginUsed / Portfolio.TotalPortfolioValue;
                if (marginUtilization > marginThreshold)
                {
                    blockReason = $"Margin utilization too high ({marginUtilization:P0} > {marginThreshold:P0})";
                    return false;
                }
            }

            // 4. Central Entry Window Check (optional)
            if (Config.UseEntryTimeWindow)
            {
                var tod = slice.Time.TimeOfDay;
                if (tod < Config.EntryWindowStart || tod > Config.EntryWindowEnd)
                {
                    blockReason = $"Outside entry window ({Config.EntryWindowStart:hh\\:mm}-{Config.EntryWindowEnd:hh\\:mm})";
                    return false;
                }
            }
            
            // 4. Available Cash Check (ensure minimum cash reserves)
            var minCashReserveRatio = Config.GetParameterValue("MinCashReserveRatio", 0.05m) as decimal? ?? 0.05m;
            if (minCashReserveRatio > 0 && Portfolio.TotalPortfolioValue > 0)
            {
                var cashRatio = Portfolio.Cash / Portfolio.TotalPortfolioValue;
                if (cashRatio < minCashReserveRatio)
                {
                    blockReason = $"Insufficient cash reserves ({cashRatio:P1} < {minCashReserveRatio:P1})";
                    return false;
                }
            }
            
            // 5. Call strategy-specific validation hook
            if (!OnShouldExecuteTrade(slice, out var customReason))
            {
                blockReason = customReason;
                return false;
            }
            
            return true;
        }
        
        /// <summary>
        /// Strategy-specific validation hook. Override in templates for custom validations
        /// like daily trade limits, loss limits, or other strategy-specific conditions.
        /// </summary>
        /// <param name="slice">Current market data slice</param>
        /// <param name="blockReason">Reason why trading should be blocked</param>
        /// <returns>True if strategy-specific conditions allow trading</returns>
        protected virtual bool OnShouldExecuteTrade(Slice slice, out string blockReason)
        {
            blockReason = "";
            return true;
        }

        /// <summary>
        /// Check exit conditions for all current positions using ExitRestrictions.
        /// This is called even when new trades are blocked to ensure proper exits.
        /// </summary>
        /// <param name="slice">Current market data slice</param>
        protected virtual void CheckExitConditions(Slice slice)
        {
            if (ExitRestrictions == null) return;
            
            // Check all invested positions for exit conditions
            var positionsToCheck = Portfolio.Where(p => p.Value.Invested).ToList();
            
            foreach (var position in positionsToCheck)
            {
                if (ExitRestrictions.ShouldExitPosition(position.Key, slice, out var reason))
                {
                    SmartLog($"[EXIT SIGNAL] {position.Key}: {reason}");
                    
                    // Call exit signal handler - templates can override for custom exit logic
                    OnExitSignal(position.Key, reason);
                }
            }
        }
        
        /// <summary>
        /// Handle exit signal for a position. Override in templates for custom exit logic.
        /// Default implementation liquidates the position immediately.
        /// </summary>
        /// <param name="symbol">Symbol to exit</param>
        /// <param name="reason">Reason for the exit</param>
        protected virtual void OnExitSignal(Symbol symbol, string reason)
        {
            try
            {
                // Default behavior - liquidate immediately
                var ticket = Algorithm.Liquidate(symbol: symbol, tag: reason);
                if (ticket != null)
                {
                    SmartLog($"[EXIT ORDER] Liquidating {symbol}: {reason}");
                }
                else
                {
                    SmartError($"[EXIT ERROR] Failed to liquidate {symbol}: {reason}");
                }
            }
            catch (Exception ex)
            {
                SmartError($"[EXIT ERROR] Exception liquidating {symbol}: {ex.Message}");
            }
        }

        /// <inheritdoc/>
        public virtual void Execute(Slice slice)
        {
            if (State == StrategyState.Ready)
                State = StrategyState.Running;

            if (State != StrategyState.Running)
            {
                return;
            }

            try
            {
                // Allow strategies to prepare required state before gating (no order placement here)
                OnPreExecuteAlways(slice);
                // Update positions using QC's native Portfolio
                // QC's Portfolio updates automatically
                
                // 1. Always check exit conditions first (even if new trades are blocked)
                CheckExitConditions(slice);
                
                // 2. Check if new trades should be executed
                if (!ShouldExecuteTrade(slice, out var blockReason))
                {
                    // Log blocking reason periodically to avoid spam
                    // Only log on the first minute of each hour to reduce noise
                    if (slice.Time.Minute == 0 && slice.Time.Second == 0)
                    {
                        SmartLog($"[TRADING BLOCKED] {blockReason}");
                    }
                    return;
                }
                
                // 3. Execute strategy-specific logic if validation passes
                OnExecute(slice);
            }
            catch (Exception ex)
            {
                ((dynamic)Logger).Error($"Error during strategy execution: {ex.Message}");
                ((dynamic)Logger).Error($"Stack trace: {ex.StackTrace}");
                OnError(ex, ErrorSeverity.Error, true, "Error during strategy execution");
                throw;
            }
        }

        /// <inheritdoc/>
        public virtual void Shutdown()
        {
            if (State == StrategyState.Shutdown || State == StrategyState.ShuttingDown)
                return;

            try
            {
                State = StrategyState.ShuttingDown;
                OnShutdown();
                State = StrategyState.Shutdown;
            }
            catch (Exception ex)
            {
                OnError(ex, ErrorSeverity.Error, false, "Error during strategy shutdown");
                State = StrategyState.Error;
                throw;
            }
        }

        /// <inheritdoc/>
        public virtual bool Validate()
        {
            try
            {
                return OnValidate();
            }
            catch (Exception ex)
            {
                OnError(ex, ErrorSeverity.Warning, true, "Error during strategy validation");
                return false;
            }
        }

        /// <inheritdoc/>
        public virtual Dictionary<string, double> GetPerformanceMetrics()
        {
            var metrics = new Dictionary<string, double>();
            
            try
            {
                // Use QC's native portfolio metrics
                metrics["TotalPortfolioValue"] = (double)Portfolio.TotalPortfolioValue;
                metrics["Cash"] = (double)Portfolio.Cash;
                metrics["TotalHoldingsValue"] = (double)Portfolio.TotalHoldingsValue;
                metrics["UnrealizedProfit"] = (double)Portfolio.TotalUnrealizedProfit;
                metrics["TotalProfit"] = (double)Portfolio.TotalProfit;
                
                // Add custom metrics from derived strategies
                OnGetPerformanceMetrics(metrics);
            }
            catch (Exception ex)
            {
                OnError(ex, ErrorSeverity.Warning, true, "Error getting performance metrics");
            }

            return metrics;
        }

        /// <inheritdoc/>
        public virtual void Reset()
        {
            if (State == StrategyState.Running)
                throw new InvalidOperationException("Cannot reset strategy while running");

            try
            {
                OnReset();
                State = StrategyState.Ready;
            }
            catch (Exception ex)
            {
                OnError(ex, ErrorSeverity.Error, false, "Error during strategy reset");
                throw;
            }
        }

        /// <summary>
        /// Strategy-specific execution logic
        /// </summary>
        protected abstract void OnExecute(Slice slice);

        /// <summary>
        /// Override for custom shutdown logic
        /// </summary>
        protected virtual void OnShutdown() 
        { 
            // Export trade tracking data to logs (like Python main.py export)
            try
            {
                SmartLog("Exporting trade tracking data...");
                TradeTracker.ExportToLogs(message => SmartLog(message));
                SmartLog(TradeTracker.GetSummary());
            }
            catch (Exception ex)
            {
                SmartError($"Failed to export trade data: {ex.Message}");
            }
        }

        /// <summary>
        /// Override for custom validation logic
        /// </summary>
        protected virtual bool OnValidate() => true;

        /// <summary>
        /// Override to add custom performance metrics
        /// </summary>
        protected virtual void OnGetPerformanceMetrics(Dictionary<string, double> metrics) { }

        /// <summary>
        /// Override for custom reset logic
        /// </summary>
        protected virtual void OnReset() { }

        /// <summary>
        /// Handle security changes from universe selection.
        /// Override in templates that use dynamic universe selection.
        /// </summary>
        /// <param name="changes">Security changes from QuantConnect</param>
        public virtual void OnSecuritiesChanged(SecurityChanges changes)
        {
            // Default implementation - do nothing
            // Strategies using universe selection can override
        }

        // Additional helper methods moved to SimpleBaseStrategy.Helpers.cs

        // Trade tracking methods moved to SimpleBaseStrategy.Tracking.cs

        // Option setup helpers moved to SimpleBaseStrategy.Helpers.cs

        // Universe optimization helpers moved to SimpleBaseStrategy.Helpers.cs
    }
}
#region imports
    using System;
    using System.Collections;
    using System.Collections.Generic;
    using System.Linq;
    using System.Globalization;
    using System.Drawing;
    using QuantConnect;
    using QuantConnect.Algorithm.Framework;
    using QuantConnect.Algorithm.Framework.Selection;
    using QuantConnect.Algorithm.Framework.Alphas;
    using QuantConnect.Algorithm.Framework.Portfolio;
    using QuantConnect.Algorithm.Framework.Portfolio.SignalExports;
    using QuantConnect.Algorithm.Framework.Execution;
    using QuantConnect.Algorithm.Framework.Risk;
    using QuantConnect.Algorithm.Selection;
    using QuantConnect.Api;
    using QuantConnect.Parameters;
    using QuantConnect.Benchmarks;
    using QuantConnect.Brokerages;
    using QuantConnect.Commands;
    using QuantConnect.Configuration;
    using QuantConnect.Util;
    using QuantConnect.Interfaces;
    using QuantConnect.Algorithm;
    using QuantConnect.Indicators;
    using QuantConnect.Data;
    using QuantConnect.Data.Auxiliary;
    using QuantConnect.Data.Consolidators;
    using QuantConnect.Data.Custom;
    using QuantConnect.Data.Custom.IconicTypes;
    using QuantConnect.DataSource;
    using QuantConnect.Data.Fundamental;
    using QuantConnect.Data.Market;
    using QuantConnect.Data.Shortable;
    using QuantConnect.Data.UniverseSelection;
    using QuantConnect.Notifications;
    using QuantConnect.Orders;
    using QuantConnect.Orders.Fees;
    using QuantConnect.Orders.Fills;
    using QuantConnect.Orders.OptionExercise;
    using QuantConnect.Orders.Slippage;
    using QuantConnect.Orders.TimeInForces;
    using QuantConnect.Python;
    using QuantConnect.Scheduling;
    using QuantConnect.Securities;
    using QuantConnect.Securities.Equity;
    using QuantConnect.Securities.Future;
    using QuantConnect.Securities.Option;
    using QuantConnect.Securities.Positions;
    using QuantConnect.Securities.Forex;
    using QuantConnect.Securities.Crypto;
    using QuantConnect.Securities.CryptoFuture;
    using QuantConnect.Securities.IndexOption;
    using QuantConnect.Securities.Interfaces;
    using QuantConnect.Securities.Volatility;
    using QuantConnect.Storage;
    using QuantConnect.Statistics;
    using QCAlgorithmFramework = QuantConnect.Algorithm.QCAlgorithm;
    using QCAlgorithmFrameworkBridge = QuantConnect.Algorithm.QCAlgorithm;
    using Calendar = QuantConnect.Data.Consolidators.Calendar;
#endregion

namespace CoreAlgo.Architecture.Core.Implementations
{
    /// <summary>
    /// Event arguments for strategy state changes
    /// </summary>
    public class StrategyStateChangedEventArgs : EventArgs
    {
        /// <summary>
        /// Gets the previous state
        /// </summary>
        public StrategyState PreviousState { get; }

        /// <summary>
        /// Gets the new state
        /// </summary>
        public StrategyState NewState { get; }

        /// <summary>
        /// Gets the timestamp of the state change
        /// </summary>
        public DateTime Timestamp { get; }

        /// <summary>
        /// Gets any additional message about the state change
        /// </summary>
        public string Message { get; }

        /// <summary>
        /// Creates a new instance of StrategyStateChangedEventArgs
        /// </summary>
        public StrategyStateChangedEventArgs(StrategyState previousState, StrategyState newState, string message = null)
        {
            PreviousState = previousState;
            NewState = newState;
            Timestamp = DateTime.UtcNow;
            Message = message;
        }
    }

    /// <summary>
    /// Event arguments for strategy errors
    /// </summary>
    public class StrategyErrorEventArgs : EventArgs
    {
        /// <summary>
        /// Gets the error that occurred
        /// </summary>
        public Exception Error { get; }

        /// <summary>
        /// Gets the error severity
        /// </summary>
        public ErrorSeverity Severity { get; }

        /// <summary>
        /// Gets the timestamp of the error
        /// </summary>
        public DateTime Timestamp { get; }

        /// <summary>
        /// Gets whether the strategy can continue
        /// </summary>
        public bool CanContinue { get; }

        /// <summary>
        /// Gets additional context about the error
        /// </summary>
        public string Context { get; }

        /// <summary>
        /// Creates a new instance of StrategyErrorEventArgs
        /// </summary>
        public StrategyErrorEventArgs(Exception error, ErrorSeverity severity, bool canContinue, string context = null)
        {
            Error = error;
            Severity = severity;
            CanContinue = canContinue;
            Context = context;
            Timestamp = DateTime.UtcNow;
        }
    }

    /// <summary>
    /// Error severity levels
    /// </summary>
    public enum ErrorSeverity
    {
        /// <summary>
        /// Informational message
        /// </summary>
        Info,

        /// <summary>
        /// Warning that doesn't affect execution
        /// </summary>
        Warning,

        /// <summary>
        /// Error that may affect execution
        /// </summary>
        Error,

        /// <summary>
        /// Critical error that stops execution
        /// </summary>
        Critical
    }
}
#region imports
    using System;
    using System.Collections;
    using System.Collections.Generic;
    using System.Linq;
    using System.Globalization;
    using System.Drawing;
    using QuantConnect;
    using QuantConnect.Algorithm.Framework;
    using QuantConnect.Algorithm.Framework.Selection;
    using QuantConnect.Algorithm.Framework.Alphas;
    using QuantConnect.Algorithm.Framework.Portfolio;
    using QuantConnect.Algorithm.Framework.Portfolio.SignalExports;
    using QuantConnect.Algorithm.Framework.Execution;
    using QuantConnect.Algorithm.Framework.Risk;
    using QuantConnect.Algorithm.Selection;
    using QuantConnect.Api;
    using QuantConnect.Parameters;
    using QuantConnect.Benchmarks;
    using QuantConnect.Brokerages;
    using QuantConnect.Commands;
    using QuantConnect.Configuration;
    using QuantConnect.Util;
    using QuantConnect.Interfaces;
    using QuantConnect.Algorithm;
    using QuantConnect.Indicators;
    using QuantConnect.Data;
    using QuantConnect.Data.Auxiliary;
    using QuantConnect.Data.Consolidators;
    using QuantConnect.Data.Custom;
    using QuantConnect.Data.Custom.IconicTypes;
    using QuantConnect.DataSource;
    using QuantConnect.Data.Fundamental;
    using QuantConnect.Data.Market;
    using QuantConnect.Data.Shortable;
    using QuantConnect.Data.UniverseSelection;
    using QuantConnect.Notifications;
    using QuantConnect.Orders;
    using QuantConnect.Orders.Fees;
    using QuantConnect.Orders.Fills;
    using QuantConnect.Orders.OptionExercise;
    using QuantConnect.Orders.Slippage;
    using QuantConnect.Orders.TimeInForces;
    using QuantConnect.Python;
    using QuantConnect.Scheduling;
    using QuantConnect.Securities;
    using QuantConnect.Securities.Equity;
    using QuantConnect.Securities.Future;
    using QuantConnect.Securities.Option;
    using QuantConnect.Securities.Positions;
    using QuantConnect.Securities.Forex;
    using QuantConnect.Securities.Crypto;
    using QuantConnect.Securities.CryptoFuture;
    using QuantConnect.Securities.IndexOption;
    using QuantConnect.Securities.Interfaces;
    using QuantConnect.Securities.Volatility;
    using QuantConnect.Storage;
    using QuantConnect.Statistics;
    using QCAlgorithmFramework = QuantConnect.Algorithm.QCAlgorithm;
    using QCAlgorithmFrameworkBridge = QuantConnect.Algorithm.QCAlgorithm;
    using Calendar = QuantConnect.Data.Consolidators.Calendar;
#endregion
namespace CoreAlgo.Architecture.Core.Implementations
{
    /// <summary>
    /// Represents the state of a strategy
    /// </summary>
    public enum StrategyState
    {
        /// <summary>
        /// Strategy has not been initialized
        /// </summary>
        NotInitialized,

        /// <summary>
        /// Strategy is being initialized
        /// </summary>
        Initializing,

        /// <summary>
        /// Strategy is initialized and ready
        /// </summary>
        Ready,

        /// <summary>
        /// Strategy is actively running
        /// </summary>
        Running,

        /// <summary>
        /// Strategy is paused
        /// </summary>
        Paused,

        /// <summary>
        /// Strategy is being shut down
        /// </summary>
        ShuttingDown,

        /// <summary>
        /// Strategy has been shut down
        /// </summary>
        Shutdown,

        /// <summary>
        /// Strategy is in error state
        /// </summary>
        Error,

        /// <summary>
        /// Strategy is in maintenance mode
        /// </summary>
        Maintenance
    }
}
using System;
using System.Collections.Generic;
using System.Linq;
using QuantConnect;
using QuantConnect.Securities;
using CoreAlgo.Architecture.Core.Interfaces;

namespace CoreAlgo.Architecture.Core.Implementations
{
    /// <summary>
    /// Prevents overlapping strike prices that could create margin model conflicts
    /// Especially important for Iron Condors and complex multi-leg strategies
    /// </summary>
    public class StrikeOverlapRule : IPositionOverlapRule
    {
        private readonly IAlgorithmContext _context;
        private readonly object _logger;
        private readonly decimal _minimumStrikeDistance = 5m; // Minimum distance between strikes

        public string RuleName => "StrikeOverlapRule";
        public string Description => "Prevents overlapping strikes that cause margin conflicts";
        public bool IsEnabled { get; set; } = true;

        public StrikeOverlapRule(IAlgorithmContext context, decimal minimumStrikeDistance = 5m)
        {
            _context = context;
            _logger = context.Logger;
            _minimumStrikeDistance = minimumStrikeDistance;
        }

        public ValidationResult Validate(
            Symbol proposedSymbol, 
            decimal quantity, 
            IEnumerable<KeyValuePair<Symbol, SecurityHolding>> existingPositions,
            string strategyTag = "")
        {
            if (!IsEnabled) 
                return ValidationResult.Success();

            try
            {
                // Only validate option positions
                if (proposedSymbol.SecurityType != SecurityType.Option)
                    return ValidationResult.Success();

                var underlying = proposedSymbol.Underlying;
                var proposedStrike = proposedSymbol.ID.StrikePrice;
                var proposedExpiry = proposedSymbol.ID.Date;
                var proposedRight = proposedSymbol.ID.OptionRight;

                // Get existing option positions on same underlying
                var existingOptions = existingPositions
                    .Where(p => p.Value.Invested &&
                               p.Key.SecurityType == SecurityType.Option &&
                               p.Key.Underlying == underlying)
                    .ToList();

                if (!existingOptions.Any())
                    return ValidationResult.Success();

                // Only check for exact duplicates - allow all other combinations
                return ValidateExactDuplicates(
                    proposedStrike, 
                    proposedExpiry,
                    proposedRight,
                    quantity,
                    existingOptions);
            }
            catch (System.Exception ex)
            {
                ((dynamic)_logger).Error($"[{RuleName}] Error validating strike overlaps: {ex.Message}");
                return ValidationResult.Error($"Strike validation error: {ex.Message}");
            }
        }

        /// <summary>
        /// Validates for exact duplicate positions only
        /// </summary>
        private ValidationResult ValidateExactDuplicates(
            decimal proposedStrike,
            System.DateTime proposedExpiry,
            OptionRight proposedRight,
            decimal quantity,
            List<KeyValuePair<Symbol, SecurityHolding>> existingOptions)
        {
            // Check for exact strike/expiry/right matches
            var exactMatch = existingOptions.FirstOrDefault(p => 
                p.Key.ID.StrikePrice == proposedStrike &&
                p.Key.ID.Date == proposedExpiry &&
                p.Key.ID.OptionRight == proposedRight);

            if (exactMatch.Key != null)
            {
                // Allow position modifications (different quantities)
                if (IsPositionModification(quantity, exactMatch.Value.Quantity))
                {
                    ((dynamic)_logger).Debug($"[{RuleName}] Allowing position modification for {proposedStrike} {proposedRight} {proposedExpiry:yyyy-MM-dd}");
                    return ValidationResult.Success();
                }

                // Block exact duplicates (same quantity, same position)
                if (Math.Sign(quantity) == Math.Sign(exactMatch.Value.Quantity))
                {
                    return ValidationResult.Blocked(
                        $"Exact duplicate position: {proposedStrike} {proposedRight} {proposedExpiry:yyyy-MM-dd} " +
                        $"already exists with quantity {exactMatch.Value.Quantity}");
                }
            }

            return ValidationResult.Success();
        }


        /// <summary>
        /// Determines if this is a position modification rather than a new position
        /// </summary>
        private bool IsPositionModification(decimal proposedQuantity, decimal existingQuantity)
        {
            // Allow all position modifications:
            // - Same sign = position increase (e.g., double the position)
            // - Different sign = position reduction/close (e.g., sell 50%)
            // - Different quantity = any modification is allowed
            return Math.Abs(proposedQuantity) != Math.Abs(existingQuantity);
        }
    }
}
using System;
using System.Collections.Generic;
using System.Globalization;

namespace CoreAlgo.Architecture.Core.Implementations
{
    /// <summary>
    /// Simple trade record for tracking individual trades through their lifecycle
    /// Supports both single-leg and multi-leg (combo) orders
    /// </summary>
    public class TradeRecord
    {
        public string OrderId { get; set; } = string.Empty;
        public string Symbol { get; set; } = string.Empty;
        public string Strategy { get; set; } = string.Empty;
        public DateTime OpenTime { get; set; }
        public DateTime? CloseTime { get; set; }
        public decimal OpenPrice { get; set; }
        public decimal? ClosePrice { get; set; }
        public int Quantity { get; set; }
        public string Status { get; set; } = "Working"; // Working, Open, Closed, Cancelled, PartialFill
        public decimal PnL { get; set; }
        public string OrderTag { get; set; } = string.Empty;
        
        /// <summary>
        /// List of order IDs for multi-leg trades (combo orders)
        /// Single-leg trades will have just one entry
        /// </summary>
        public List<int> OrderIds { get; set; } = new List<int>();
        
        /// <summary>
        /// List of SecurityIdentifier strings for all symbols in this trade
        /// QC Symbol.ID.ToString() provides a stable, serializable identifier
        /// Can be used to reconstruct Symbol via SecurityIdentifier.Parse()
        /// </summary>
        public List<string> SymbolIds { get; set; } = new List<string>();
        
        /// <summary>
        /// Filled quantity (for partial fills tracking)
        /// </summary>
        public int FilledQuantity { get; set; }
        
        /// <summary>
        /// Last update timestamp
        /// </summary>
        public DateTime LastUpdateUtc { get; set; }
        
        /// <summary>
        /// Strategy-specific details bag for dynamic tracking
        /// Allows any strategy to store custom data for later use (adjustments, analysis, etc.)
        /// </summary>
        public Dictionary<string, string> StrategyDetails { get; set; } = new Dictionary<string, string>();
        
        /// <summary>
        /// Creates a new trade record
        /// </summary>
        public TradeRecord(string orderId, string symbol, string strategy = "", string orderTag = "")
        {
            OrderId = orderId;
            Symbol = symbol;
            Strategy = strategy;
            OrderTag = orderTag;
            OpenTime = DateTime.UtcNow;
            LastUpdateUtc = DateTime.UtcNow;
            
            // Parse order ID and add to list
            if (int.TryParse(orderId, out var parsedOrderId))
            {
                OrderIds.Add(parsedOrderId);
            }
        }
        
        /// <summary>
        /// Parameterless constructor for serialization
        /// </summary>
        public TradeRecord()
        {
            OpenTime = DateTime.UtcNow;
            LastUpdateUtc = DateTime.UtcNow;
        }
        
        /// <summary>
        /// Marks trade as open with fill details
        /// </summary>
        public void MarkAsOpen(decimal fillPrice, int fillQuantity)
        {
            Status = "Open";
            OpenPrice = fillPrice;
            Quantity = fillQuantity;
            FilledQuantity = fillQuantity;
            LastUpdateUtc = DateTime.UtcNow;
        }
        
        /// <summary>
        /// Updates partial fill status
        /// </summary>
        public void MarkAsPartialFill(int filledQuantity, decimal averagePrice)
        {
            Status = "PartialFill";
            FilledQuantity = filledQuantity;
            OpenPrice = averagePrice;
            LastUpdateUtc = DateTime.UtcNow;
        }
        
        /// <summary>
        /// Marks trade as closed with close details
        /// </summary>
        public void MarkAsClosed(decimal closePrice, decimal pnl)
        {
            Status = "Closed";
            CloseTime = DateTime.UtcNow;
            ClosePrice = closePrice;
            PnL = pnl;
            LastUpdateUtc = DateTime.UtcNow;
        }
        
        /// <summary>
        /// Marks trade as cancelled
        /// </summary>
        public void MarkAsCancelled()
        {
            Status = "Cancelled";
            LastUpdateUtc = DateTime.UtcNow;
        }
        
        /// <summary>
        /// Set a string detail
        /// </summary>
        public void SetDetail(string key, string value)
        {
            StrategyDetails[key] = value;
            LastUpdateUtc = DateTime.UtcNow;
        }
        
        /// <summary>
        /// Set a decimal detail
        /// </summary>
        public void SetDetail(string key, decimal value)
        {
            StrategyDetails[key] = value.ToString(CultureInfo.InvariantCulture);
            LastUpdateUtc = DateTime.UtcNow;
        }
        
        /// <summary>
        /// Set an int detail
        /// </summary>
        public void SetDetail(string key, int value)
        {
            StrategyDetails[key] = value.ToString(CultureInfo.InvariantCulture);
            LastUpdateUtc = DateTime.UtcNow;
        }
        
        /// <summary>
        /// Set a bool detail
        /// </summary>
        public void SetDetail(string key, bool value)
        {
            StrategyDetails[key] = value ? "true" : "false";
            LastUpdateUtc = DateTime.UtcNow;
        }
        
        /// <summary>
        /// Set a DateTime detail (stored in UTC)
        /// </summary>
        public void SetDetail(string key, DateTime valueUtc)
        {
            StrategyDetails[key] = valueUtc.ToUniversalTime().ToString("o", CultureInfo.InvariantCulture);
            LastUpdateUtc = DateTime.UtcNow;
        }
        
        /// <summary>
        /// Try to get a string detail
        /// </summary>
        public bool TryGetDetail(string key, out string value) => StrategyDetails.TryGetValue(key, out value);
        
        /// <summary>
        /// Try to get a decimal detail
        /// </summary>
        public bool TryGetDecimal(string key, out decimal value)
        {
            value = 0m;
            return StrategyDetails.TryGetValue(key, out var s)
                && decimal.TryParse(s, NumberStyles.Number, CultureInfo.InvariantCulture, out value);
        }
        
        /// <summary>
        /// Try to get an int detail
        /// </summary>
        public bool TryGetInt(string key, out int value)
        {
            value = 0;
            return StrategyDetails.TryGetValue(key, out var s)
                && int.TryParse(s, NumberStyles.Integer, CultureInfo.InvariantCulture, out value);
        }
        
        /// <summary>
        /// Try to get a bool detail
        /// </summary>
        public bool TryGetBool(string key, out bool value)
        {
            value = false;
            return StrategyDetails.TryGetValue(key, out var s)
                && bool.TryParse(s, out value);
        }
        
        /// <summary>
        /// Try to get a DateTime detail (returns UTC)
        /// </summary>
        public bool TryGetDateTime(string key, out DateTime valueUtc)
        {
            valueUtc = default;
            return StrategyDetails.TryGetValue(key, out var s)
                && DateTime.TryParse(s, CultureInfo.InvariantCulture, DateTimeStyles.AdjustToUniversal | DateTimeStyles.AssumeUniversal, out valueUtc);
        }
        
        /// <summary>
        /// Returns CSV header
        /// </summary>
        public static string CsvHeader()
        {
            return "OrderId,Symbol,Strategy,OrderTag,Status,OpenTime,CloseTime,OpenPrice,ClosePrice,Quantity,PnL";
        }
        
        /// <summary>
        /// Returns trade data as CSV row
        /// </summary>
        public string ToCsv()
        {
            return $"{OrderId},{Symbol},{Strategy},{OrderTag},{Status},{OpenTime:yyyy-MM-dd HH:mm:ss}," +
                   $"{CloseTime?.ToString("yyyy-MM-dd HH:mm:ss") ?? ""}," +
                   $"{OpenPrice},{ClosePrice?.ToString() ?? ""},{Quantity},{PnL}";
        }
    }
}
using System;
using System.Collections.Generic;
using System.Linq;

namespace CoreAlgo.Architecture.Core.Implementations
{
    /// <summary>
    /// Trade tracking system that maintains lists of trades in different states
    /// Similar to the Python setupbasestructure.py arrays for position management
    /// Supports both single-leg and multi-leg (combo) orders with ObjectStore persistence
    /// </summary>
    public class TradeTracker
    {
        /// <summary>
        /// All trades ever created (complete history)
        /// </summary>
        public List<TradeRecord> AllTrades { get; set; } = new List<TradeRecord>();
        
        /// <summary>
        /// Currently open trades (filled and active)
        /// </summary>
        public List<TradeRecord> OpenTrades { get; set; } = new List<TradeRecord>();
        
        /// <summary>
        /// Working trades (submitted but not yet filled)
        /// </summary>
        public List<TradeRecord> WorkingTrades { get; set; } = new List<TradeRecord>();
        
        /// <summary>
        /// Closed trades (completed positions)
        /// </summary>
        public List<TradeRecord> ClosedTrades { get; set; } = new List<TradeRecord>();
        
        /// <summary>
        /// Adds a new working trade
        /// </summary>
        public void AddWorkingTrade(string orderId, string symbol, string strategy = "", string orderTag = "")
        {
            var trade = new TradeRecord(orderId, symbol, strategy, orderTag);
            AllTrades.Add(trade);
            WorkingTrades.Add(trade);
        }
        
        /// <summary>
        /// Moves a trade from working to open when it gets filled
        /// If trade doesn't exist in Working (race condition), looks in AllTrades or creates new one
        /// </summary>
        public void MarkTradeAsOpen(string orderId, decimal fillPrice, int fillQuantity)
        {
            var trade = WorkingTrades.FirstOrDefault(t => t.OrderId == orderId);
            if (trade != null)
            {
                trade.MarkAsOpen(fillPrice, fillQuantity);
                WorkingTrades.Remove(trade);
                OpenTrades.Add(trade);
                return;
            }
            
            // Fallback: Check if trade exists in AllTrades but not in WorkingTrades (already moved)
            trade = AllTrades.FirstOrDefault(t => t.OrderId == orderId);
            if (trade != null && trade.Status != "Open")
            {
                trade.MarkAsOpen(fillPrice, fillQuantity);
                if (!OpenTrades.Contains(trade))
                {
                    OpenTrades.Add(trade);
                }
                return;
            }
            
            // Last resort: Create trade on-the-fly (handles race condition where fill arrives before tracking)
            if (trade == null)
            {
                trade = new TradeRecord(orderId, "Unknown", "Unknown", "")
                {
                    Quantity = fillQuantity,
                    FilledQuantity = fillQuantity,
                    OpenPrice = fillPrice,
                    Status = "Open"
                };
                AllTrades.Add(trade);
                OpenTrades.Add(trade);
            }
        }
        
        /// <summary>
        /// Moves a trade from open to closed when position is closed
        /// </summary>
        public void MarkTradeAsClosed(string orderId, decimal closePrice, decimal pnl)
        {
            var trade = OpenTrades.FirstOrDefault(t => t.OrderId == orderId);
            if (trade != null)
            {
                trade.MarkAsClosed(closePrice, pnl);
                OpenTrades.Remove(trade);
                ClosedTrades.Add(trade);
            }
        }
        
        /// <summary>
        /// Removes a working trade (cancelled order)
        /// </summary>
        public void CancelWorkingTrade(string orderId)
        {
            var trade = WorkingTrades.FirstOrDefault(t => t.OrderId == orderId);
            if (trade != null)
            {
                WorkingTrades.Remove(trade);
                // Keep in AllTrades for audit trail but mark as cancelled
                trade.Status = "Cancelled";
            }
        }
        
        /// <summary>
        /// Exports all trades to QC logs (CSV format in logs instead of file)
        /// QuantConnect doesn't allow file operations, so we use logging instead
        /// </summary>
        public void ExportToLogs(Action<string> logAction)
        {
            try
            {
                // Log header
                logAction?.Invoke($"=== TRADE EXPORT === {TradeRecord.CsvHeader()}");
                
                // Log all trades
                foreach (var trade in AllTrades.OrderBy(t => t.OpenTime))
                {
                    logAction?.Invoke($"TRADE_DATA: {trade.ToCsv()}");
                }
                
                logAction?.Invoke("=== END TRADE EXPORT ===");
            }
            catch (Exception ex)
            {
                // Fail silently - don't break algorithm execution
                logAction?.Invoke($"Failed to export trades to logs: {ex.Message}");
            }
        }
        
        /// <summary>
        /// Gets summary statistics for logging
        /// </summary>
        public string GetSummary()
        {
            var totalTrades = AllTrades.Count;
            var workingCount = WorkingTrades.Count;
            var openCount = OpenTrades.Count;
            var closedCount = ClosedTrades.Count;
            var totalPnL = ClosedTrades.Sum(t => t.PnL);
            
            return $"Trade Summary: Total={totalTrades}, Working={workingCount}, Open={openCount}, Closed={closedCount}, PnL=${totalPnL:F2}";
        }
        
        /// <summary>
        /// Clears all trade data (for testing)
        /// </summary>
        public void Clear()
        {
            AllTrades.Clear();
            WorkingTrades.Clear();
            OpenTrades.Clear();
            ClosedTrades.Clear();
        }
    }
}
using System;
using System.Collections.Generic;
using System.Linq;
using QuantConnect;
using QuantConnect.Securities;
using CoreAlgo.Architecture.Core.Interfaces;

namespace CoreAlgo.Architecture.Core.Implementations
{
    /// <summary>
    /// Prevents cross-strategy conflicts on the same underlying asset
    /// Example: Blocks Iron Condor + Covered Call on SPY simultaneously
    /// </summary>
    public class UnderlyingConflictRule : IPositionOverlapRule
    {
        private readonly IAlgorithmContext _context;
        private readonly object _logger;

        public string RuleName => "UnderlyingConflictRule";
        public string Description => "Prevents multiple complex strategies on same underlying";
        public bool IsEnabled { get; set; } = true;

        public UnderlyingConflictRule(IAlgorithmContext context)
        {
            _context = context;
            _logger = context.Logger;
        }

        public ValidationResult Validate(
            Symbol proposedSymbol, 
            decimal quantity, 
            IEnumerable<KeyValuePair<Symbol, SecurityHolding>> existingPositions,
            string strategyTag = "")
        {
            if (!IsEnabled) 
                return ValidationResult.Success();

            try
            {
                // Determine the underlying asset for the proposed position
                var proposedUnderlying = GetUnderlying(proposedSymbol);
                if (proposedUnderlying == null)
                    return ValidationResult.Success();

                // Check for existing positions on the same underlying
                var conflictingPositions = existingPositions
                    .Where(p => p.Value.Invested)
                    .Where(p => GetUnderlying(p.Key) == proposedUnderlying)
                    .ToList();

                if (!conflictingPositions.Any())
                    return ValidationResult.Success();

                // Count different types of positions on this underlying
                var optionPositions = conflictingPositions.Count(p => p.Key.SecurityType == SecurityType.Option);
                var stockPositions = conflictingPositions.Count(p => p.Key.SecurityType == SecurityType.Equity);

                // Only check for truly conflicting positions (e.g., opposite directions on same asset)
                // Allow multiple strategies on same underlying - they can complement each other
                
                // For now, allow all combinations - let individual strategies manage their own limits
                // This rule focuses on preventing true conflicts, not limiting strategy diversity
                
                ((dynamic)_logger).Debug($"[{RuleName}] Allowed: {proposedUnderlying.Value} has {optionPositions} options, {stockPositions} stock positions");
                return ValidationResult.Success();
            }
            catch (System.Exception ex)
            {
                ((dynamic)_logger).Error($"[{RuleName}] Error validating underlying conflict: {ex.Message}");
                return ValidationResult.Error($"Validation error: {ex.Message}");
            }
        }

        /// <summary>
        /// Gets the underlying symbol for any security type
        /// </summary>
        private Symbol GetUnderlying(Symbol symbol)
        {
            if (symbol.SecurityType == SecurityType.Option)
                return symbol.Underlying;
            if (symbol.SecurityType == SecurityType.Equity)
                return symbol;
            
            return null; // Unsupported security type
        }

    }
}
#region imports
    using System;
    using System.Collections;
    using System.Collections.Generic;
    using System.Linq;
    using System.Globalization;
    using System.Drawing;
    using QuantConnect;
    using QuantConnect.Algorithm.Framework;
    using QuantConnect.Algorithm.Framework.Selection;
    using QuantConnect.Algorithm.Framework.Alphas;
    using QuantConnect.Algorithm.Framework.Portfolio;
    using QuantConnect.Algorithm.Framework.Portfolio.SignalExports;
    using QuantConnect.Algorithm.Framework.Execution;
    using QuantConnect.Algorithm.Framework.Risk;
    using QuantConnect.Algorithm.Selection;
    using QuantConnect.Api;
    using QuantConnect.Parameters;
    using QuantConnect.Benchmarks;
    using QuantConnect.Brokerages;
    using QuantConnect.Commands;
    using QuantConnect.Configuration;
    using QuantConnect.Util;
    using QuantConnect.Interfaces;
    using QuantConnect.Algorithm;
    using QuantConnect.Indicators;
    using QuantConnect.Data;
    using QuantConnect.Data.Auxiliary;
    using QuantConnect.Data.Consolidators;
    using QuantConnect.Data.Custom;
    using QuantConnect.Data.Custom.IconicTypes;
    using QuantConnect.DataSource;
    using QuantConnect.Data.Fundamental;
    using QuantConnect.Data.Market;
    using QuantConnect.Data.Shortable;
    using QuantConnect.Data.UniverseSelection;
    using QuantConnect.Notifications;
    using QuantConnect.Orders;
    using QuantConnect.Orders.Fees;
    using QuantConnect.Orders.Fills;
    using QuantConnect.Orders.OptionExercise;
    using QuantConnect.Orders.Slippage;
    using QuantConnect.Orders.TimeInForces;
    using QuantConnect.Python;
    using QuantConnect.Scheduling;
    using QuantConnect.Securities;
    using QuantConnect.Securities.Equity;
    using QuantConnect.Securities.Future;
    using QuantConnect.Securities.Option;
    using QuantConnect.Securities.Positions;
    using QuantConnect.Securities.Forex;
    using QuantConnect.Securities.Crypto;
    using QuantConnect.Securities.CryptoFuture;
    using QuantConnect.Securities.IndexOption;
    using QuantConnect.Securities.Interfaces;
    using QuantConnect.Securities.Volatility;
    using QuantConnect.Storage;
    using QuantConnect.Statistics;
    using QCAlgorithmFramework = QuantConnect.Algorithm.QCAlgorithm;
    using QCAlgorithmFrameworkBridge = QuantConnect.Algorithm.QCAlgorithm;
    using Calendar = QuantConnect.Data.Consolidators.Calendar;
#endregion

namespace CoreAlgo.Architecture.Core.Implementations
{
    /// <summary>
    /// Represents the result of a validation operation
    /// </summary>
    public class ValidationResult
    {
        /// <summary>
        /// Gets whether the validation passed
        /// </summary>
        public bool IsValid { get; private set; }

        /// <summary>
        /// Gets the validation errors
        /// </summary>
        public List<ValidationError> Errors { get; private set; }

        /// <summary>
        /// Gets the validation warnings
        /// </summary>
        public List<ValidationWarning> Warnings { get; private set; }

        /// <summary>
        /// Gets the first error or warning message
        /// </summary>
        public string Message
        {
            get
            {
                if (Errors?.Any() == true)
                    return Errors.First().Message;
                if (Warnings?.Any() == true)
                    return Warnings.First().Message;
                return string.Empty;
            }
        }

        /// <summary>
        /// Creates a successful validation result
        /// </summary>
        public static ValidationResult Success()
        {
            return new ValidationResult
            {
                IsValid = true,
                Errors = new List<ValidationError>(),
                Warnings = new List<ValidationWarning>()
            };
        }

        /// <summary>
        /// Creates a failed validation result with errors
        /// </summary>
        public static ValidationResult Failure(params ValidationError[] errors)
        {
            return new ValidationResult
            {
                IsValid = false,
                Errors = new List<ValidationError>(errors),
                Warnings = new List<ValidationWarning>()
            };
        }

        /// <summary>
        /// Creates a blocked validation result with message
        /// </summary>
        public static ValidationResult Blocked(string message)
        {
            var result = new ValidationResult
            {
                IsValid = false,
                Errors = new List<ValidationError>(),
                Warnings = new List<ValidationWarning>()
            };
            result.AddError("BLOCKED", message);
            return result;
        }

        /// <summary>
        /// Creates an error validation result with message
        /// </summary>
        public static ValidationResult Error(string message)
        {
            var result = new ValidationResult
            {
                IsValid = false,
                Errors = new List<ValidationError>(),
                Warnings = new List<ValidationWarning>()
            };
            result.AddError("ERROR", message);
            return result;
        }

        /// <summary>
        /// Creates a warning validation result with message
        /// </summary>
        public static ValidationResult Warning(string message)
        {
            var result = new ValidationResult
            {
                IsValid = true,
                Errors = new List<ValidationError>(),
                Warnings = new List<ValidationWarning>()
            };
            result.AddWarning("WARNING", message);
            return result;
        }

        /// <summary>
        /// Adds an error to the validation result
        /// </summary>
        public void AddError(string code, string message, string field = null)
        {
            IsValid = false;
            Errors.Add(new ValidationError { Code = code, Message = message, Field = field });
        }

        /// <summary>
        /// Adds a warning to the validation result
        /// </summary>
        public void AddWarning(string code, string message, string field = null)
        {
            Warnings.Add(new ValidationWarning { Code = code, Message = message, Field = field });
        }

        /// <summary>
        /// Merges another validation result into this one
        /// </summary>
        public void Merge(ValidationResult other)
        {
            if (!other.IsValid)
            {
                IsValid = false;
            }
            Errors.AddRange(other.Errors);
            Warnings.AddRange(other.Warnings);
        }

        /// <summary>
        /// Creates a new instance of ValidationResult
        /// </summary>
        public ValidationResult()
        {
            Errors = new List<ValidationError>();
            Warnings = new List<ValidationWarning>();
            IsValid = true;
        }
    }

    /// <summary>
    /// Represents a validation error
    /// </summary>
    public class ValidationError
    {
        /// <summary>
        /// Gets or sets the error code
        /// </summary>
        public string Code { get; set; }

        /// <summary>
        /// Gets or sets the error message
        /// </summary>
        public string Message { get; set; }

        /// <summary>
        /// Gets or sets the field that caused the error
        /// </summary>
        public string Field { get; set; }
    }

    /// <summary>
    /// Represents a validation warning
    /// </summary>
    public class ValidationWarning
    {
        /// <summary>
        /// Gets or sets the warning code
        /// </summary>
        public string Code { get; set; }

        /// <summary>
        /// Gets or sets the warning message
        /// </summary>
        public string Message { get; set; }

        /// <summary>
        /// Gets or sets the field that caused the warning
        /// </summary>
        public string Field { get; set; }
    }
}
using QuantConnect.Algorithm;

namespace CoreAlgo.Architecture.Core.Interfaces
{
    /// <summary>
    /// Context interface that provides access to algorithm instance and centralized logger
    /// Implements the context pattern like CentralAlgorithm where classes access context.logger
    /// </summary>
    public interface IAlgorithmContext
    {
        /// <summary>
        /// The QuantConnect algorithm instance - provides access to all QC functionality
        /// </summary>
        QCAlgorithm Algorithm { get; }
        
        /// <summary>
        /// Centralized smart logger with level checking and advanced features
        /// Access via context.Logger.Debug(), context.Logger.Info(), etc.
        /// Uses object type to avoid circular dependency with Main.cs CoreAlgo class
        /// </summary>
        object Logger { get; }
    }
}
#region imports
    using System;
    using System.Collections;
    using System.Collections.Generic;
    using System.Linq;
    using System.Globalization;
    using System.Drawing;
    using QuantConnect;
    using QuantConnect.Algorithm.Framework;
    using QuantConnect.Algorithm.Framework.Selection;
    using QuantConnect.Algorithm.Framework.Alphas;
    using QuantConnect.Algorithm.Framework.Portfolio;
    using QuantConnect.Algorithm.Framework.Portfolio.SignalExports;
    using QuantConnect.Algorithm.Framework.Execution;
    using QuantConnect.Algorithm.Framework.Risk;
    using QuantConnect.Algorithm.Selection;
    using QuantConnect.Api;
    using QuantConnect.Parameters;
    using QuantConnect.Benchmarks;
    using QuantConnect.Brokerages;
    using QuantConnect.Commands;
    using QuantConnect.Configuration;
    using QuantConnect.Util;
    using QuantConnect.Interfaces;
    using QuantConnect.Algorithm;
    using QuantConnect.Indicators;
    using QuantConnect.Data;
    using QuantConnect.Data.Auxiliary;
    using QuantConnect.Data.Consolidators;
    using QuantConnect.Data.Custom;
    using QuantConnect.Data.Custom.IconicTypes;
    using QuantConnect.DataSource;
    using QuantConnect.Data.Fundamental;
    using QuantConnect.Data.Market;
    using QuantConnect.Data.Shortable;
    using QuantConnect.Data.UniverseSelection;
    using QuantConnect.Notifications;
    using QuantConnect.Orders;
    using QuantConnect.Orders.Fees;
    using QuantConnect.Orders.Fills;
    using QuantConnect.Orders.OptionExercise;
    using QuantConnect.Orders.Slippage;
    using QuantConnect.Orders.TimeInForces;
    using QuantConnect.Python;
    using QuantConnect.Scheduling;
    using QuantConnect.Securities;
    using QuantConnect.Securities.Equity;
    using QuantConnect.Securities.Future;
    using QuantConnect.Securities.Option;
    using QuantConnect.Securities.Positions;
    using QuantConnect.Securities.Forex;
    using QuantConnect.Securities.Crypto;
    using QuantConnect.Securities.CryptoFuture;
    using QuantConnect.Securities.IndexOption;
    using QuantConnect.Securities.Interfaces;
    using QuantConnect.Securities.Volatility;
    using QuantConnect.Storage;
    using QuantConnect.Statistics;
    using QCAlgorithmFramework = QuantConnect.Algorithm.QCAlgorithm;
    using QCAlgorithmFrameworkBridge = QuantConnect.Algorithm.QCAlgorithm;
    using Calendar = QuantConnect.Data.Consolidators.Calendar;
    using CoreAlgo.Architecture.Core.Implementations;
#endregion

namespace CoreAlgo.Architecture.Core.Interfaces
{
    /// <summary>
    /// Simple logger interface for the core infrastructure
    /// </summary>
    public interface ILogger<T>
    {
        /// <summary>
        /// Logs an informational message
        /// </summary>
        void LogInformation(string message, params object[] args);

        /// <summary>
        /// Logs a debug message
        /// </summary>
        void LogDebug(string message, params object[] args);

        /// <summary>
        /// Logs a warning message
        /// </summary>
        void LogWarning(string message, params object[] args);

        /// <summary>
        /// Logs an error message
        /// </summary>
        void LogError(string message, params object[] args);

        /// <summary>
        /// Logs an error message with exception
        /// </summary>
        void LogError(Exception exception, string message, params object[] args);
        
        /// <summary>
        /// Logs a message with explicit context information for better traceability
        /// </summary>
        void LogWithContext(string message, string className, string methodName, LogLevel level = LogLevel.Info);
        
        /// <summary>
        /// Central logging method with level checking
        /// </summary>
        void LogMessage(string message, LogLevel level = LogLevel.Debug);
    }
}
using System.Collections.Generic;
using QuantConnect;
using QuantConnect.Securities;
using CoreAlgo.Architecture.Core.Implementations;

namespace CoreAlgo.Architecture.Core.Interfaces
{
    /// <summary>
    /// Interface for position overlap validation rules
    /// Enables extensible rule system for different overlap scenarios
    /// </summary>
    public interface IPositionOverlapRule
    {
        /// <summary>
        /// Validates whether a new position can be opened without creating dangerous overlaps
        /// </summary>
        /// <param name="proposedSymbol">Symbol for the new position</param>
        /// <param name="quantity">Proposed quantity (positive for long, negative for short)</param>
        /// <param name="existingPositions">Current portfolio positions from QC's Portfolio API</param>
        /// <param name="strategyTag">Strategy identifier for context</param>
        /// <returns>ValidationResult indicating if position is allowed</returns>
        ValidationResult Validate(
            Symbol proposedSymbol, 
            decimal quantity, 
            IEnumerable<KeyValuePair<Symbol, SecurityHolding>> existingPositions,
            string strategyTag = "");

        /// <summary>
        /// Gets the name of this rule for logging and configuration
        /// </summary>
        string RuleName { get; }

        /// <summary>
        /// Gets description of what this rule validates
        /// </summary>
        string Description { get; }

        /// <summary>
        /// Whether this rule is enabled (allows dynamic rule toggling)
        /// </summary>
        bool IsEnabled { get; set; }
    }
}
#region imports
    using System;
    using System.Collections;
    using System.Collections.Generic;
    using System.Linq;
    using System.Globalization;
    using System.Drawing;
    using QuantConnect;
    using QuantConnect.Algorithm.Framework;
    using QuantConnect.Algorithm.Framework.Selection;
    using QuantConnect.Algorithm.Framework.Alphas;
    using QuantConnect.Algorithm.Framework.Portfolio;
    using QuantConnect.Algorithm.Framework.Portfolio.SignalExports;
    using QuantConnect.Algorithm.Framework.Execution;
    using QuantConnect.Algorithm.Framework.Risk;
    using QuantConnect.Algorithm.Selection;
    using QuantConnect.Api;
    using QuantConnect.Parameters;
    using QuantConnect.Benchmarks;
    using QuantConnect.Brokerages;
    using QuantConnect.Commands;
    using QuantConnect.Configuration;
    using QuantConnect.Util;
    using QuantConnect.Interfaces;
    using QuantConnect.Algorithm;
    using QuantConnect.Indicators;
    using QuantConnect.Data;
    using QuantConnect.Data.Auxiliary;
    using QuantConnect.Data.Consolidators;
    using QuantConnect.Data.Custom;
    using QuantConnect.Data.Custom.IconicTypes;
    using QuantConnect.DataSource;
    using QuantConnect.Data.Fundamental;
    using QuantConnect.Data.Market;
    using QuantConnect.Data.Shortable;
    using QuantConnect.Data.UniverseSelection;
    using QuantConnect.Notifications;
    using QuantConnect.Orders;
    using QuantConnect.Orders.Fees;
    using QuantConnect.Orders.Fills;
    using QuantConnect.Orders.OptionExercise;
    using QuantConnect.Orders.Slippage;
    using QuantConnect.Orders.TimeInForces;
    using QuantConnect.Python;
    using QuantConnect.Scheduling;
    using QuantConnect.Securities;
    using QuantConnect.Securities.Equity;
    using QuantConnect.Securities.Future;
    using QuantConnect.Securities.Option;
    using QuantConnect.Securities.Positions;
    using QuantConnect.Securities.Forex;
    using QuantConnect.Securities.Crypto;
    using QuantConnect.Securities.CryptoFuture;
    using QuantConnect.Securities.IndexOption;
    using QuantConnect.Securities.Interfaces;
    using QuantConnect.Securities.Volatility;
    using QuantConnect.Storage;
    using QuantConnect.Statistics;
    using QCAlgorithmFramework = QuantConnect.Algorithm.QCAlgorithm;
    using QCAlgorithmFrameworkBridge = QuantConnect.Algorithm.QCAlgorithm;
    using Calendar = QuantConnect.Data.Consolidators.Calendar;
    using CoreAlgo.Architecture.Core;
    using CoreAlgo.Architecture.Core.Implementations;
#endregion

namespace CoreAlgo.Architecture.Core.Interfaces
{
    /// <summary>
    /// Base interface for all trading strategies
    /// </summary>
    public interface IStrategy
    {
        /// <summary>
        /// Gets the unique name of the strategy
        /// </summary>
        string Name { get; }

        /// <summary>
        /// Gets the description of the strategy
        /// </summary>
        string Description { get; }

        /// <summary>
        /// Gets the version of the strategy
        /// </summary>
        string Version { get; }

        /// <summary>
        /// Gets the current state of the strategy
        /// </summary>
        StrategyState State { get; }

        /// <summary>
        /// Gets the parameters for this strategy
        /// </summary>
        Dictionary<string, object> Parameters { get; }

        /// <summary>
        /// Initializes the strategy with the given algorithm instance
        /// </summary>
        /// <param name="algorithm">The QCAlgorithm instance</param>
        void Initialize(QCAlgorithm algorithm);

        /// <summary>
        /// Initializes the strategy with parameters
        /// </summary>
        /// <param name="algorithm">The QCAlgorithm instance</param>
        /// <param name="parameters">Strategy-specific parameters</param>
        void Initialize(QCAlgorithm algorithm, Dictionary<string, object> parameters);

        /// <summary>
        /// Executes the strategy logic for the given data slice
        /// </summary>
        /// <param name="slice">The current data slice</param>
        void Execute(Slice slice);

        /// <summary>
        /// Called when the strategy is being shut down
        /// </summary>
        void Shutdown();

        /// <summary>
        /// Validates the strategy configuration
        /// </summary>
        /// <returns>True if the strategy is valid</returns>
        bool Validate();

        /// <summary>
        /// Gets performance metrics for the strategy
        /// </summary>
        /// <returns>Dictionary of performance metrics</returns>
        Dictionary<string, double> GetPerformanceMetrics();

        /// <summary>
        /// Resets the strategy to its initial state
        /// </summary>
        void Reset();

        /// <summary>
        /// Event raised when the strategy state changes
        /// </summary>
        event EventHandler<StrategyStateChangedEventArgs> StateChanged;

        /// <summary>
        /// Event raised when the strategy encounters an error
        /// </summary>
        event EventHandler<StrategyErrorEventArgs> ErrorOccurred;
    }
}
using System;
using System.Collections.Generic;
using System.ComponentModel;
using System.Linq;
using QuantConnect;
using CoreAlgo.Architecture.Core.Attributes;

namespace CoreAlgo.Architecture.Core.Models
{
    /// <summary>
    /// Configuration for Adaptive Scalper strategy
    /// Micro-scalps equities using ATR-driven adaptive stops/targets and spread quality gates
    /// </summary>
    public class AdaptiveScalperConfig : StrategyConfig
    {
        /// <summary>
        /// Default constructor - sets AMD as default underlying symbol for scalping
        /// </summary>
        public AdaptiveScalperConfig()
        {
            UnderlyingSymbol = "AMD";
            Symbols = new[] { "AMD" };
            StartDate = new DateTime(2025, 1, 1);
            EndDate = new DateTime(2025, 10, 10);

            TradingStartTime = new TimeSpan(9, 0, 0);  // 09:00 AM
            TradingEndTime = new TimeSpan(15, 0, 0);   // 15:00 PM

            MaxPositions = 0; // Disable base MaxPositions check - use MaxConcurrentTrades instead
            EnableOverlapPrevention = false; // Allow multiple entries for same underlying

            ProfitTarget = 0;
            StopLoss = 0;

            AccountSize = 1000000;
            DebugMode = false;
            LogEntryRestrictions = false;
            UnderlyingResolution = "Minute";
        }

        // ============================================================================
        // INDICATOR PARAMETERS
        // ============================================================================

        [StrategyParameter("AtrPeriod", 14)]
        [Description("ATR indicator lookback period (default: 14)")]
        public int AtrPeriod { get; set; } = 14;

        [StrategyParameter("AtrResolution", "Minute")]
        [Description("Resolution for ATR updates: Tick, Second, Minute (default: Minute)")]
        public string AtrResolution { get; set; } = "Minute";

        [StrategyParameter("VolatilityBaselineWindow", 50)]
        [Description("Rolling window size for ATR baseline calculation (default: 50)")]
        public int VolatilityBaselineWindow { get; set; } = 50;

        // ============================================================================
        // SPREAD QUALITY GATES
        // ============================================================================

        [StrategyParameter("MinSpread", 0.02)]
        [Description("Minimum acceptable spread in dollars (default: $0.02)")]
        public decimal MinSpread { get; set; } = 0.02m;

        [StrategyParameter("MaxSpread", 0.08)]
        [Description("Maximum acceptable spread in dollars (default: $0.08)")]
        public decimal MaxSpread { get; set; } = 0.08m;

        // ============================================================================
        // RISK, TARGETS, AND POSITION SIZING
        // ============================================================================

        [StrategyParameter("TargetDollarRisk", 120)]
        [Description("Target dollar risk per trade for position sizing (default: $120)")]
        public decimal TargetDollarRisk { get; set; } = 120m;

        [StrategyParameter("MinStopLoss", 0.015)]
        [Description("Minimum stop loss in dollars (default: $0.015)")]
        public decimal MinStopLoss { get; set; } = 0.015m;

        [StrategyParameter("StopLossAtrMultiple", 0.5)]
        [Description("Stop loss as multiple of ATR (default: 0.5x ATR)")]
        public decimal StopLossAtrMultiple { get; set; } = 0.5m;

        [StrategyParameter("TakeProfitAtrMultiple", 0.4)]
        [Description("Take profit as multiple of ATR (default: 0.4x ATR)")]
        public decimal TakeProfitAtrMultiple { get; set; } = 0.4m;

        [StrategyParameter("TrailingStartFraction", 0.5)]
        [Description("Fraction of take profit at which trailing stop activates (default: 0.5 = 50%)")]
        public decimal TrailingStartFraction { get; set; } = 0.5m;

        [StrategyParameter("DebugMode", false)]
        [Description("Enable verbose diagnostic logging for AdaptiveScalper (default: false)")]
        public bool DebugMode { get; set; }

        [StrategyParameter("LogEntryRestrictions", false)]
        [Description("Log detailed entry restriction checks (default: false)")]
        public override bool LogEntryRestrictions { get; set; }

        [StrategyParameter("OHVolatilityMultiplier", 2.0)]
        [Description("Multiplier for stop/target during opening hour (9:30-10:30) to account for higher volatility (default: 2.0)")]
        public decimal OHVolatilityMultiplier { get; set; } = 2.0m;

        // ============================================================================
        // THROTTLE AND KILL SWITCH
        // ============================================================================

        [StrategyParameter("DailyKillSwitch", -50000)]
        [Description("Daily loss limit - halt trading when reached (default: -$50,000)")]
        public decimal DailyKillSwitch { get; set; } = -50000m;

        [StrategyParameter("ThrottleDecay", 0.25)]
        [Description("Rate at which throttle reduces position size as losses build (default: 0.25)")]
        public decimal ThrottleDecay { get; set; } = 0.25m;

        [StrategyParameter("MinThrottle", 0.10)]
        [Description("Minimum throttle level - trading halts below this (default: 0.10 = 10%)")]
        public decimal MinThrottle { get; set; } = 0.10m;

        // ============================================================================
        // HFT MULTI-TRADE PARAMETERS
        // ============================================================================

        [StrategyParameter("MaxConcurrentTrades", 50)]
        [Description("Maximum number of concurrent independent trades (default: 50)")]
        public int MaxConcurrentTrades { get; set; } = 50;

        [StrategyParameter("MaxEntriesPerDay", 0)]
        [Description("Maximum number of entry orders per day (default: 0)")]
        public int MaxEntriesPerDay { get; set; } = 0;

        [StrategyParameter("EntryCooldownMs", 150)]
        [Description("Minimum milliseconds between entry orders to prevent bursts (default: 150ms)")]
        public int EntryCooldownMs { get; set; } = 150;

        [StrategyParameter("MaxOrdersPerMinute", 300)]
        [Description("Maximum orders per minute rate limit (default: 300)")]
        public int MaxOrdersPerMinute { get; set; } = 300;

        [StrategyParameter("RequireQuotesForEntry", false)]
        [Description("If true, require valid bid/ask quotes before entering trades (default: false - use fallback spread=0 for Minute bars)")]
        public bool RequireQuotesForEntry { get; set; } = false;

        [StrategyParameter("EntryLimitOffsetTicks", 0)]
        [Description("Offset in ticks from mid-price for entry limit orders (default: 0 = at mid)")]
        public int EntryLimitOffsetTicks { get; set; } = 0;

        [StrategyParameter("StopLimitBufferTicks", 2)]
        [Description("Buffer in ticks between stop price and limit price for stop-limit orders (default: 2)")]
        public int StopLimitBufferTicks { get; set; } = 2;

        // ============================================================================
        // DATA RESOLUTION
        // ============================================================================

        /// <summary>
        /// Get the Resolution enum value for ATR updates
        /// </summary>
        public Resolution GetAtrResolution()
        {
            return AtrResolution?.ToUpperInvariant() switch
            {
                "TICK" => Resolution.Tick,
                "SECOND" => Resolution.Second,
                "MINUTE" => Resolution.Minute,
                "HOUR" => Resolution.Hour,
                "DAILY" => Resolution.Daily,
                _ => Resolution.Minute // Default to Minute
            };
        }

        /// <summary>
        /// Key parameters for AdaptiveScalper optimization
        /// </summary>
        public override string[] OptimizationParameters => base.OptimizationParameters.Concat(new[]
        {
            "AtrPeriod",
            "VolatilityBaselineWindow",
            "MinSpread",
            "MaxSpread",
            "TargetDollarRisk",
            "MinStopLoss",
            "StopLossAtrMultiple",
            "TakeProfitAtrMultiple",
            "TrailingStartFraction",
            "DailyKillSwitch",
            "ThrottleDecay",
            "OHVolatilityMultiplier",
            "MaxConcurrentTrades",
            "MaxEntriesPerDay",
            "EntryCooldownMs",
            "MaxOrdersPerMinute",
            "RequireQuotesForEntry",
            "EntryLimitOffsetTicks",
            "StopLimitBufferTicks"
        }).ToArray();

        /// <summary>
        /// Override base validation to allow MaxPositions = 0 (we use MaxConcurrentTrades instead)
        /// </summary>
        public override string[] Validate()
        {
            var errors = new List<string>();

            // Skip base MaxPositions validation - we use MaxConcurrentTrades instead
            // Validate our own concurrency parameter
            if (MaxConcurrentTrades <= 0)
                errors.Add($"MaxConcurrentTrades must be greater than 0 (current: {MaxConcurrentTrades})");

            // Entry window validation
            if (UseEntryTimeWindow)
            {
                if (EntryWindowStart > EntryWindowEnd)
                {
                    errors.Add($"EntryWindowStart {EntryWindowStart:hh\\:mm} must be <= EntryWindowEnd {EntryWindowEnd:hh\\:mm}");
                }
            }

            // HFT-specific validations
            if (EntryCooldownMs < 0)
                errors.Add($"EntryCooldownMs must be >= 0 (current: {EntryCooldownMs})");

            if (MaxOrdersPerMinute <= 0)
                errors.Add($"MaxOrdersPerMinute must be greater than 0 (current: {MaxOrdersPerMinute})");

            return errors.ToArray();
        }

        public override string ToString()
        {
            return $"AdaptiveScalper[{UnderlyingSymbol}] ATR:{AtrPeriod} Spread:{MinSpread:F3}-{MaxSpread:F3} " +
                   $"Risk:${TargetDollarRisk} SL:{StopLossAtrMultiple}xATR TP:{TakeProfitAtrMultiple}xATR " +
                   $"Trail@{TrailingStartFraction:P0} Kill:${DailyKillSwitch} MaxConcurrent:{MaxConcurrentTrades} " +
                   $"EntryOffset:{EntryLimitOffsetTicks}t SLBuffer:{StopLimitBufferTicks}t " +
                   $"Resolution:{UnderlyingResolution}";
        }
    }
}

using CoreAlgo.Architecture.Core.Attributes;

namespace CoreAlgo.Architecture.Core.Models
{
    /// <summary>
    /// Basic strategy configuration for simple strategies like Iron Condor, Covered Call, etc.
    /// Provides concrete implementation of StrategyConfig for strategies that don't need special configuration.
    /// </summary>
    public class BasicStrategyConfig : StrategyConfig
    {
        // This class inherits all [StrategyParameter] attributes from StrategyConfig
        // and provides a concrete implementation that can be instantiated
        
        // No additional properties needed - base class has all the standard parameters:
        // - Symbols, UnderlyingSymbol
        // - EntryDeltaMin/Max, ExitDelta
        // - AllocationPerPosition, MaxPositions
        // - TradingStartTime/EndTime
        // - ProfitTarget, StopLoss, MaxDaysInTrade
        // - MinImpliedVolatility
        // - UseUniverseSelection

        /// <summary>
        /// Basic strategies use the core optimization parameters from base class
        /// No strategy-specific parameters need to be added for optimization
        /// </summary>
        public override string[] OptimizationParameters => base.OptimizationParameters;
    }
}
using System;
using System.Linq;
using QuantConnect;
using CoreAlgo.Architecture.Core.Attributes;

namespace CoreAlgo.Architecture.Core.Models
{
    /// <summary>
    /// Base configuration class for futures trading strategies.
    /// Provides common parameters for futures contract management, rollover handling,
    /// and continuous contract settings.
    /// </summary>
    public abstract class FuturesConfig : StrategyConfig
    {
        /// <summary>
        /// The futures symbol to trade (e.g., "ES", "GC", "CL")
        /// </summary>
        [StrategyParameter("Futures symbol", "ES")]
        public string FutureSymbol { get; set; } = "ES";

        /// <summary>
        /// Number of days before contract expiration to initiate rollover
        /// Only relevant for manual contract management (not continuous contracts)
        /// </summary>
        [StrategyParameter("Rollover days to expiry", 5)]
        public int ContractRolloverDays { get; set; } = 5;

        /// <summary>
        /// Whether to use continuous contracts (recommended for backtesting)
        /// When true, QuantConnect automatically handles contract rollovers
        /// </summary>
        [StrategyParameter("Use continuous contracts", true)]
        public bool UseContinuousContract { get; set; } = true;

        /// <summary>
        /// Contract depth offset (0 = front month, 1 = next month, etc.)
        /// Used when adding the futures contract
        /// </summary>
        [StrategyParameter("Contract depth offset", 0)]
        public int ContractDepthOffset { get; set; } = 0;

        /// <summary>
        /// Position sizing multiplier relative to account equity
        /// For futures, this is combined with contract multiplier
        /// </summary>
        [StrategyParameter("Position size multiplier", 1.0)]
        public decimal PositionSizeMultiplier { get; set; } = 1.0m;

        /// <summary>
        /// Maximum number of contracts to trade (risk management)
        /// </summary>
        [StrategyParameter("Maximum contracts per trade", 5)]
        public int MaxContractsPerTrade { get; set; } = 5;

        /// <summary>
        /// Data resolution for futures data subscription
        /// </summary>
        [StrategyParameter("Data resolution", "Minute")]
        public string DataResolution { get; set; } = "Minute";

        /// <summary>
        /// Parse the data resolution string to Resolution enum
        /// </summary>
        /// <returns>Resolution enum value</returns>
        public Resolution GetDataResolution()
        {
            if (Enum.TryParse<Resolution>(DataResolution, true, out var resolution))
            {
                return resolution;
            }
            return Resolution.Minute; // Default fallback
        }

        /// <summary>
        /// Get the futures category based on the symbol
        /// Useful for strategy-specific logic
        /// </summary>
        /// <returns>Futures category string</returns>
        public string GetFuturesCategory()
        {
            var symbol = FutureSymbol?.ToUpperInvariant();
            
            // Equity Index Futures
            if (new[] { "ES", "NQ", "YM", "RTY", "EMD", "NKD" }.Contains(symbol))
                return "equity";
                
            // Energy Futures
            if (new[] { "CL", "NG", "RB", "HO", "BZ" }.Contains(symbol))
                return "energy";
                
            // Metal Futures
            if (new[] { "GC", "SI", "HG", "PA", "PL" }.Contains(symbol))
                return "metals";
                
            // Agricultural Futures
            if (new[] { "ZC", "ZS", "ZW", "ZM", "ZL", "KC", "CT", "SB", "CC", "OJ" }.Contains(symbol))
                return "agricultural";
                
            // Bond/Interest Rate Futures
            if (new[] { "ZB", "ZN", "ZF", "TU", "UB", "ED", "SR1", "SR3" }.Contains(symbol))
                return "bonds";
                
            // Currency Futures
            if (new[] { "6E", "6J", "6B", "6S", "6C", "6A", "6N", "6M", "E7", "J7" }.Contains(symbol))
                return "currency";
                
            // Volatility Futures
            if (new[] { "VX" }.Contains(symbol))
                return "volatility";
                
            // Crypto Futures
            if (new[] { "BTC", "ETH" }.Contains(symbol))
                return "crypto";
                
            return "unknown";
        }

        /// <summary>
        /// Optimization parameters for futures trading strategies.
        /// Focuses on core futures parameters and contract management
        /// </summary>
        public override string[] OptimizationParameters => base.OptimizationParameters.Concat(new[]
        {
            "FutureSymbol",           // Asset selection for futures
            "PositionSizeMultiplier", // Position sizing relative to equity
            "MaxContractsPerTrade",   // Risk management - contract limit
            "ContractRolloverDays",   // Contract management timing
            "UseContinuousContract",  // Contract type selection
            "ContractDepthOffset",    // Contract selection depth
            "DataResolution"          // Data granularity for strategy
        }).ToArray();
    }
}
using System;
using System.Linq;
using QuantConnect.Algorithm;
using CoreAlgo.Architecture.Core.Attributes;
using CoreAlgo.Architecture.QC.Helpers;
using CoreAlgo.Architecture.Core.Interfaces;

namespace CoreAlgo.Architecture.Core.Models
{
    /// <summary>
    /// Enhanced strategy configuration for multi-asset options trading.
    /// Extends StrategyConfig with asset-specific parameter adaptation and validation.
    /// </summary>
    public class MultiAssetConfig : StrategyConfig
    {
        /// <summary>
        /// Enable automatic asset-specific parameter adaptation
        /// When true, delta ranges, position limits, and strike widths are automatically
        /// adjusted based on the underlying asset characteristics
        /// </summary>
        [StrategyParameter("EnableAssetAdaptation", true)]
        public bool EnableAssetAdaptation { get; set; } = true;

        /// <summary>
        /// Maximum number of different assets to trade simultaneously
        /// Helps control correlation risk when trading multiple underlyings
        /// </summary>
        [StrategyParameter("MaxAssets", 3)]
        public int MaxAssets { get; set; } = 3;

        /// <summary>
        /// Strike width multiplier for option selection
        /// Base value that gets adjusted per asset (e.g., SPX gets wider, AAPL gets narrower)
        /// </summary>
        [StrategyParameter("BaseStrikeWidth", 0.05)]
        public decimal BaseStrikeWidth { get; set; } = 0.05m; // 5% base strike width

        /// <summary>
        /// Whether to use equal allocation across all assets or asset-specific allocation
        /// </summary>
        [StrategyParameter("UseEqualAllocation", false)]
        public bool UseEqualAllocation { get; set; } = false;

        /// <summary>
        /// Maximum correlation threshold between assets (0.0 to 1.0)
        /// Prevents trading highly correlated assets simultaneously
        /// </summary>
        [StrategyParameter("MaxCorrelation", 0.7)]
        public decimal MaxCorrelation { get; set; } = 0.7m;

        /// <summary>
        /// Minimum asset price for inclusion (helps filter penny stocks)
        /// </summary>
        [StrategyParameter("MinAssetPrice", 10.0)]
        public decimal MinAssetPrice { get; set; } = 10.0m;

        /// <summary>
        /// Load parameters and apply asset-specific adaptations
        /// </summary>
        public override void LoadFromParameters(IAlgorithmContext context)
        {
            // Load base parameters first
            base.LoadFromParameters(context);

            // Apply asset-specific adaptations if enabled
            if (EnableAssetAdaptation && Symbols != null && Symbols.Length > 0)
            {
                ApplyAssetAdaptations(context);
            }

            // Validate multi-asset specific constraints
            ValidateMultiAssetConstraints(context);
        }

        /// <summary>
        /// Apply asset-specific parameter adaptations based on the selected symbols
        /// </summary>
        private void ApplyAssetAdaptations(IAlgorithmContext context)
        {
            ((dynamic)context.Logger).Info("MultiAssetConfig: Applying asset-specific adaptations");

            // For multi-asset strategies, we'll adapt based on the first (primary) symbol
            // or calculate weighted averages for portfolio-level parameters
            var primarySymbol = Symbols[0];
            
            // Get asset-specific delta targets
            var (adaptedDeltaMin, adaptedDeltaMax) = MultiAssetHelper.GetAssetDeltaTargets(
                primarySymbol, EntryDeltaMin, EntryDeltaMax);
                
            EntryDeltaMin = adaptedDeltaMin;
            EntryDeltaMax = adaptedDeltaMax;

            // Get asset-specific position limits
            var (minPositions, maxPositions, recommendedAllocation) = MultiAssetHelper.GetAssetPositionLimits(
                primarySymbol, StartingCash);

            // Update max positions if not using equal allocation
            if (!UseEqualAllocation)
            {
                MaxPositions = Math.Min(MaxPositions, maxPositions);
                AllocationPerPosition = recommendedAllocation;
            }

            ((dynamic)context.Logger).Info($"MultiAssetConfig: Adapted for {primarySymbol} - " +
                               $"Delta: {EntryDeltaMin:F2}-{EntryDeltaMax:F2}, " +
                               $"MaxPos: {MaxPositions}, " +
                               $"Allocation: {AllocationPerPosition:P1}");

            // Log asset profiles for all symbols
            foreach (var symbol in Symbols)
            {
                var profile = MultiAssetHelper.GetAssetProfile(symbol);
                if (profile != null)
                {
                    ((dynamic)context.Logger).Info($"MultiAssetConfig: {symbol} profile - {profile}");
                }
                else
                {
                    ((dynamic)context.Logger).Info($"MultiAssetConfig: {symbol} - using default profile (unknown asset)");
                }
            }
        }

        /// <summary>
        /// Validate multi-asset specific constraints
        /// </summary>
        private void ValidateMultiAssetConstraints(IAlgorithmContext context)
        {
            // Check asset count limit
            if (Symbols.Length > MaxAssets)
            {
                ((dynamic)context.Logger).Error($"MultiAssetConfig: Too many assets ({Symbols.Length}), maximum allowed is {MaxAssets}");
            }

            // Check for valid options support
            var unsupportedSymbols = Symbols.Where(symbol => !MultiAssetHelper.HasLiquidOptions(symbol)).ToList();
            if (unsupportedSymbols.Any())
            {
                ((dynamic)context.Logger).Warning($"MultiAssetConfig: Warning - symbols may have limited options liquidity: {string.Join(", ", unsupportedSymbols)}");
            }

            // Log configuration summary
            var summaryMessages = new[]
            {
                $"MultiAssetConfig: Trading {Symbols.Length} assets: {string.Join(", ", Symbols)}",
                $"MultiAssetConfig: Asset adaptation: {(EnableAssetAdaptation ? "Enabled" : "Disabled")}",
                $"MultiAssetConfig: Equal allocation: {(UseEqualAllocation ? "Enabled" : "Disabled")}"
            };

            foreach (var message in summaryMessages)
            {
                ((dynamic)context.Logger).Info(message);
            }
        }

        /// <summary>
        /// Get asset-specific strike width for a symbol
        /// </summary>
        public decimal GetStrikeWidthForAsset(string symbol)
        {
            return MultiAssetHelper.GetAssetStrikeWidth(symbol, BaseStrikeWidth);
        }

        /// <summary>
        /// Get position limits for a specific asset
        /// </summary>
        public (int MinPositions, int MaxPositions, decimal RecommendedAllocation) GetPositionLimitsForAsset(string symbol)
        {
            return MultiAssetHelper.GetAssetPositionLimits(symbol, StartingCash);
        }

        /// <summary>
        /// Get delta targets for a specific asset
        /// </summary>
        public (decimal DeltaMin, decimal DeltaMax) GetDeltaTargetsForAsset(string symbol)
        {
            return MultiAssetHelper.GetAssetDeltaTargets(symbol, EntryDeltaMin, EntryDeltaMax);
        }

        /// <summary>
        /// Validate the multi-asset configuration
        /// </summary>
        public override string[] Validate()
        {
            var errors = base.Validate().ToList();

            // Multi-asset specific validations
            if (Symbols == null || Symbols.Length == 0)
            {
                errors.Add("Symbols array cannot be null or empty");
            }

            if (MaxAssets < 1)
            {
                errors.Add("MaxAssets must be at least 1");
            }

            if (BaseStrikeWidth <= 0)
            {
                errors.Add("BaseStrikeWidth must be positive");
            }

            if (MaxCorrelation < 0 || MaxCorrelation > 1)
            {
                errors.Add("MaxCorrelation must be between 0.0 and 1.0");
            }

            if (MinAssetPrice <= 0)
            {
                errors.Add("MinAssetPrice must be positive");
            }

            // Asset-specific validation against account size and margin requirements
            if (Symbols != null && Symbols.Length > 0)
            {
                foreach (var symbol in Symbols)
                {
                    var profile = MultiAssetHelper.GetAssetProfile(symbol);
                    if (profile != null)
                    {
                        // Check if account size meets minimum requirements for this asset
                        if (AccountSize < profile.MinAccountSize)
                        {
                            errors.Add($"Account size ${AccountSize:F0} below minimum ${profile.MinAccountSize:F0} required for {symbol}");
                        }
                        
                        // Check if allocation is reasonable for this asset type
                        if (AssetManager.IsIndex(symbol) && AllocationPerPosition > 0.1m)
                        {
                            errors.Add($"Allocation {AllocationPerPosition:P0} too high for index option {symbol} (recommended max 10%)");
                        }
                    }
                    else
                    {
                        // Warning for unknown assets
                        errors.Add($"Unknown asset {symbol} - option liquidity not verified");
                    }
                }
            }

            return errors.ToArray();
        }

        /// <summary>
        /// Key parameters for Multi-Asset strategy optimization
        /// Focuses on asset selection, adaptation, and allocation management
        /// </summary>
        public override string[] OptimizationParameters => base.OptimizationParameters.Concat(new[]
        {
            "EnableAssetAdaptation", // Asset-specific adaptation toggle
            "MaxAssets",             // Portfolio diversification limit
            "BaseStrikeWidth",       // Strike selection base width
            "UseEqualAllocation",    // Allocation strategy choice
            "MaxCorrelation",        // Risk management - correlation limit
            "MinAssetPrice"          // Asset filtering - minimum price
        }).ToArray();

    }
}
using System.Linq;
using CoreAlgo.Architecture.Core.Attributes;

namespace CoreAlgo.Architecture.Core.Models
{
    /// <summary>
    /// Configuration for multi-asset Iron Condor strategy focused on Equity options (AAPL, MSFT, GOOGL)
    /// Demonstrates asset-specific parameter defaults optimized for individual stock options
    /// </summary>
    public class MultiAssetEquityConfig : MultiAssetConfig
    {
        /// <summary>
        /// Default to major tech stocks with liquid options
        /// </summary>
        [StrategyParameter("Symbols", "AAPL,MSFT,GOOGL")]
        public new string[] Symbols { get; set; } = new[] { "AAPL", "MSFT", "GOOGL" };

        /// <summary>
        /// Enable asset adaptation for equity options
        /// </summary>
        [StrategyParameter("EnableAssetAdaptation", true)]
        public new bool EnableAssetAdaptation { get; set; } = true;

        /// <summary>
        /// Allow more equity assets for diversification
        /// </summary>
        [StrategyParameter("MaxAssets", 5)]
        public new int MaxAssets { get; set; } = 5;

        /// <summary>
        /// Narrower strike width for equity options (smaller premium)
        /// </summary>
        [StrategyParameter("BaseStrikeWidth", 0.04)]
        public new decimal BaseStrikeWidth { get; set; } = 0.04m; // 4% for equities

        /// <summary>
        /// Use equal allocation across equity positions
        /// </summary>
        [StrategyParameter("UseEqualAllocation", true)]
        public new bool UseEqualAllocation { get; set; } = true;

        /// <summary>
        /// Lower minimum price for equity options
        /// </summary>
        [StrategyParameter("MinAssetPrice", 50.0)]
        public new decimal MinAssetPrice { get; set; } = 50.0m;

        /// <summary>
        /// Wider delta range for equities (lower gamma risk)
        /// </summary>
        [StrategyParameter("EntryDeltaMin", 0.15)]
        public new decimal EntryDeltaMin { get; set; } = 0.15m;

        [StrategyParameter("EntryDeltaMax", 0.35)]
        public new decimal EntryDeltaMax { get; set; } = 0.35m;

        /// <summary>
        /// Lower allocation per position for equities (more but smaller trades)
        /// </summary>
        [StrategyParameter("AllocationPerPosition", 0.08)]
        public new decimal AllocationPerPosition { get; set; } = 0.08m;

        /// <summary>
        /// More max positions for equity strategies
        /// </summary>
        [StrategyParameter("MaxPositions", 8)]
        public new int MaxPositions { get; set; } = 8;

        /// <summary>
        /// Lower profit target for equity options (faster premium decay)
        /// </summary>
        [StrategyParameter("ProfitTarget", 0.40)]
        public new decimal ProfitTarget { get; set; } = 0.40m;

        /// <summary>
        /// Wider stop loss for equity options (lower volatility)
        /// </summary>
        [StrategyParameter("StopLoss", -0.60)]
        public new decimal StopLoss { get; set; } = -0.60m;

        /// <summary>
        /// Shorter hold time for equity options (faster moves)
        /// </summary>
        [StrategyParameter("MaxDaysInTrade", 30)]
        public new int MaxDaysInTrade { get; set; } = 30;

        /// <summary>
        /// Higher volatility requirement for equity options
        /// </summary>
        [StrategyParameter("MinImpliedVolatility", 0.25)]
        public new decimal MinImpliedVolatility { get; set; } = 0.25m;

        /// <summary>
        /// Optimization parameters for multi-asset equity strategy.
        /// Focuses on equity-specific adaptations and portfolio management
        /// </summary>
        public override string[] OptimizationParameters => base.OptimizationParameters.Concat(new[]
        {
            "BaseStrikeWidth",       // Strike positioning for equity options
            "EntryDeltaMin",         // Delta targeting - minimum
            "EntryDeltaMax",         // Delta targeting - maximum
            "AllocationPerPosition",  // Position sizing per equity
            "MaxPositions",          // Portfolio diversification limit
            "ProfitTarget",          // Exit target for equity volatility
            "StopLoss",              // Risk management
            "MaxDaysInTrade",        // Holding period for equity moves
            "MinImpliedVolatility",  // Volatility filter for equity options
            "MaxAssets"              // Asset diversification control
        }).ToArray();
    }
}
using System;
using System.Linq;
using CoreAlgo.Architecture.Core.Attributes;

namespace CoreAlgo.Architecture.Core.Models
{
    /// <summary>
    /// Configuration for multi-asset Iron Condor strategy focused on Index options (SPX, NDX)
    /// Demonstrates asset-specific parameter defaults optimized for high-value index options
    /// </summary>
    public class MultiAssetSPXConfig : MultiAssetConfig
    {
        /// <summary>
        /// Default to SPX and SPY for viable margin strategy (SPX index + SPY equity)
        /// </summary>
        [StrategyParameter("Symbols", "SPX,SPY")]
        public new string[] Symbols { get; set; } = new[] { "SPX", "SPY" };

        /// <summary>
        /// Enable asset adaptation for index options
        /// </summary>
        [StrategyParameter("EnableAssetAdaptation", true)]
        public new bool EnableAssetAdaptation { get; set; } = true;

        /// <summary>
        /// Limit to 2 index assets to control correlation risk
        /// </summary>
        [StrategyParameter("MaxAssets", 2)]
        public new int MaxAssets { get; set; } = 2;

        /// <summary>
        /// Wider strike width for index options (higher premium)
        /// </summary>
        [StrategyParameter("BaseStrikeWidth", 0.06)]
        public new decimal BaseStrikeWidth { get; set; } = 0.06m; // 6% for indices

        /// <summary>
        /// Use asset-specific allocation (indices need less diversification)
        /// </summary>
        [StrategyParameter("UseEqualAllocation", false)]
        public new bool UseEqualAllocation { get; set; } = false;

        /// <summary>
        /// Higher minimum price for index options
        /// </summary>
        [StrategyParameter("MinAssetPrice", 3000.0)]
        public new decimal MinAssetPrice { get; set; } = 3000.0m;

        /// <summary>
        /// Tighter delta range for indices (higher gamma risk)
        /// </summary>
        [StrategyParameter("EntryDeltaMin", 0.20)]
        public new decimal EntryDeltaMin { get; set; } = 0.20m;

        [StrategyParameter("EntryDeltaMax", 0.30)]
        public new decimal EntryDeltaMax { get; set; } = 0.30m;

        /// <summary>
        /// Conservative allocation per position for margin-intensive strategies
        /// </summary>
        [StrategyParameter("AllocationPerPosition", 0.05)]
        public new decimal AllocationPerPosition { get; set; } = 0.05m;

        /// <summary>
        /// Fewer max positions for index strategies
        /// </summary>
        [StrategyParameter("MaxPositions", 3)]
        public new int MaxPositions { get; set; } = 3;

        /// <summary>
        /// Higher profit target for index options (better premium decay)
        /// </summary>
        [StrategyParameter("ProfitTarget", 0.60)]
        public new decimal ProfitTarget { get; set; } = 0.60m;

        /// <summary>
        /// Tighter stop loss for index options (higher volatility)
        /// </summary>
        [StrategyParameter("StopLoss", -0.80)]
        public new decimal StopLoss { get; set; } = -0.80m;

        /// <summary>
        /// Longer hold time for index options (better time decay)
        /// </summary>
        [StrategyParameter("MaxDaysInTrade", 45)]
        public new int MaxDaysInTrade { get; set; } = 45;

        /// <summary>
        /// Higher account size for index options (required for SPX margin requirements)
        /// </summary>
        [StrategyParameter("AccountSize", 300000)]
        public override decimal AccountSize { get; set; } = 300000m;

        /// <summary>
        /// Shorter test period for performance testing (1 month instead of 1 year)
        /// </summary>
        [StrategyParameter("StartDate", "2024-01-01")]
        public new DateTime StartDate { get; set; } = new DateTime(2024, 1, 1);

        [StrategyParameter("EndDate", "2024-01-31")]
        public new DateTime EndDate { get; set; } = new DateTime(2024, 1, 31);

        /// <summary>
        /// Key parameters for Multi-Asset SPX strategy optimization
        /// Focuses on index-specific parameters and high-value option characteristics
        /// </summary>
        public override string[] OptimizationParameters => base.OptimizationParameters.Concat(new[]
        {
            "BaseStrikeWidth",        // Strike width for index options
            "EntryDeltaMin",          // Delta targeting - minimum
            "EntryDeltaMax",          // Delta targeting - maximum
            "AllocationPerPosition",  // Position sizing for high-margin strategies
            "MaxPositions",           // Portfolio concentration for indices
            "ProfitTarget",           // Exit strategy - profit taking
            "StopLoss",               // Exit strategy - loss cutting
            "MaxDaysInTrade"          // Hold period for index options
        }).ToArray();
    }
}
using System;
using System.Collections.Generic;
using System.Linq;
using System.Reflection;
using QuantConnect;
using QuantConnect.Algorithm;
using CoreAlgo.Architecture.Core.Attributes;
using CoreAlgo.Architecture.Core.Interfaces;

namespace CoreAlgo.Architecture.Core.Models
{
    /// <summary>
    /// Base class for all strategy configurations with QC GetParameter() integration
    /// </summary>
    public abstract class StrategyConfig
    {
        /// <summary>
        /// Underlying symbol to trade (e.g., "SPY", "QQQ", "AAPL")
        /// </summary>
        public string UnderlyingSymbol { get; set; } = "SPY";

        /// <summary>
        /// Strategy start date
        /// Configurable via StartDate parameter in config.json
        /// </summary>
        [StrategyParameter("StartDate", "2023-01-01")]
        public DateTime StartDate { get; set; } = new DateTime(2023, 1, 1);

        /// <summary>
        /// Strategy end date
        /// Configurable via EndDate parameter in config.json
        /// </summary>
        [StrategyParameter("EndDate", "2023-12-31")]
        public DateTime EndDate { get; set; } = new DateTime(2023, 12, 31);

        /// <summary>
        /// Starting cash amount
        /// </summary>
        public decimal StartingCash { get; set; } = 100000m;

        /// <summary>
        /// Account size for position sizing and margin calculations (configurable via parameters)
        /// </summary>
        [StrategyParameter("AccountSize", 100000)]
        public virtual decimal AccountSize { get; set; } = 100000m;

        // ============================================================================
        // POSITION OVERLAP PREVENTION CONFIGURATION (Task 19)
        // ============================================================================

        /// <summary>
        /// Enable position overlap prevention system
        /// </summary>
        [StrategyParameter("EnableOverlapPrevention", true)]
        public bool EnableOverlapPrevention { get; set; } = true;

        /// <summary>
        /// Position overlap prevention mode: Block, Warn, or Log
        /// Block: Prevents orders that would create overlaps
        /// Warn: Allows orders but logs warnings
        /// Log: Only logs overlap detection for analysis
        /// </summary>
        [StrategyParameter("OverlapPreventionMode", "Block")]
        public string OverlapPreventionMode { get; set; } = "Block";

        /// <summary>
        /// Allow multiple strategies on same underlying (advanced users only)
        /// </summary>
        [StrategyParameter("AllowMultiStrategyUnderlying", false)]
        public bool AllowSameUnderlyingMultiStrategy { get; set; } = false;

        /// <summary>
        /// Maximum number of active positions per underlying asset
        /// </summary>
        [StrategyParameter("MaxPositionsPerUnderlying", 1)]
        public int MaxPositionsPerUnderlying { get; set; } = 1;

        /// <summary>
        /// Maximum number of active combo orders per underlying asset
        /// Combo orders are multi-leg strategies like Iron Condors, Spreads, etc.
        /// </summary>
        [StrategyParameter("MaxPositionsPerCombo", 2)]
        public int MaxPositionsPerCombo { get; set; } = 2;

        /// <summary>
        /// Expected leg count for this strategy's combo orders
        /// Used for structural analysis: 2=Spread, 4=Iron Condor, etc.
        /// Set to 0 for single-leg strategies or variable leg counts
        /// </summary>
        [StrategyParameter("ComboOrderLegCount", 0)]
        public int ComboOrderLegCount { get; set; } = 0;

        /// <summary>
        /// Minimum strike distance for options overlap detection (in dollars)
        /// </summary>
        [StrategyParameter("MinimumStrikeDistance", 5.0)]
        public decimal MinimumStrikeDistance { get; set; } = 5.0m;

        // ============================================================================
        // ENHANCED STRATEGY PARAMETERS (Added for Task 2)
        // ============================================================================

        /// <summary>
        /// Array of symbols to trade (supports multi-asset strategies)
        /// Default to single SPY for backward compatibility
        /// </summary>
        [StrategyParameter("Symbols", "SPY")]
        public string[] Symbols { get; set; } = new[] { "SPY" };

        /// <summary>
        /// Enable universe selection instead of manual symbol list
        /// </summary>
        [StrategyParameter("UseUniverseSelection", false)]
        public bool UseUniverseSelection { get; set; } = false;

        /// <summary>
        /// Minimum delta for option entry (for options strategies)
        /// </summary>
        [StrategyParameter("EntryDeltaMin", 0.25)]
        public decimal EntryDeltaMin { get; set; } = 0.25m;

        /// <summary>
        /// Maximum delta for option entry (for options strategies)
        /// </summary>
        [StrategyParameter("EntryDeltaMax", 0.35)]
        public decimal EntryDeltaMax { get; set; } = 0.35m;

        /// <summary>
        /// Delta threshold for option exit (for options strategies)
        /// </summary>
        [StrategyParameter("ExitDelta", 0.10)]
        public decimal ExitDelta { get; set; } = 0.10m;

        /// <summary>
        /// Allocation percentage per position (e.g., 0.1 = 10% of portfolio per position)
        /// </summary>
        [StrategyParameter("AllocationPerPosition", 0.1)]
        public decimal AllocationPerPosition { get; set; } = 0.1m;

        /// <summary>
        /// Maximum number of concurrent positions
        /// </summary>
        [StrategyParameter("MaxPositions", 5)]
        public int MaxPositions { get; set; } = 5;

        /// <summary>
        /// Trading start time (market hours restriction)
        /// </summary>
        [StrategyParameter("TradingStartTime", "09:30:00")]
        public TimeSpan TradingStartTime { get; set; } = new TimeSpan(9, 30, 0);

        /// <summary>
        /// Trading end time (market hours restriction)
        /// </summary>
        [StrategyParameter("TradingEndTime", "15:30:00")]
        public TimeSpan TradingEndTime { get; set; } = new TimeSpan(15, 30, 0);

        /// <summary>
        /// Enable a central entry time window. When enabled, entries are only allowed between EntryWindowStart and EntryWindowEnd
        /// </summary>
        [StrategyParameter("UseEntryTimeWindow", false)]
        public bool UseEntryTimeWindow { get; set; } = false;

        /// <summary>
        /// Earliest time-of-day to allow new entries (ET). Ignored when UseEntryTimeWindow is false
        /// </summary>
        [StrategyParameter("EntryWindowStart", "00:00:00")]
        public TimeSpan EntryWindowStart { get; set; } = TimeSpan.Zero;

        /// <summary>
        /// Latest time-of-day to allow new entries (ET). Ignored when UseEntryTimeWindow is false
        /// </summary>
        [StrategyParameter("EntryWindowEnd", "23:59:59")]
        public TimeSpan EntryWindowEnd { get; set; } = new TimeSpan(23, 59, 59);

        /// <summary>
        /// Profit target as percentage (e.g., 0.5 = 50% profit target)
        /// Set to 0 to disable profit target checks (for strategies using custom exits)
        /// </summary>
        [StrategyParameter("ProfitTarget", 0.5)]
        public decimal ProfitTarget { get; set; } = 0.5m;

        /// <summary>
        /// Stop loss as percentage (e.g., -0.5 = 50% stop loss)
        /// Set to 0 to disable stop loss checks (for strategies using custom exits)
        /// </summary>
        [StrategyParameter("StopLoss", -0.5)]
        public decimal StopLoss { get; set; } = -0.5m;

        /// <summary>
        /// Maximum days to hold a position
        /// </summary>
        [StrategyParameter("MaxDaysInTrade", 30)]
        public int MaxDaysInTrade { get; set; } = 30;

        /// <summary>
        /// Enable detailed entry restriction logging
        /// </summary>
        [StrategyParameter("LogEntryRestrictions", false)]
        public virtual bool LogEntryRestrictions { get; set; }

        /// <summary>
        /// Minimum implied volatility for entry (options strategies)
        /// </summary>
        [StrategyParameter("MinImpliedVolatility", 0.15)]
        public decimal MinImpliedVolatility { get; set; } = 0.15m;

        // ============================================================================
        // SMARTPRICING EXECUTION PARAMETERS (Added for Task 12)
        // ============================================================================

        /// <summary>
        /// SmartPricing execution mode for improved fill rates on options spreads
        /// Supported values: "Normal", "Fast", "Patient", "Off"
        /// </summary>
        [StrategyParameter("SmartPricingMode", "Normal")]
        public string SmartPricingMode { get; set; } = "Normal";

        /// <summary>
        /// Maximum acceptable net spread width for combo orders before using market orders
        /// Prevents smart pricing on combos with excessive spread costs
        /// </summary>
        [StrategyParameter("ComboMaxNetSpreadWidth", 5.0)]
        public decimal ComboMaxNetSpreadWidth { get; set; } = 5.0m;

        /// <summary>
        /// SmartPricing mode specifically for combo orders (can be different from single-leg)
        /// If not specified, uses the main SmartPricingMode. Supported values: "Normal", "Fast", "Patient", "Off"
        /// </summary>
        [StrategyParameter("ComboSmartPricingMode", "")]
        public string ComboSmartPricingMode { get; set; } = "";

        /// <summary>
        /// Enable smart pricing for combo orders (multi-leg strategies)
        /// When false, combo orders use basic market execution regardless of SmartPricingMode
        /// </summary>
        [StrategyParameter("EnableComboSmartPricing", true)]
        public bool EnableComboSmartPricing { get; set; } = true;

        // ============================================================================
        // OPTIMIZATION PARAMETERS CONFIGURATION
        // ============================================================================

        /// <summary>
        /// Parameters to include in config.json for QuantConnect optimization
        /// These are the key parameters that should be exposed for backtesting optimization
        /// All other StrategyParameter attributes remain available for internal configuration
        /// </summary>
        /// <summary>
        /// Preferred underlying data resolution for strategies that need to downsample equities
        /// Defaults to Minute to preserve existing behaviour
        /// </summary>
        [StrategyParameter("DataResolution", "Minute")]
        public string UnderlyingResolution { get; set; } = "Minute";

        /// <summary>
        /// Parse the UnderlyingResolution string into Resolution enum (fallback Minute)
        /// </summary>
        public virtual Resolution GetUnderlyingResolution()
        {
            return UnderlyingResolution?.ToUpperInvariant() switch
            {
                "SECOND" => Resolution.Second,
                "MINUTE" => Resolution.Minute,
                "HOUR" => Resolution.Hour,
                "DAILY" => Resolution.Daily,
                _ => Resolution.Minute
            };
        }

        public virtual string[] OptimizationParameters => new[]
        {
            "Strategy",              // Always required for strategy selection
            "StartDate",             // Backtest period start
            "EndDate",               // Backtest period end  
            "AccountSize",           // Position sizing base
            "MaxPositions",          // Risk management - position count
            "ProfitTarget",          // Exit strategy - profit taking
            "StopLoss",              // Exit strategy - loss cutting
            "AllocationPerPosition"  // Position sizing per trade
        };

        /// <summary>
        /// Load parameters from QC's GetParameter() method using context pattern
        /// </summary>
        public virtual void LoadFromParameters(IAlgorithmContext context)
        {
            var properties = GetType().GetProperties();
            var loadedParams = new System.Collections.Generic.List<string>();
            
            foreach (var property in properties)
            {
                var attribute = property.GetCustomAttribute<StrategyParameterAttribute>();
                if (attribute != null)
                {
                    var parameterName = attribute.Name;
                    var defaultValue = property.GetValue(this)?.ToString() ?? attribute.DefaultValue?.ToString() ?? "";

                    var parameterValue = context.Algorithm.GetParameter(parameterName, defaultValue);

                    // Convert the parameter value to the property type
                    var convertedValue = ConvertParameterValue(parameterValue, property.PropertyType);
                    property.SetValue(this, convertedValue);

                    // Format parameter for logging
                    var formattedValue = convertedValue is string[] arrayValue 
                        ? $"[{string.Join(", ", arrayValue)}]"
                        : $"{convertedValue}";
                    loadedParams.Add($"{parameterName}={formattedValue}");
                }
            }
            
            // Single log output for all parameters
            if (loadedParams.Count > 0)
            {
                ((dynamic)context.Logger).Debug($"Loaded parameters: {string.Join(", ", loadedParams)}");
            }
        }

        /// <summary>
        /// Convert parameter value to the target type
        /// </summary>
        private object ConvertParameterValue(string value, Type targetType)
        {
            if (targetType == typeof(string))
                return value;
                
            if (targetType == typeof(int))
                return int.Parse(value);
                
            if (targetType == typeof(decimal))
                return decimal.Parse(value);
                
            if (targetType == typeof(bool))
                return bool.Parse(value);
                
            if (targetType == typeof(DateTime))
                return DateTime.Parse(value);
                
            if (targetType == typeof(TimeSpan))
                return TimeSpan.Parse(value);
                
            // Handle string arrays (for Symbols parameter)
            if (targetType == typeof(string[]))
            {
                // Handle various input formats that QuantConnect might send
                if (string.IsNullOrWhiteSpace(value))
                {
                    return new string[] { "SPY" }; // Default fallback
                }
                
                // Handle already-parsed arrays (QuantConnect might pass this way)
                if (value.StartsWith("System.String[]"))
                {
                    // QC passes arrays as "System.String[]" string - extract from property if available
                    return new string[] { "SPY" }; // Fallback, will be overridden by template logic
                }
                
                // Handle comma-separated string (our expected format)
                var result = value.Split(',').Select(s => s.Trim()).Where(s => !string.IsNullOrEmpty(s)).ToArray();
                return result.Length > 0 ? result : new string[] { "SPY" }; // Ensure we always have at least one symbol
            }
                
            // For other types, try generic conversion
            return Convert.ChangeType(value, targetType);
        }

        /// <summary>
        /// Validate the configuration
        /// </summary>
        public virtual string[] Validate()
        {
            var errors = new List<string>();
            
            // Generic validations that apply to all strategies
            if (AccountSize < 10000m)
                errors.Add($"Account size ${AccountSize:F0} too small (minimum $10,000)");
                
            if (AllocationPerPosition > 0.5m)
                errors.Add($"Allocation {AllocationPerPosition:P0} too high (maximum 50%)");
                
            if (MaxPositions <= 0)
                errors.Add($"MaxPositions must be greater than 0 (current: {MaxPositions})");
                
            // Skip validation if profit target is disabled (0 means disabled)
            if (ProfitTarget != 0 && ProfitTarget <= 0)
                errors.Add($"ProfitTarget must be positive (current: {ProfitTarget:P1}) or 0 to disable");
                
            // Skip validation if stop loss is disabled (0 means disabled)
            if (StopLoss != 0 && StopLoss >= 0)
                errors.Add($"StopLoss must be negative (current: {StopLoss:P1}) or 0 to disable");

            // Validate SmartPricing mode
            if (!CoreAlgo.Architecture.Core.Execution.SmartPricingEngineFactory.IsValidMode(SmartPricingMode))
                errors.Add($"Invalid SmartPricingMode '{SmartPricingMode}' (valid: Normal, Fast, Patient, Off)");

            // Entry window validation
            if (UseEntryTimeWindow)
            {
                if (EntryWindowStart > EntryWindowEnd)
                {
                    errors.Add($"EntryWindowStart {EntryWindowStart:hh\\:mm} must be <= EntryWindowEnd {EntryWindowEnd:hh\\:mm}");
                }
            }
            
            return errors.ToArray();
        }

        /// <summary>
        /// Gets a parameter value by name with a default fallback
        /// </summary>
        public object GetParameterValue(string parameterName, object defaultValue)
        {
            var property = GetType().GetProperty(parameterName);
            if (property != null)
            {
                return property.GetValue(this);
            }
            return defaultValue;
        }

        /// <summary>
        /// Creates a SmartPricing engine based on the current configuration
        /// </summary>
        /// <returns>Configured SmartPricing engine or null if disabled</returns>
        public CoreAlgo.Architecture.Core.Execution.ISmartPricingEngine CreateSmartPricingEngine()
        {
            var mode = CoreAlgo.Architecture.Core.Execution.SmartPricingEngineFactory.ParseMode(SmartPricingMode);
            
            if (mode == CoreAlgo.Architecture.Core.Execution.SmartPricingMode.Off)
                return null;

            return CoreAlgo.Architecture.Core.Execution.SmartPricingEngineFactory.Create(mode);
        }

    }
}
using System;
using System.Collections.Generic;
using System.Linq;
using QuantConnect;
using QuantConnect.Algorithm;
using QuantConnect.Securities;
using CoreAlgo.Architecture.Core.Interfaces;

namespace CoreAlgo.Architecture.Core.Services
{
    /// <summary>
    /// Cross-asset risk management system that extends QuantConnect's native risk capabilities.
    /// Provides portfolio-level risk aggregation, concentration limits, and multi-asset risk monitoring.
    /// 
    /// Design Philosophy: Extend QC's excellent risk framework rather than replace it.
    /// Integrates with QC's Portfolio, margin calculations, and position tracking.
    /// </summary>
    public class CoreAlgoRiskManager
    {
        private readonly IAlgorithmContext _context;
        
        // Risk thresholds - configurable via strategy parameters
        private readonly decimal _maxPortfolioMarginUtilization;
        private readonly decimal _maxAssetConcentration;
        private readonly decimal _maxCorrelatedAssetsAllocation;
        
        public CoreAlgoRiskManager(IAlgorithmContext context, 
            decimal maxMarginUtilization = 0.70m,
            decimal maxAssetConcentration = 0.30m, 
            decimal maxCorrelatedAllocation = 0.50m)
        {
            _context = context ?? throw new ArgumentNullException(nameof(context));
            _maxPortfolioMarginUtilization = maxMarginUtilization;
            _maxAssetConcentration = maxAssetConcentration;
            _maxCorrelatedAssetsAllocation = maxCorrelatedAllocation;
        }

        /// <summary>
        /// Validates if a new position can be safely added to the portfolio.
        /// Integrates with QC's native margin and position tracking.
        /// </summary>
        /// <param name="symbol">Symbol to validate</param>
        /// <param name="quantity">Proposed quantity</param>
        /// <param name="estimatedPrice">Estimated fill price</param>
        /// <returns>True if position is within risk limits</returns>
        public bool ValidateNewPosition(string symbol, decimal quantity, decimal estimatedPrice)
        {
            try
            {
                var algorithm = _context.Algorithm;
                var portfolio = algorithm.Portfolio;
                
                // 1. Check portfolio-level margin utilization (extends QC's native margin tracking)
                var currentMarginUtilization = GetPortfolioMarginUtilization();
                if (currentMarginUtilization > _maxPortfolioMarginUtilization)
                {
                    ((dynamic)_context.Logger).Warning($"Risk: Portfolio margin utilization too high: {currentMarginUtilization:P1} > {_maxPortfolioMarginUtilization:P1}");
                    return false;
                }

                // 2. Check asset concentration limits
                var proposedConcentration = CalculateAssetConcentrationAfterTrade(symbol, quantity, estimatedPrice);
                if (proposedConcentration > _maxAssetConcentration)
                {
                    ((dynamic)_context.Logger).Warning($"Risk: Asset concentration would exceed limit: {proposedConcentration:P1} > {_maxAssetConcentration:P1} for {symbol}");
                    return false;
                }

                // 3. Check if we have too many positions (basic diversification)
                var totalOptionPositions = portfolio.Values.Count(x => x.Invested && x.Symbol.SecurityType == SecurityType.Option);
                if (totalOptionPositions >= 20) // Reasonable upper limit for option strategies
                {
                    ((dynamic)_context.Logger).Warning($"Risk: Too many option positions: {totalOptionPositions} >= 20");
                    return false;
                }

                return true;
            }
            catch (Exception ex)
            {
                ((dynamic)_context.Logger).Error($"Risk validation error for {symbol}: {ex.Message}");
                return false; // Fail safe - reject if we can't validate
            }
        }

        /// <summary>
        /// Gets comprehensive portfolio risk metrics using QC's native data.
        /// </summary>
        public PortfolioRiskMetrics GetPortfolioRisk()
        {
            var algorithm = _context.Algorithm;
            var portfolio = algorithm.Portfolio;
            
            return new PortfolioRiskMetrics
            {
                // QC Native metrics
                TotalPortfolioValue = portfolio.TotalPortfolioValue,
                TotalMarginUsed = portfolio.TotalMarginUsed,
                MarginRemaining = portfolio.MarginRemaining,
                Cash = portfolio.Cash,
                TotalUnrealizedProfit = portfolio.TotalUnrealizedProfit,
                
                // CoreAlgo calculated metrics
                MarginUtilization = GetPortfolioMarginUtilization(),
                AssetConcentrations = GetAssetConcentrations(),
                OptionPositionCount = portfolio.Values.Count(x => x.Invested && x.Symbol.SecurityType == SecurityType.Option),
                LargestAssetExposure = GetLargestAssetExposure(),
                
                // Risk alerts
                RiskAlerts = GenerateRiskAlerts()
            };
        }

        /// <summary>
        /// Calculates portfolio margin utilization using QC's native margin tracking.
        /// </summary>
        private decimal GetPortfolioMarginUtilization()
        {
            var portfolio = _context.Algorithm.Portfolio;
            
            if (portfolio.TotalPortfolioValue <= 0)
                return 0;
                
            return portfolio.TotalMarginUsed / portfolio.TotalPortfolioValue;
        }

        /// <summary>
        /// Calculates concentration by underlying asset across all positions.
        /// </summary>
        private Dictionary<string, decimal> GetAssetConcentrations()
        {
            var portfolio = _context.Algorithm.Portfolio;
            var concentrations = new Dictionary<string, decimal>();
            var totalValue = portfolio.TotalPortfolioValue;
            
            if (totalValue <= 0) return concentrations;

            // Group by underlying asset for options
            var assetGroups = portfolio.Values
                .Where(x => x.Invested)
                .GroupBy(x => x.Symbol.SecurityType == SecurityType.Option ? 
                    x.Symbol.Underlying.Value : x.Symbol.Value);

            foreach (var group in assetGroups)
            {
                var assetValue = group.Sum(x => Math.Abs(x.HoldingsValue));
                var concentration = assetValue / totalValue;
                concentrations[group.Key] = concentration;
            }

            return concentrations;
        }

        /// <summary>
        /// Calculates what asset concentration would be after a proposed trade.
        /// </summary>
        private decimal CalculateAssetConcentrationAfterTrade(string symbol, decimal quantity, decimal price)
        {
            var concentrations = GetAssetConcentrations();
            var portfolio = _context.Algorithm.Portfolio;
            var totalValue = portfolio.TotalPortfolioValue;
            
            // Calculate additional value from proposed trade
            var additionalValue = Math.Abs(quantity * price);
            var newTotalValue = totalValue + additionalValue;
            
            // Get underlying symbol for options
            var underlyingSymbol = symbol; // Assume equity by default
            // TODO: Extract underlying symbol for options when we add option support
            
            var currentAssetValue = concentrations.ContainsKey(underlyingSymbol) 
                ? concentrations[underlyingSymbol] * totalValue 
                : 0;
                
            var newAssetValue = currentAssetValue + additionalValue;
            
            return newTotalValue > 0 ? newAssetValue / newTotalValue : 0;
        }

        /// <summary>
        /// Gets the largest single asset exposure as percentage of portfolio.
        /// </summary>
        private decimal GetLargestAssetExposure()
        {
            var concentrations = GetAssetConcentrations();
            return concentrations.Values.DefaultIfEmpty(0).Max();
        }

        /// <summary>
        /// Generates active risk alerts based on current portfolio state.
        /// </summary>
        private List<RiskAlert> GenerateRiskAlerts()
        {
            var alerts = new List<RiskAlert>();
            
            // Margin utilization alert
            var marginUtil = GetPortfolioMarginUtilization();
            if (marginUtil > _maxPortfolioMarginUtilization)
            {
                alerts.Add(new RiskAlert
                {
                    Type = RiskAlertType.MarginUtilization,
                    Severity = marginUtil > 0.85m ? RiskSeverity.High : RiskSeverity.Medium,
                    Message = $"Margin utilization: {marginUtil:P1} exceeds limit: {_maxPortfolioMarginUtilization:P1}",
                    Value = marginUtil
                });
            }

            // Asset concentration alerts
            var concentrations = GetAssetConcentrations();
            foreach (var kvp in concentrations.Where(x => x.Value > _maxAssetConcentration))
            {
                alerts.Add(new RiskAlert
                {
                    Type = RiskAlertType.AssetConcentration,
                    Severity = kvp.Value > 0.50m ? RiskSeverity.High : RiskSeverity.Medium,
                    Message = $"Asset {kvp.Key} concentration: {kvp.Value:P1} exceeds limit: {_maxAssetConcentration:P1}",
                    Symbol = kvp.Key,
                    Value = kvp.Value
                });
            }

            return alerts;
        }
    }

    /// <summary>
    /// Portfolio risk metrics combining QC native data with CoreAlgo calculations.
    /// </summary>
    public class PortfolioRiskMetrics
    {
        // QC Native Portfolio metrics
        public decimal TotalPortfolioValue { get; set; }
        public decimal TotalMarginUsed { get; set; }
        public decimal MarginRemaining { get; set; }
        public decimal Cash { get; set; }
        public decimal TotalUnrealizedProfit { get; set; }
        
        // CoreAlgo calculated risk metrics
        public decimal MarginUtilization { get; set; }
        public Dictionary<string, decimal> AssetConcentrations { get; set; } = new Dictionary<string, decimal>();
        public int OptionPositionCount { get; set; }
        public decimal LargestAssetExposure { get; set; }
        
        // Risk monitoring
        public List<RiskAlert> RiskAlerts { get; set; } = new List<RiskAlert>();
    }

    /// <summary>
    /// Risk alert for portfolio monitoring.
    /// </summary>
    public class RiskAlert
    {
        public RiskAlertType Type { get; set; }
        public RiskSeverity Severity { get; set; }
        public string Message { get; set; }
        public string Symbol { get; set; }
        public decimal Value { get; set; }
        public DateTime Timestamp { get; set; } = DateTime.UtcNow;
    }

    public enum RiskAlertType
    {
        MarginUtilization,
        AssetConcentration,
        PositionCount,
        CorrelationRisk
    }

    public enum RiskSeverity
    {
        Low,
        Medium,
        High,
        Critical
    }
}
using System;
using System.Collections.Generic;
using System.Linq;
using QuantConnect;
using QuantConnect.Securities;
using CoreAlgo.Architecture.Core.Interfaces;
using CoreAlgo.Architecture.Core.Implementations;

namespace CoreAlgo.Architecture.Core.Services
{
    /// <summary>
    /// TODO: INTEGRATE LATER - Advanced portfolio reporting for multi-asset strategies.
    /// 
    /// CURRENT STATUS: Built but not integrated with templates yet.
    /// REASON: Avoiding complexity in templates while building foundation for future reporting needs.
    /// 
    /// FUTURE INTEGRATION PLAN:
    /// 1. Add periodic portfolio reporting to Main.cs OnEndOfDay
    /// 2. Integrate with risk monitoring dashboard
    /// 3. Add performance analytics and attribution reporting
    /// 4. Connect with correlation analysis for risk attribution
    /// 
    /// This component extends QuantConnect's native portfolio metrics with multi-asset
    /// specific reporting, risk attribution, and performance analytics.
    /// </summary>
    public class PortfolioReporter
    {
        private readonly IAlgorithmContext _context;
        private readonly CoreAlgoRiskManager _riskManager;
        
        // TODO: FUTURE USE - Add correlation calculator when ready for integration
        // private readonly CorrelationCalculator _correlationCalculator;

        public PortfolioReporter(IAlgorithmContext context, CoreAlgoRiskManager riskManager)
        {
            _context = context ?? throw new ArgumentNullException(nameof(context));
            _riskManager = riskManager ?? throw new ArgumentNullException(nameof(riskManager));
        }

        /// <summary>
        /// TODO: FUTURE USE - Generates comprehensive portfolio risk report.
        /// Combines QC native metrics with CoreAlgo risk analysis.
        /// </summary>
        public PortfolioRiskReport GenerateRiskReport()
        {
            try
            {
                var portfolio = _context.Algorithm.Portfolio;
                var riskMetrics = _riskManager.GetPortfolioRisk();
                
                var report = new PortfolioRiskReport
                {
                    Timestamp = _context.Algorithm.Time,
                    
                    // Portfolio overview
                    PortfolioSummary = new PortfolioSummary
                    {
                        TotalValue = portfolio.TotalPortfolioValue,
                        Cash = portfolio.Cash,
                        InvestedCapital = portfolio.TotalHoldingsValue,
                        UnrealizedPnL = portfolio.TotalUnrealizedProfit,
                        RealizedPnL = portfolio.TotalProfit - portfolio.TotalUnrealizedProfit,
                        MarginUsed = portfolio.TotalMarginUsed,
                        MarginUtilization = riskMetrics.MarginUtilization
                    },
                    
                    // Asset breakdown
                    AssetBreakdown = GenerateAssetBreakdown(),
                    
                    // Risk metrics
                    RiskMetrics = riskMetrics,
                    
                    // Position summary
                    PositionSummary = GeneratePositionSummary(),
                    
                    // Performance metrics (TODO: Add more sophisticated metrics)
                    PerformanceMetrics = GeneratePerformanceMetrics()
                };

                ((dynamic)_context.Logger).Debug($"Portfolio risk report generated: {report.AssetBreakdown.Count} assets, {report.PositionSummary.TotalPositions} positions");
                
                return report;
            }
            catch (Exception ex)
            {
                ((dynamic)_context.Logger).Error($"Error generating portfolio risk report: {ex.Message}");
                return new PortfolioRiskReport { Timestamp = _context.Algorithm.Time };
            }
        }

        /// <summary>
        /// TODO: FUTURE USE - Gets key portfolio metrics for dashboard display.
        /// Designed for periodic monitoring and alert systems.
        /// </summary>
        public Dictionary<string, object> GetDashboardMetrics()
        {
            var portfolio = _context.Algorithm.Portfolio;
            var riskMetrics = _riskManager.GetPortfolioRisk();
            
            return new Dictionary<string, object>
            {
                // Key performance indicators
                ["TotalValue"] = portfolio.TotalPortfolioValue,
                ["DailyPnL"] = portfolio.TotalUnrealizedProfit, // TODO: Calculate actual daily P&L
                ["MarginUtilization"] = riskMetrics.MarginUtilization,
                ["CashBalance"] = portfolio.Cash,
                
                // Risk indicators
                ["ActivePositions"] = riskMetrics.OptionPositionCount,
                ["LargestExposure"] = riskMetrics.LargestAssetExposure,
                ["RiskAlerts"] = riskMetrics.RiskAlerts.Count,
                
                // Asset diversification
                ["UniqueAssets"] = riskMetrics.AssetConcentrations.Count,
                ["TopAssetConcentration"] = riskMetrics.AssetConcentrations.Values.DefaultIfEmpty(0).Max(),
                
                // Status indicators
                ["LastUpdate"] = _context.Algorithm.Time,
                ["PortfolioHealth"] = CalculatePortfolioHealthScore(riskMetrics)
            };
        }

        /// <summary>
        /// TODO: FUTURE USE - Generates asset-level breakdown of portfolio.
        /// Groups positions by underlying asset for multi-asset analysis.
        /// </summary>
        private List<AssetBreakdown> GenerateAssetBreakdown()
        {
            var portfolio = _context.Algorithm.Portfolio;
            var breakdown = new List<AssetBreakdown>();
            
            // Group positions by underlying asset
            var assetGroups = portfolio.Values
                .Where(x => x.Invested)
                .GroupBy(x => GetUnderlyingSymbol(x.Symbol));

            foreach (var group in assetGroups)
            {
                var positions = group.ToList();
                var totalValue = positions.Sum(x => x.HoldingsValue);
                var totalUnrealized = positions.Sum(x => x.UnrealizedProfit);
                
                breakdown.Add(new AssetBreakdown
                {
                    UnderlyingSymbol = group.Key,
                    PositionCount = positions.Count,
                    TotalValue = totalValue,
                    UnrealizedPnL = totalUnrealized,
                    Concentration = portfolio.TotalPortfolioValue > 0 ? 
                        Math.Abs(totalValue) / portfolio.TotalPortfolioValue : 0,
                    
                    // Position details
                    Positions = positions.Select(x => new PositionDetail
                    {
                        Symbol = x.Symbol.Value,
                        Quantity = x.Quantity,
                        AveragePrice = x.AveragePrice,
                        MarketPrice = x.Price,
                        HoldingsValue = x.HoldingsValue,
                        UnrealizedPnL = x.UnrealizedProfit,
                        SecurityType = x.Symbol.SecurityType
                    }).ToList()
                });
            }

            return breakdown.OrderByDescending(x => Math.Abs(x.TotalValue)).ToList();
        }

        /// <summary>
        /// TODO: FUTURE USE - Generates summary of all positions.
        /// </summary>
        private PositionSummary GeneratePositionSummary()
        {
            var portfolio = _context.Algorithm.Portfolio;
            var investedPositions = portfolio.Values.Where(x => x.Invested).ToList();
            
            return new PositionSummary
            {
                TotalPositions = investedPositions.Count,
                OptionPositions = investedPositions.Count(x => x.Symbol.SecurityType == SecurityType.Option),
                EquityPositions = investedPositions.Count(x => x.Symbol.SecurityType == SecurityType.Equity),
                
                ProfitablePositions = investedPositions.Count(x => x.UnrealizedProfit > 0),
                LosingPositions = investedPositions.Count(x => x.UnrealizedProfit < 0),
                
                LargestPosition = investedPositions.DefaultIfEmpty()
                    .OrderByDescending(x => Math.Abs(x?.HoldingsValue ?? 0))
                    .FirstOrDefault()?.Symbol.Value ?? "None",
                    
                LargestPositionValue = investedPositions.DefaultIfEmpty()
                    .Max(x => Math.Abs(x?.HoldingsValue ?? 0))
            };
        }

        /// <summary>
        /// TODO: FUTURE USE - Generates performance metrics.
        /// Placeholder for more sophisticated performance analytics.
        /// </summary>
        private PerformanceMetrics GeneratePerformanceMetrics()
        {
            var portfolio = _context.Algorithm.Portfolio;
            
            // TODO: Implement proper performance calculations
            // - Sharpe ratio, Sortino ratio, maximum drawdown
            // - Risk-adjusted returns, alpha, beta
            // - Asset-specific performance attribution
            
            return new PerformanceMetrics
            {
                TotalReturn = portfolio.TotalProfit,
                UnrealizedReturn = portfolio.TotalUnrealizedProfit,
                RealizedReturn = portfolio.TotalProfit - portfolio.TotalUnrealizedProfit,
                
                // Placeholder metrics - TODO: Implement proper calculations
                WinRate = CalculateWinRate(),
                AverageWin = 0, // TODO: Calculate from closed positions
                AverageLoss = 0, // TODO: Calculate from closed positions
                
                // TODO: Add time-weighted returns, risk metrics, benchmarking
            };
        }

        /// <summary>
        /// Calculates a simple portfolio health score (0-100).
        /// TODO: FUTURE USE - Enhance with more sophisticated scoring.
        /// </summary>
        private int CalculatePortfolioHealthScore(PortfolioRiskMetrics riskMetrics)
        {
            var score = 100;
            
            // Penalize high margin utilization
            if (riskMetrics.MarginUtilization > 0.8m) score -= 30;
            else if (riskMetrics.MarginUtilization > 0.6m) score -= 15;
            
            // Penalize high concentration
            if (riskMetrics.LargestAssetExposure > 0.5m) score -= 25;
            else if (riskMetrics.LargestAssetExposure > 0.3m) score -= 10;
            
            // Penalize too many risk alerts
            score -= riskMetrics.RiskAlerts.Count * 5;
            
            return Math.Max(0, Math.Min(100, score));
        }

        /// <summary>
        /// Gets the underlying symbol for an asset (handles options).
        /// </summary>
        private string GetUnderlyingSymbol(Symbol symbol)
        {
            return symbol.SecurityType == SecurityType.Option ? 
                symbol.Underlying.Value : symbol.Value;
        }

        /// <summary>
        /// Calculates win rate from current unrealized positions.
        /// TODO: Enhance to use actual trade history.
        /// </summary>
        private decimal CalculateWinRate()
        {
            var portfolio = _context.Algorithm.Portfolio;
            var investedPositions = portfolio.Values.Where(x => x.Invested).ToList();
            
            if (investedPositions.Count == 0) return 0;
            
            var winners = investedPositions.Count(x => x.UnrealizedProfit > 0);
            return (decimal)winners / investedPositions.Count;
        }

        /// <summary>
        /// Logs daily order position summary using the trade persistence service
        /// Simple report showing counts by state and sample positions
        /// </summary>
        public void LogDailyOrderPositions(TradePersistenceService persistenceService, int maxPositionsToLog = 10)
        {
            if (persistenceService == null) return;

            try
            {
                var summary = persistenceService.GetTradeSummary(null); // Will be called with tracker from Main.cs
                
                ((dynamic)_context.Logger).Info("=== DAILY ORDER POSITION REPORT ===");
                ((dynamic)_context.Logger).Info($"Total Trades: {summary.TotalTrades}");
                ((dynamic)_context.Logger).Info($"  Working (Pending): {summary.WorkingCount}");
                ((dynamic)_context.Logger).Info($"  Partial Fills: {summary.PartialFillCount}");
                ((dynamic)_context.Logger).Info($"  Open (Filled): {summary.OpenCount}");
                ((dynamic)_context.Logger).Info($"  Closed: {summary.ClosedCount}");
                ((dynamic)_context.Logger).Info($"  Cancelled: {summary.CancelledCount}");
                ((dynamic)_context.Logger).Info($"Total P&L (Closed): ${summary.TotalPnL:F2}");
                ((dynamic)_context.Logger).Info($"Report Time: {summary.AsOfUtc:yyyy-MM-dd HH:mm:ss} UTC");
                ((dynamic)_context.Logger).Info("=================================");
            }
            catch (Exception ex)
            {
                ((dynamic)_context.Logger).Error($"Error generating daily order position report: {ex.Message}");
            }
        }
        
        /// <summary>
        /// Logs daily order position summary with detailed position list
        /// </summary>
        public void LogDailyOrderPositionsDetailed(TradeTracker tracker, int maxPositionsToLog = 10)
        {
            if (tracker == null) return;

            try
            {
                ((dynamic)_context.Logger).Info("=== DAILY ORDER POSITION REPORT (DETAILED) ===");
                ((dynamic)_context.Logger).Info($"Total Trades: {tracker.AllTrades.Count}");
                ((dynamic)_context.Logger).Info($"  Working (Pending): {tracker.WorkingTrades.Count}");
                ((dynamic)_context.Logger).Info($"  Open (Filled): {tracker.OpenTrades.Count}");
                ((dynamic)_context.Logger).Info($"  Closed: {tracker.ClosedTrades.Count}");
                
                var partialFillCount = tracker.AllTrades.Count(t => t.Status == "PartialFill");
                var cancelledCount = tracker.AllTrades.Count(t => t.Status == "Cancelled");
                ((dynamic)_context.Logger).Info($"  Partial Fills: {partialFillCount}");
                ((dynamic)_context.Logger).Info($"  Cancelled: {cancelledCount}");
                
                var totalPnL = tracker.ClosedTrades.Sum(t => t.PnL);
                ((dynamic)_context.Logger).Info($"Total P&L (Closed): ${totalPnL:F2}");

                // Log sample of open positions
                if (tracker.OpenTrades.Count > 0)
                {
                    ((dynamic)_context.Logger).Info($"\nOpen Positions (showing up to {maxPositionsToLog}):");
                    foreach (var trade in tracker.OpenTrades.Take(maxPositionsToLog))
                    {
                        var symbolsDisplay = trade.SymbolIds.Count > 0 
                            ? string.Join(", ", trade.SymbolIds.Take(2)) + (trade.SymbolIds.Count > 2 ? "..." : "")
                            : trade.Symbol;
                        
                        ((dynamic)_context.Logger).Info($"  [{trade.Strategy}] {trade.OrderTag} | " +
                                                       $"Orders: {string.Join(",", trade.OrderIds)} | " +
                                                       $"Qty: {trade.FilledQuantity}/{trade.Quantity} | " +
                                                       $"Symbols: {symbolsDisplay}");
                    }
                }

                // Log sample of working positions
                if (tracker.WorkingTrades.Count > 0)
                {
                    ((dynamic)_context.Logger).Info($"\nWorking Orders (showing up to {Math.Min(5, maxPositionsToLog)}):");
                    foreach (var trade in tracker.WorkingTrades.Take(Math.Min(5, maxPositionsToLog)))
                    {
                        ((dynamic)_context.Logger).Info($"  [{trade.Strategy}] {trade.OrderTag} | Order: {trade.OrderId} | Status: {trade.Status}");
                    }
                }

                ((dynamic)_context.Logger).Info($"Report Time: {_context.Algorithm.Time:yyyy-MM-dd HH:mm:ss}");
                ((dynamic)_context.Logger).Info("=============================================");
            }
            catch (Exception ex)
            {
                ((dynamic)_context.Logger).Error($"Error generating detailed order position report: {ex.Message}");
            }
        }
    }

    #region Report Data Models

    /// <summary>
    /// TODO: FUTURE USE - Comprehensive portfolio risk report.
    /// </summary>
    public class PortfolioRiskReport
    {
        public DateTime Timestamp { get; set; }
        public PortfolioSummary PortfolioSummary { get; set; }
        public List<AssetBreakdown> AssetBreakdown { get; set; } = new List<AssetBreakdown>();
        public PortfolioRiskMetrics RiskMetrics { get; set; }
        public PositionSummary PositionSummary { get; set; }
        public PerformanceMetrics PerformanceMetrics { get; set; }
    }

    public class PortfolioSummary
    {
        public decimal TotalValue { get; set; }
        public decimal Cash { get; set; }
        public decimal InvestedCapital { get; set; }
        public decimal UnrealizedPnL { get; set; }
        public decimal RealizedPnL { get; set; }
        public decimal MarginUsed { get; set; }
        public decimal MarginUtilization { get; set; }
    }

    public class AssetBreakdown
    {
        public string UnderlyingSymbol { get; set; }
        public int PositionCount { get; set; }
        public decimal TotalValue { get; set; }
        public decimal UnrealizedPnL { get; set; }
        public decimal Concentration { get; set; }
        public List<PositionDetail> Positions { get; set; } = new List<PositionDetail>();
    }

    public class PositionDetail
    {
        public string Symbol { get; set; }
        public decimal Quantity { get; set; }
        public decimal AveragePrice { get; set; }
        public decimal MarketPrice { get; set; }
        public decimal HoldingsValue { get; set; }
        public decimal UnrealizedPnL { get; set; }
        public SecurityType SecurityType { get; set; }
    }

    public class PositionSummary
    {
        public int TotalPositions { get; set; }
        public int OptionPositions { get; set; }
        public int EquityPositions { get; set; }
        public int ProfitablePositions { get; set; }
        public int LosingPositions { get; set; }
        public string LargestPosition { get; set; }
        public decimal LargestPositionValue { get; set; }
    }

    public class PerformanceMetrics
    {
        public decimal TotalReturn { get; set; }
        public decimal UnrealizedReturn { get; set; }
        public decimal RealizedReturn { get; set; }
        public decimal WinRate { get; set; }
        public decimal AverageWin { get; set; }
        public decimal AverageLoss { get; set; }
    }

    #endregion
}
using System;
using System.Collections.Generic;
using System.Linq;
using QuantConnect;
using QuantConnect.Securities;
using CoreAlgo.Architecture.Core.Interfaces;
using CoreAlgo.Architecture.Core.Implementations;

namespace CoreAlgo.Architecture.Core.Services
{
    /// <summary>
    /// Manages position overlap detection and prevention across all strategies
    /// Leverages QuantConnect's native Portfolio for real-time position tracking
    /// </summary>
    public class PositionOverlapManager
    {
        private readonly IAlgorithmContext _context;
        private readonly List<IPositionOverlapRule> _rules;
        private readonly object _logger;

        public PositionOverlapManager(IAlgorithmContext context)
        {
            _context = context ?? throw new ArgumentNullException(nameof(context));
            _logger = context.Logger;
            _rules = new List<IPositionOverlapRule>();
            
            // Initialize with built-in rules
            InitializeBuiltInRules();
        }

        /// <summary>
        /// Validates whether a new combo order (multi-leg strategy) can be opened without creating dangerous overlaps
        /// </summary>
        /// <param name="legs">List of legs in the combo order</param>
        /// <param name="quantity">Combo order quantity</param>
        /// <param name="strategyTag">Strategy identifier for validation</param>
        /// <returns>ValidationResult indicating if combo order is allowed</returns>
        public ValidationResult ValidateComboOrder(List<QuantConnect.Orders.Leg> legs, int quantity, string strategyTag = "")
        {
            try
            {
                // For combo orders, validate the strategy as a whole unit rather than individual legs
                // This preserves ComboMarketOrder atomicity and prevents partial execution issues
                
                ((dynamic)_logger).Debug($"[COMBO VALIDATION] Validating combo order: {legs.Count} legs, qty:{quantity}, strategy:{strategyTag}");
                
                // Get current portfolio positions using QC's native Portfolio API
                var existingPositions = _context.Algorithm.Portfolio
                    .Where(p => p.Value.Invested)
                    .ToList();
                
                // Apply combo-specific validation logic based on strategy type
                return ValidateComboStrategy(legs, quantity, existingPositions, strategyTag);
            }
            catch (Exception ex)
            {
                var errorMsg = $"Error in combo order validation: {ex.Message}";
                ((dynamic)_logger).Error(errorMsg);
                return ValidationResult.Error(errorMsg);
            }
        }

        /// <summary>
        /// Validates whether a new position can be opened without creating dangerous overlaps
        /// </summary>
        /// <param name="symbol">Symbol to validate</param>
        /// <param name="quantity">Proposed quantity</param>
        /// <param name="strategyTag">Strategy identifier for logging</param>
        /// <returns>ValidationResult indicating if position is allowed</returns>
        public ValidationResult ValidateNewPosition(Symbol symbol, decimal quantity, string strategyTag = "")
        {
            try
            {
                // Get current portfolio positions using QC's native Portfolio API
                var existingPositions = _context.Algorithm.Portfolio
                    .Where(p => p.Value.Invested)
                    .ToList();

                ((dynamic)_logger).Debug($"[OVERLAP CHECK] Validating {symbol} qty:{quantity} strategy:{strategyTag}");
                ((dynamic)_logger).Debug($"[OVERLAP CHECK] Existing positions: {existingPositions.Count}");

                // Apply all registered rules
                foreach (var rule in _rules)
                {
                    var result = rule.Validate(symbol, quantity, existingPositions, strategyTag);
                    if (!result.IsValid)
                    {
                        ((dynamic)_logger).Warning($"[OVERLAP BLOCKED] {rule.GetType().Name}: {result.Message}");
                        return result;
                    }
                }

                ((dynamic)_logger).Debug($"[OVERLAP ALLOWED] Position validated successfully");
                return ValidationResult.Success();
            }
            catch (Exception ex)
            {
                var errorMsg = $"Error in position overlap validation: {ex.Message}";
                ((dynamic)_logger).Error(errorMsg);
                return ValidationResult.Error(errorMsg);
            }
        }

        /// <summary>
        /// Validates a combo strategy as an atomic unit using structural analysis (QC-First approach)
        /// </summary>
        private ValidationResult ValidateComboStrategy(List<QuantConnect.Orders.Leg> legs, int quantity, 
            List<KeyValuePair<Symbol, SecurityHolding>> existingPositions, string strategyTag)
        {
            // Analyze order structure instead of relying on strategy names
            var orderStructure = AnalyzeOrderStructure(legs);
            
            ((dynamic)_logger).Debug($"[COMBO STRATEGY] Analyzed structure: {orderStructure.LegCount} legs, {orderStructure.PutCount} puts, {orderStructure.CallCount} calls");
            
            // Use structural analysis for validation
            if (orderStructure.LegCount == 4 && orderStructure.PutCount == 2 && orderStructure.CallCount == 2)
            {
                // 4-leg combo with 2 puts + 2 calls = Iron Condor pattern
                return ValidateFourLegCombo(legs, quantity, existingPositions, orderStructure);
            }
            else if (orderStructure.LegCount == 2)
            {
                // 2-leg combo = Spread pattern
                return ValidateTwoLegCombo(legs, quantity, existingPositions, orderStructure);
            }
            else
            {
                // Other combo patterns - use conservative validation
                return ValidateGenericCombo(legs, quantity, existingPositions, orderStructure);
            }
        }
        
        /// <summary>
        /// Validates 4-leg combo orders (Iron Condor pattern) using configuration-driven limits
        /// </summary>
        private ValidationResult ValidateFourLegCombo(List<QuantConnect.Orders.Leg> legs, int quantity,
            List<KeyValuePair<Symbol, SecurityHolding>> existingPositions, OrderStructureAnalysis structure)
        {
            // Get the underlying from the first leg
            var underlying = legs.First().Symbol.Underlying;
            var expiry = legs.First().Symbol.ID.Date;
            
            // Count existing 4-leg combos on same underlying and expiry
            var activeFourLegCombos = existingPositions
                .Where(h => h.Key.SecurityType == SecurityType.Option &&
                           h.Key.Underlying == underlying &&
                           h.Key.ID.Date == expiry)
                .GroupBy(h => h.Key.ID.Date)
                .Where(g => g.Count() == 4) // 4-leg combo pattern
                .Count();
            
            // Get limit from strategy config (default to 1 for 4-leg combos if not specified)
            var maxComboPositions = GetComboPositionLimit(4); // 4-leg combos default to strict limit
            
            if (activeFourLegCombos >= maxComboPositions)
            {
                return ValidationResult.Blocked(
                    $"4-leg combo limit exceeded: {activeFourLegCombos} active positions on {underlying.Value} {expiry:yyyy-MM-dd} (max: {maxComboPositions})");
            }
            
            ((dynamic)_logger).Debug($"[4-LEG COMBO] Validation passed: {activeFourLegCombos}/{maxComboPositions} positions");
            return ValidationResult.Success();
        }
        
        /// <summary>
        /// Validates 2-leg combo orders (Spread pattern) using configuration-driven limits
        /// </summary>
        private ValidationResult ValidateTwoLegCombo(List<QuantConnect.Orders.Leg> legs, int quantity,
            List<KeyValuePair<Symbol, SecurityHolding>> existingPositions, OrderStructureAnalysis structure)
        {
            var underlying = legs.First().Symbol.Underlying;
            
            // Count existing 2-leg combos on same underlying
            var activeTwoLegCombos = existingPositions
                .Where(h => h.Key.SecurityType == SecurityType.Option &&
                           h.Key.Underlying == underlying)
                .GroupBy(h => h.Key.ID.Date)
                .Where(g => g.Count() == 2) // 2-leg combo pattern
                .Count();
                
            // Get limit from strategy config (more permissive for 2-leg combos)
            var maxComboPositions = GetComboPositionLimit(2);
                
            if (activeTwoLegCombos >= maxComboPositions)
            {
                return ValidationResult.Blocked(
                    $"2-leg combo limit exceeded: {activeTwoLegCombos} active positions on {underlying.Value} (max: {maxComboPositions})");
            }
            
            ((dynamic)_logger).Debug($"[2-LEG COMBO] Validation passed: {activeTwoLegCombos}/{maxComboPositions} positions");
            return ValidationResult.Success();
        }
        
        /// <summary>
        /// Validates generic combo orders with conservative portfolio-level limits
        /// </summary>
        private ValidationResult ValidateGenericCombo(List<QuantConnect.Orders.Leg> legs, int quantity,
            List<KeyValuePair<Symbol, SecurityHolding>> existingPositions, OrderStructureAnalysis structure)
        {
            // Conservative validation for unknown combo patterns
            // Focus on portfolio-level risk management
            
            var underlying = legs.First().Symbol.Underlying;
            var activeComboPositions = existingPositions
                .Where(h => h.Key.SecurityType == SecurityType.Option &&
                           h.Key.Underlying == underlying)
                .GroupBy(h => h.Key.ID.Date)
                .Where(g => g.Count() >= structure.LegCount)
                .Count();
                
            // Use configuration-driven limit with conservative fallback
            var maxComboPositions = GetComboPositionLimit(structure.LegCount);
                
            if (activeComboPositions >= maxComboPositions)
            {
                return ValidationResult.Blocked(
                    $"{structure.LegCount}-leg combo limit exceeded: {activeComboPositions} active positions on {underlying.Value} (max: {maxComboPositions})");
            }
            
            ((dynamic)_logger).Debug($"[GENERIC COMBO] Validation passed: {activeComboPositions}/{maxComboPositions} positions");
            return ValidationResult.Success();
        }
        
        /// <summary>
        /// Analyzes combo order structure without relying on strategy names
        /// </summary>
        private OrderStructureAnalysis AnalyzeOrderStructure(List<QuantConnect.Orders.Leg> legs)
        {
            var analysis = new OrderStructureAnalysis
            {
                LegCount = legs.Count,
                PutCount = legs.Count(l => l.Symbol.SecurityType == SecurityType.Option && l.Symbol.ID.OptionRight == OptionRight.Put),
                CallCount = legs.Count(l => l.Symbol.SecurityType == SecurityType.Option && l.Symbol.ID.OptionRight == OptionRight.Call),
                EquityCount = legs.Count(l => l.Symbol.SecurityType == SecurityType.Equity),
                HasOptions = legs.Any(l => l.Symbol.SecurityType == SecurityType.Option),
                HasEquity = legs.Any(l => l.Symbol.SecurityType == SecurityType.Equity)
            };
            
            // Analyze strike relationships for pattern detection
            if (analysis.HasOptions)
            {
                var strikes = legs.Where(l => l.Symbol.SecurityType == SecurityType.Option)
                                 .Select(l => l.Symbol.ID.StrikePrice)
                                 .OrderBy(s => s)
                                 .ToList();
                                 
                analysis.UniqueStrikes = strikes.Distinct().Count();
                analysis.StrikeSpread = strikes.Count > 1 ? strikes.Max() - strikes.Min() : 0;
            }
            
            return analysis;
        }
        
        /// <summary>
        /// Gets combo position limit from strategy configuration
        /// </summary>
        private int GetComboPositionLimit(int legCount)
        {
            try
            {
                // Try to get MaxPositionsPerCombo from current strategy configuration
                var algorithm = _context.Algorithm;
                if (algorithm != null)
                {
                    // Access strategy config through algorithm if available
                    var maxComboPositions = algorithm.GetParameter("MaxPositionsPerCombo");
                    if (int.TryParse(maxComboPositions, out var configLimit))
                    {
                        return configLimit;
                    }
                }
            }
            catch (Exception ex)
            {
                ((dynamic)_logger).Debug($"[CONFIG] Could not read MaxPositionsPerCombo: {ex.Message}");
            }
            
            // Fallback limits based on leg count complexity
            return legCount switch
            {
                4 => 1,  // 4-leg combos (Iron Condor pattern) - most restrictive
                3 => 2,  // 3-leg combos - moderate
                2 => 3,  // 2-leg combos (Spreads) - more permissive
                _ => 2   // Other patterns - moderate default
            };
        }

        /// <summary>
        /// Structure analysis result for combo orders
        /// </summary>
        private class OrderStructureAnalysis
        {
            public int LegCount { get; set; }
            public int PutCount { get; set; }
            public int CallCount { get; set; }
            public int EquityCount { get; set; }
            public bool HasOptions { get; set; }
            public bool HasEquity { get; set; }
            public int UniqueStrikes { get; set; }
            public decimal StrikeSpread { get; set; }
        }
        
        /// <summary>
        /// Adds a custom overlap rule to the validation pipeline
        /// </summary>
        public void AddRule(IPositionOverlapRule rule)
        {
            if (rule == null) throw new ArgumentNullException(nameof(rule));
            _rules.Add(rule);
            ((dynamic)_logger).Debug($"[OVERLAP MANAGER] Added rule: {rule.GetType().Name}");
        }

        /// <summary>
        /// Removes a rule from the validation pipeline
        /// </summary>
        public void RemoveRule<T>() where T : IPositionOverlapRule
        {
            var removed = _rules.RemoveAll(r => r is T);
            if (removed > 0)
            {
                ((dynamic)_logger).Debug($"[OVERLAP MANAGER] Removed {removed} rule(s) of type: {typeof(T).Name}");
            }
        }

        /// <summary>
        /// Gets summary of current overlap prevention configuration
        /// </summary>
        public string GetConfigurationSummary()
        {
            var summary = $"Position Overlap Manager - {_rules.Count} active rules:\n";
            foreach (var rule in _rules)
            {
                summary += $"  - {rule.GetType().Name}\n";
            }
            return summary;
        }

        /// <summary>
        /// Initialize built-in overlap prevention rules
        /// </summary>
        private void InitializeBuiltInRules()
        {
            // Add core overlap prevention rules
            AddRule(new UnderlyingConflictRule(_context));
            AddRule(new CollateralValidationRule(_context));
            AddRule(new StrikeOverlapRule(_context));
            
            ((dynamic)_logger).Info($"[OVERLAP MANAGER] Initialized with {_rules.Count} built-in rules");
        }

        /// <summary>
        /// Gets all positions for a specific underlying symbol
        /// </summary>
        public List<SecurityHolding> GetPositionsForUnderlying(Symbol underlying)
        {
            return _context.Algorithm.Portfolio.Values
                .Where(h => h.Invested && 
                           (h.Symbol == underlying || 
                            (h.Symbol.SecurityType == SecurityType.Option && h.Symbol.Underlying == underlying)))
                .ToList();
        }

        /// <summary>
        /// Checks if there are any active positions for a specific underlying
        /// </summary>
        public bool HasActivePositions(Symbol underlying)
        {
            return GetPositionsForUnderlying(underlying).Any();
        }

        /// <summary>
        /// Gets count of active option positions for an underlying
        /// </summary>
        public int GetActiveOptionPositionCount(Symbol underlying)
        {
            return _context.Algorithm.Portfolio.Values
                .Count(h => h.Invested && 
                           h.Symbol.SecurityType == SecurityType.Option &&
                           h.Symbol.Underlying == underlying);
        }

        /// <summary>
        /// Calculates total margin requirement for all positions on an underlying
        /// </summary>
        public decimal GetTotalMarginRequirement(Symbol underlying)
        {
            try
            {
                var positions = GetPositionsForUnderlying(underlying);
                return positions.Sum(p => Math.Abs(p.HoldingsValue * 0.2m)); // Simplified margin calculation
            }
            catch (Exception ex)
            {
                ((dynamic)_logger).Warning($"Error calculating margin requirement: {ex.Message}");
                return 0m;
            }
        }
    }
}
using System;
using QuantConnect;
using QuantConnect.Algorithm;
using CoreAlgo.Architecture.Core.Interfaces;

namespace CoreAlgo.Architecture.Core.Services
{
    /// <summary>
    /// Simple implementation of IAlgorithmContext for SmartOrderManager
    /// </summary>
    public class SimpleAlgorithmContext : IAlgorithmContext
    {
        public QCAlgorithm Algorithm { get; }
        public object Logger { get; }

        public SimpleAlgorithmContext(QCAlgorithm algorithm, object logger)
        {
            Algorithm = algorithm ?? throw new ArgumentNullException(nameof(algorithm));
            Logger = logger ?? throw new ArgumentNullException(nameof(logger));
        }
    }
}
using System;
using System.Collections.Generic;
using System.Linq;
using QuantConnect;
using QuantConnect.Algorithm;
using QuantConnect.Orders;
using QuantConnect.Scheduling;
using QuantConnect.Securities;
using CoreAlgo.Architecture.Core.Execution;
using CoreAlgo.Architecture.Core.Interfaces;
using CoreAlgo.Architecture.QC.Helpers;

namespace CoreAlgo.Architecture.Core.Services
{
    /// <summary>
    /// Manages smart orders with progressive pricing without requiring Algorithm Framework
    /// Intercepts order placement and applies progressive limit pricing to improve fill rates
    /// </summary>
    public class SmartOrderManager
    {
        private readonly QCAlgorithm _algorithm;
        private readonly IAlgorithmContext _context;
        private readonly Dictionary<int, SmartOrderTracker> _activeOrders;
        private readonly Dictionary<int, ComboOrderTracker> _activeComboOrders;
        private readonly HashSet<ScheduledEvent> _scheduledEvents;
        private ISmartPricingEngine _pricingEngine;
        private PositionOverlapManager _overlapManager;
        
        public SmartOrderManager(QCAlgorithm algorithm, IAlgorithmContext context)
        {
            _algorithm = algorithm ?? throw new ArgumentNullException(nameof(algorithm));
            _context = context ?? throw new ArgumentNullException(nameof(context));
            _activeOrders = new Dictionary<int, SmartOrderTracker>();
            _activeComboOrders = new Dictionary<int, ComboOrderTracker>();
            _scheduledEvents = new HashSet<ScheduledEvent>();
        }

        /// <summary>
        /// Sets the pricing engine for smart order execution
        /// </summary>
        public void SetPricingEngine(ISmartPricingEngine pricingEngine)
        {
            _pricingEngine = pricingEngine;
        }

        /// <summary>
        /// Sets the position overlap manager for order validation
        /// </summary>
        public void SetOverlapManager(PositionOverlapManager overlapManager)
        {
            _overlapManager = overlapManager;
        }

        /// <summary>
        /// Places a smart market order that starts as a limit order at mid-spread
        /// and progressively moves toward market price
        /// </summary>
        public OrderTicket SmartMarketOrder(Symbol symbol, decimal quantity, string tag = "")
        {
            // Validate position overlap before placing order
            if (_overlapManager != null)
            {
                var validation = _overlapManager.ValidateNewPosition(symbol, quantity, tag);
                if (!validation.IsValid)
                {
                    ((dynamic)_context.Logger).Warning($"[OVERLAP PREVENTION] Order blocked: {validation.Message}");
                    
                    // Return null ticket to indicate order was blocked
                    // Strategies should check for null returns and handle appropriately
                    return null;
                }
            }

            if (_pricingEngine == null)
            {
                // Fall back to regular market order if no pricing engine
                return _algorithm.MarketOrder(symbol, quantity, tag: tag);
            }

            try
            {
                var security = _algorithm.Securities[symbol];
                var quote = GetCurrentQuote(security);
                
                if (quote == null || quote.Spread > 10m) // Skip if spread too wide
                {
                    ((dynamic)_context.Logger).Debug($"SmartOrder: Using market order for {symbol} due to wide spread or no quote");
                    return _algorithm.MarketOrder(symbol, quantity, tag: tag);
                }

                // Calculate initial limit price at mid-spread
                var direction = quantity > 0 ? CoreAlgo.Architecture.Core.Execution.OrderDirection.Buy : CoreAlgo.Architecture.Core.Execution.OrderDirection.Sell;
                var initialPrice = _pricingEngine.CalculateInitialPrice(quote, direction);

                var roundedInitialPrice = PriceRounding.RoundLimitPrice(_algorithm.Securities, symbol, quantity, initialPrice);
                
                // Place initial limit order
                var ticket = _algorithm.LimitOrder(symbol, quantity, roundedInitialPrice, tag: tag + " [Smart]");
                
                if (ticket == null || ticket.Status == OrderStatus.Invalid)
                {
                    ((dynamic)_context.Logger).Error($"SmartOrder: Failed to place initial limit order for {symbol}");
                    return _algorithm.MarketOrder(symbol, quantity, tag: tag);
                }

                // Create tracker for progressive pricing
                var tracker = new SmartOrderTracker(ticket, quote, direction, SmartPricingMode.Normal, roundedInitialPrice);

                _activeOrders[ticket.OrderId] = tracker;
                
                ((dynamic)_context.Logger).Info($"SmartOrder: Placed initial limit order for {symbol} at ${initialPrice:F2} " +
                                              $"(Mid: ${quote.Price:F2}, Spread: ${quote.Spread:F2})");

                // Schedule first price update
                ScheduleNextPricingUpdate(tracker);
                
                return ticket;
            }
            catch (Exception ex)
            {
                ((dynamic)_context.Logger).Error($"SmartOrder error: {ex.Message}");
                // Fall back to market order on any error
                return _algorithm.MarketOrder(symbol, quantity, tag: tag);
            }
        }

        /// <summary>
        /// Places a smart combo limit order for multi-leg options with progressive net pricing
        /// Uses QC's native ComboLimitOrder with intelligent net price calculation and updates
        /// </summary>
        public List<OrderTicket> SmartComboMarketOrder(List<Leg> legs, int quantity, string tag = "")
        {
            // Validate combo order as atomic unit (QC-First approach)
            if (_overlapManager != null)
            {
                var validation = _overlapManager.ValidateComboOrder(legs, quantity, tag);
                if (!validation.IsValid)
                {
                    ((dynamic)_context.Logger).Warning($"[COMBO ORDER BLOCKED] {validation.Message}");
                    return new List<OrderTicket>(); // Return empty list to indicate order was blocked
                }
                
                ((dynamic)_context.Logger).Debug($"[COMBO ORDER APPROVED] {legs.Count}-leg order validated for {tag}");
            }

            // If no pricing engine, fall back to basic combo market order
            if (_pricingEngine == null || _pricingEngine.Mode == SmartPricingMode.Off)
            {
                ((dynamic)_context.Logger).Debug($"SmartCombo: Using basic combo market order (no smart pricing)");
                return _algorithm.ComboMarketOrder(legs, quantity, tag: tag);
            }

            try
            {
                // Get current market quotes for the combo
                var comboQuote = ComboQuote.FromSecurities(legs, _algorithm.Securities);
                if (comboQuote == null)
                {
                    ((dynamic)_context.Logger).Warning($"SmartCombo: No valid quotes available for combo, using market order");
                    return _algorithm.ComboMarketOrder(legs, quantity, tag: tag);
                }

                // Determine combo direction (buy = net debit, sell = net credit)
                var comboDirection = ComboPricingEngine.DetermineComboDirection(legs);

                // Check if we should attempt smart pricing
                if (!_pricingEngine.ShouldAttemptComboPricing(comboQuote, comboDirection))
                {
                    ((dynamic)_context.Logger).Debug($"SmartCombo: Conditions not suitable for smart pricing, using market order");
                    return _algorithm.ComboMarketOrder(legs, quantity, tag: tag);
                }

                // Calculate initial net limit price
                var initialNetPrice = _pricingEngine.CalculateInitialComboPrice(legs, comboQuote, comboDirection);
                if (!initialNetPrice.HasValue)
                {
                    ((dynamic)_context.Logger).Debug($"SmartCombo: Could not calculate initial price, using market order");
                    return _algorithm.ComboMarketOrder(legs, quantity, tag: tag);
                }

                // Place initial combo limit order with calculated net price
                var comboTickets = _algorithm.ComboLimitOrder(legs, quantity, initialNetPrice.Value, tag: tag + " [SmartCombo]");
                
                if (comboTickets == null || comboTickets.Count == 0)
                {
                    ((dynamic)_context.Logger).Error($"SmartCombo: Failed to place combo limit order, trying market order fallback");
                    return _algorithm.ComboMarketOrder(legs, quantity, tag: tag);
                }

                // Create combo tracker for progressive pricing
                var roundedInitialNetPrice = PriceRounding.RoundComboNetPrice(
                    _algorithm.Securities,
                    legs,
                    initialNetPrice.Value,
                    comboDirection == CoreAlgo.Architecture.Core.Execution.OrderDirection.Buy);

                // Update initial ticket to rounded net price if it differs
                if (Math.Abs(roundedInitialNetPrice - initialNetPrice.Value) > 0m)
                {
                    foreach (var comboTicket in comboTickets)
                    {
                        comboTicket.Update(new UpdateOrderFields { LimitPrice = roundedInitialNetPrice });
                    }
                }

                var comboTracker = new ComboOrderTracker(comboTickets, legs, comboQuote, 
                    comboDirection, _pricingEngine.Mode, roundedInitialNetPrice);

                // Track using primary order ID
                _activeComboOrders[comboTracker.PrimaryOrderId] = comboTracker;

                ((dynamic)_context.Logger).Info($"SmartCombo: Placed {legs.Count}-leg combo limit order " +
                                              $"at net price ${roundedInitialNetPrice:F2} " +
                                              $"(NetBid: ${comboQuote.NetBid:F2}, NetAsk: ${comboQuote.NetAsk:F2}, " +
                                              $"NetMid: ${comboQuote.NetMid:F2}, Direction: {comboDirection})");

                // Schedule first pricing update
                ScheduleNextComboPricingUpdate(comboTracker);

                return comboTickets;
            }
            catch (Exception ex)
            {
                ((dynamic)_context.Logger).Error($"SmartCombo error: {ex.Message}");
                // Fall back to basic combo market order on any error
                return _algorithm.ComboMarketOrder(legs, quantity, tag: tag);
            }
        }

        /// <summary>
        /// Handles order events to track fills and update order state
        /// </summary>
        public void OnOrderEvent(OrderEvent orderEvent)
        {
            // Handle single-leg orders
            if (_activeOrders.TryGetValue(orderEvent.OrderId, out var tracker))
            {
                switch (orderEvent.Status)
                {
                    case OrderStatus.Filled:
                        ((dynamic)_context.Logger).Info($"SmartOrder: Order {orderEvent.OrderId} filled at ${orderEvent.FillPrice:F2} " +
                                                      $"after {tracker.AttemptNumber} attempts");
                        CleanupOrder(tracker);
                        break;

                    case OrderStatus.PartiallyFilled:
                        ((dynamic)_context.Logger).Debug($"SmartOrder: Order {orderEvent.OrderId} partially filled " +
                                                       $"({orderEvent.FillQuantity}/{tracker.OrderTicket.Quantity})");
                        tracker.UpdatePartialFill(orderEvent);
                        break;

                    case OrderStatus.Canceled:
                    case OrderStatus.Invalid:
                        ((dynamic)_context.Logger).Warning($"SmartOrder: Order {orderEvent.OrderId} {orderEvent.Status}");
                        CleanupOrder(tracker);
                        break;
                }
                return;
            }

            // Handle combo orders - check if this order event belongs to any tracked combo
            foreach (var comboTracker in _activeComboOrders.Values)
            {
                var matchingTicket = comboTracker.ComboTickets.FirstOrDefault(t => t.OrderId == orderEvent.OrderId);
                if (matchingTicket != null)
                {
                    HandleComboOrderEvent(comboTracker, orderEvent, matchingTicket);
                    return;
                }
            }
        }

        /// <summary>
        /// Handles order events for combo orders
        /// </summary>
        private void HandleComboOrderEvent(ComboOrderTracker comboTracker, OrderEvent orderEvent, OrderTicket matchingTicket)
        {
            switch (orderEvent.Status)
            {
                case OrderStatus.Filled:
                    ((dynamic)_context.Logger).Debug($"SmartCombo: Leg {orderEvent.OrderId} of combo {comboTracker.PrimaryOrderId} " +
                                                   $"filled at ${orderEvent.FillPrice:F2}");
                    
                    // Check if entire combo is now filled
                    if (comboTracker.IsCompletelyFilled)
                    {
                        ((dynamic)_context.Logger).Info($"SmartCombo: Combo order {comboTracker.PrimaryOrderId} completely filled " +
                                                      $"after {comboTracker.AttemptNumber} pricing attempts");
                        CleanupComboOrder(comboTracker);
                    }
                    break;

                case OrderStatus.PartiallyFilled:
                    ((dynamic)_context.Logger).Debug($"SmartCombo: Leg {orderEvent.OrderId} of combo {comboTracker.PrimaryOrderId} " +
                                                   $"partially filled ({orderEvent.FillQuantity}/{matchingTicket.Quantity})");
                    comboTracker.UpdatePartialFill(orderEvent);
                    break;

                case OrderStatus.Canceled:
                case OrderStatus.Invalid:
                    ((dynamic)_context.Logger).Warning($"SmartCombo: Leg {orderEvent.OrderId} of combo {comboTracker.PrimaryOrderId} {orderEvent.Status}");
                    
                    // If any leg fails, the entire combo fails
                    ((dynamic)_context.Logger).Warning($"SmartCombo: Combo order {comboTracker.PrimaryOrderId} failed due to leg {orderEvent.OrderId}");
                    CleanupComboOrder(comboTracker);
                    break;
            }
        }

        /// <summary>
        /// Updates the price of an active order using progressive pricing
        /// </summary>
        private void UpdateOrderPrice(SmartOrderTracker tracker)
        {
            try
            {
                // Remove the scheduled event
                if (tracker.ScheduledEvent != null)
                {
                    _scheduledEvents.Remove(tracker.ScheduledEvent);
                    tracker.ScheduledEvent = null;
                }

                // Check if order is still active
                if (!_activeOrders.ContainsKey(tracker.OrderTicket.OrderId) || 
                    tracker.OrderTicket.Status == OrderStatus.Filled)
                {
                    return;
                }

                // Check if we've exceeded max attempts
                if (tracker.AttemptNumber >= _pricingEngine.GetMaxAttempts())
                {
                    ((dynamic)_context.Logger).Info($"SmartOrder: Max attempts reached for order {tracker.OrderTicket.OrderId}, " +
                                                  "converting to market order");
                    
                    // Cancel limit order and place market order
                    tracker.OrderTicket.Cancel("Max pricing attempts reached");
                    
                    // Place market order for remaining quantity
                    var remainingQty = tracker.OrderTicket.Quantity - tracker.OrderTicket.QuantityFilled;
                    if (remainingQty != 0)
                    {
                        _algorithm.MarketOrder(tracker.OrderTicket.Symbol, remainingQty, 
                            tag: tracker.OrderTicket.Tag + " [Smart-Market]");
                    }
                    
                    CleanupOrder(tracker);
                    return;
                }

                // Get current market quote
                var security = _algorithm.Securities[tracker.OrderTicket.Symbol];
                var currentQuote = GetCurrentQuote(security);
                
                if (currentQuote == null)
                {
                    ((dynamic)_context.Logger).Warning($"SmartOrder: No quote available for {tracker.OrderTicket.Symbol}");
                    // Keep the order but don't update - try again next time
                    ScheduleNextPricingUpdate(tracker);
                    return;
                }

                // Calculate next price
                tracker.AttemptNumber++;
                var nextPrice = _pricingEngine.CalculateNextPrice(
                    tracker.CurrentPrice, currentQuote, tracker.OrderDirection, tracker.AttemptNumber);

                if (nextPrice.HasValue && Math.Abs(nextPrice.Value - tracker.CurrentPrice) > 0.01m)
                {
                    var roundedNextPrice = PriceRounding.RoundLimitPrice(
                        _algorithm.Securities,
                        tracker.OrderTicket.Symbol,
                        tracker.OrderTicket.Quantity,
                        nextPrice.Value);

                    // Update order price
                    var updateFields = new UpdateOrderFields { LimitPrice = roundedNextPrice };
                    var response = tracker.OrderTicket.Update(updateFields);
                    
                    if (response.IsSuccess)
                    {
                        tracker.CurrentPrice = roundedNextPrice;
                        ((dynamic)_context.Logger).Debug($"SmartOrder: Updated order {tracker.OrderTicket.OrderId} " +
                                                       $"price to ${roundedNextPrice:F2} (attempt {tracker.AttemptNumber})");
                    }
                    else
                    {
                        ((dynamic)_context.Logger).Warning($"SmartOrder: Failed to update order price: {response.ErrorMessage}");
                    }
                }

                // Schedule next update
                ScheduleNextPricingUpdate(tracker);
            }
            catch (Exception ex)
            {
                ((dynamic)_context.Logger).Error($"SmartOrder update error: {ex.Message}");
                CleanupOrder(tracker);
            }
        }

        /// <summary>
        /// Schedules the next pricing update for an order
        /// </summary>
        private void ScheduleNextPricingUpdate(SmartOrderTracker tracker)
        {
            var interval = _pricingEngine.GetPricingInterval();
            var updateTime = _algorithm.Time.Add(interval);

            var scheduledEvent = _algorithm.Schedule.On(
                _algorithm.DateRules.On(updateTime.Date),
                _algorithm.TimeRules.At(updateTime.Hour, updateTime.Minute, updateTime.Second),
                () => UpdateOrderPrice(tracker));

            _scheduledEvents.Add(scheduledEvent);
            tracker.ScheduledEvent = scheduledEvent;
        }

        /// <summary>
        /// Cleans up an order and removes associated scheduled events
        /// </summary>
        private void CleanupOrder(SmartOrderTracker tracker)
        {
            _activeOrders.Remove(tracker.OrderTicket.OrderId);
            
            if (tracker.ScheduledEvent != null)
            {
                _algorithm.Schedule.Remove(tracker.ScheduledEvent);
                _scheduledEvents.Remove(tracker.ScheduledEvent);
            }
        }

        /// <summary>
        /// Gets the current quote for a security
        /// </summary>
        private Quote GetCurrentQuote(Security security)
        {
            if (security.BidPrice == 0 || security.AskPrice == 0)
                return null;

            return new Quote(security.BidPrice, security.AskPrice);
        }

        /// <summary>
        /// Schedules the next pricing update for a combo order
        /// </summary>
        private void ScheduleNextComboPricingUpdate(ComboOrderTracker tracker)
        {
            var interval = _pricingEngine.GetPricingInterval();
            var updateTime = _algorithm.Time.Add(interval);

            var scheduledEvent = _algorithm.Schedule.On(_algorithm.DateRules.On(updateTime.Date),
                _algorithm.TimeRules.At(updateTime.Hour, updateTime.Minute, updateTime.Second),
                () => UpdateComboOrderPrice(tracker));

            _scheduledEvents.Add(scheduledEvent);
            tracker.ScheduledEvent = scheduledEvent;
        }

        /// <summary>
        /// Updates the net price of an active combo order using progressive pricing
        /// </summary>
        private void UpdateComboOrderPrice(ComboOrderTracker tracker)
        {
            try
            {
                // Remove the scheduled event
                if (tracker.ScheduledEvent != null)
                {
                    _scheduledEvents.Remove(tracker.ScheduledEvent);
                    tracker.ScheduledEvent = null;
                }

                // Check if combo is still active
                if (!_activeComboOrders.ContainsKey(tracker.PrimaryOrderId) || tracker.IsCompletelyFilled)
                {
                    return;
                }

                // Check if we should continue pricing
                var maxAttempts = _pricingEngine.GetMaxAttempts();
                var maxRuntime = TimeSpan.FromMinutes(5); // Max 5 minutes for combo orders

                if (!tracker.ShouldContinuePricing(maxAttempts, maxRuntime))
                {
                    ((dynamic)_context.Logger).Debug($"SmartCombo: Stopping progressive pricing for combo {tracker.PrimaryOrderId} " +
                                                   $"(attempts: {tracker.AttemptNumber}/{maxAttempts}, runtime: {tracker.GetRuntime().TotalSeconds:F0}s)");
                    CleanupComboOrder(tracker);
                    return;
                }

                // Get current combo quote
                var currentComboQuote = ComboQuote.FromSecurities(tracker.Legs, _algorithm.Securities);
                if (currentComboQuote == null)
                {
                    ((dynamic)_context.Logger).Warning($"SmartCombo: No quotes available for combo {tracker.PrimaryOrderId}, stopping updates");
                    CleanupComboOrder(tracker);
                    return;
                }

                // Calculate next net price
                var nextNetPrice = _pricingEngine.CalculateNextComboPrice(
                    tracker.CurrentNetPrice, currentComboQuote, tracker.ComboDirection, tracker.AttemptNumber + 1);

                if (nextNetPrice.HasValue)
                {
                    // Update all combo order tickets with the new net price
                    // QC handles the individual leg price distribution automatically
                    var updateSuccess = true;
                    foreach (var ticket in tracker.ComboTickets)
                    {
                        if (ticket.Status == OrderStatus.Submitted || ticket.Status == OrderStatus.PartiallyFilled)
                        {
                            // Note: For combo orders, we update the primary ticket's limit price
                            // QC automatically adjusts the other legs proportionally
                            var result = ticket.Update(new UpdateOrderFields { LimitPrice = nextNetPrice.Value });
                            if (!result.IsSuccess)
                            {
                                ((dynamic)_context.Logger).Warning($"SmartCombo: Failed to update combo ticket {ticket.OrderId}: {result.ErrorMessage}");
                                updateSuccess = false;
                            }
                        }
                    }

                    if (updateSuccess)
                    {
                        var isDebit = tracker.ComboDirection == CoreAlgo.Architecture.Core.Execution.OrderDirection.Buy;
                        var roundedNetPrice = PriceRounding.RoundComboNetPrice(_algorithm.Securities, tracker.Legs, nextNetPrice.Value, isDebit);

                        if (Math.Abs(roundedNetPrice - nextNetPrice.Value) > 0m)
                        {
                            foreach (var ticket in tracker.ComboTickets)
                            {
                                if (ticket.Status == OrderStatus.Submitted || ticket.Status == OrderStatus.PartiallyFilled)
                                {
                                    ticket.Update(new UpdateOrderFields { LimitPrice = roundedNetPrice });
                                }
                            }
                        }

                        tracker.UpdateNetPrice(roundedNetPrice, currentComboQuote);
                        
                        ((dynamic)_context.Logger).Debug($"SmartCombo: Updated combo {tracker.PrimaryOrderId} " +
                                                       $"to net price ${nextNetPrice.Value:F2} (attempt {tracker.AttemptNumber}) " +
                                                       $"NetMid: ${currentComboQuote.NetMid:F2}");

                        // Schedule next update if we haven't reached max attempts
                        if (tracker.AttemptNumber < maxAttempts)
                        {
                            ScheduleNextComboPricingUpdate(tracker);
                        }
                        else
                        {
                            ((dynamic)_context.Logger).Debug($"SmartCombo: Reached max attempts for combo {tracker.PrimaryOrderId}");
                        }
                    }
                    else
                    {
                        ((dynamic)_context.Logger).Warning($"SmartCombo: Failed to update combo order, stopping progressive pricing");
                        CleanupComboOrder(tracker);
                    }
                }
                else
                {
                    ((dynamic)_context.Logger).Debug($"SmartCombo: No more price improvements for combo {tracker.PrimaryOrderId}");
                    CleanupComboOrder(tracker);
                }
            }
            catch (Exception ex)
            {
                ((dynamic)_context.Logger).Error($"SmartCombo update error for combo {tracker.PrimaryOrderId}: {ex.Message}");
                CleanupComboOrder(tracker);
            }
        }

        /// <summary>
        /// Cleans up a combo order and removes associated scheduled events
        /// </summary>
        private void CleanupComboOrder(ComboOrderTracker tracker)
        {
            _activeComboOrders.Remove(tracker.PrimaryOrderId);
            
            if (tracker.ScheduledEvent != null)
            {
                _algorithm.Schedule.Remove(tracker.ScheduledEvent);
                _scheduledEvents.Remove(tracker.ScheduledEvent);
            }
        }

        /// <summary>
        /// Gets the mode for the pricing engine (for logging)
        /// </summary>
        public string GetPricingMode()
        {
            return _pricingEngine?.GetType().Name.Replace("PricingStrategy", "") ?? "Off";
        }
    }
}
using System;
using System.Collections.Generic;
using System.Linq;
using System.Reflection;
using CoreAlgo.Architecture.Core.Interfaces;
using CoreAlgo.Architecture.Core.Models;

namespace CoreAlgo.Architecture.Core.Services
{
    /// <summary>
    /// Utility class for discovering strategy implementations through reflection
    /// Eliminates hardcoded switch statements by dynamically finding Templates
    /// </summary>
    public static class StrategyDiscovery
    {
        private static readonly Dictionary<string, Type> _strategyTypes = new Dictionary<string, Type>();
        private static readonly Dictionary<string, Type> _configTypes = new Dictionary<string, Type>();
        private static readonly object _lock = new object();
        private static bool _initialized = false;

        /// <summary>
        /// Initialize discovery cache by scanning Templates namespace
        /// </summary>
        private static void EnsureInitialized()
        {
            if (_initialized) return;

            lock (_lock)
            {
                if (_initialized) return;

                // Discover all IStrategy implementations in Templates namespace
                var assembly = Assembly.GetExecutingAssembly();
                var strategyTypes = assembly.GetTypes()
                    .Where(t => typeof(IStrategy).IsAssignableFrom(t) &&
                               !t.IsInterface &&
                               !t.IsAbstract &&
                               t.Namespace == "CoreAlgo.Architecture.Core.Templates")
                    .ToList();

                // Build strategy name mappings
                foreach (var type in strategyTypes)
                {
                    var strategyName = GetStrategyNameFromType(type);
                    _strategyTypes[strategyName.ToUpperInvariant()] = type;

                    // Find corresponding config type
                    var configType = FindConfigType(strategyName);
                    if (configType != null)
                    {
                        _configTypes[strategyName.ToUpperInvariant()] = configType;
                    }
                }

                _initialized = true;
            }
        }

        /// <summary>
        /// Extract strategy name from template class name
        /// IronCondorTemplate -> IronCondor
        /// </summary>
        private static string GetStrategyNameFromType(Type type)
        {
            var name = type.Name;
            return name.EndsWith("Template") ? name.Substring(0, name.Length - 8) : name;
        }

        /// <summary>
        /// Find config type by naming convention
        /// IronCondor -> IronCondorConfig
        /// </summary>
        private static Type FindConfigType(string strategyName)
        {
            var configTypeName = $"{strategyName}Config";
            var assembly = Assembly.GetExecutingAssembly();
            
            return assembly.GetTypes()
                .FirstOrDefault(t => t.Name == configTypeName &&
                                    typeof(StrategyConfig).IsAssignableFrom(t));
        }

        /// <summary>
        /// Get all discovered strategy names
        /// </summary>
        public static IEnumerable<string> GetAllStrategyNames()
        {
            EnsureInitialized();
            return _strategyTypes.Keys.Select(k => k.ToLowerInvariant());
        }

        /// <summary>
        /// Get strategy type by name
        /// </summary>
        public static Type GetStrategyType(string strategyName)
        {
            EnsureInitialized();
            _strategyTypes.TryGetValue(strategyName.ToUpperInvariant(), out var type);
            return type;
        }

        /// <summary>
        /// Get config type by strategy name
        /// </summary>
        public static Type GetConfigType(string strategyName)
        {
            EnsureInitialized();
            _configTypes.TryGetValue(strategyName.ToUpperInvariant(), out var type);
            return type;
        }

        /// <summary>
        /// Create strategy instance by name
        /// </summary>
        public static IStrategy CreateStrategy(string strategyName)
        {
            var strategyType = GetStrategyType(strategyName);
            if (strategyType == null)
                throw new ArgumentException($"Unknown strategy '{strategyName}'. Available strategies: {string.Join(", ", GetAllStrategyNames())}");

            return (IStrategy)Activator.CreateInstance(strategyType);
        }

        /// <summary>
        /// Check if strategy exists
        /// </summary>
        public static bool StrategyExists(string strategyName)
        {
            return GetStrategyType(strategyName) != null;
        }
    }
}
#region imports
    using System;
    using System.Collections.Generic;
    using System.Linq;
    using QuantConnect;
#endregion

namespace CoreAlgo.Architecture.Core.Services
{
    /// <summary>
    /// Formatter for trade highlight log messages with ASCII-only single-line outputs
    /// Provides Entry/Exit formatting with metric suffix support
    /// </summary>
    public static class TradeHighlightFormatter
    {
        /// <summary>
        /// Build entry highlight message
        /// Format: ==> ENTRY AMD 2025-10-14 10:25 @219.95 x153 | SL 219.17 | TP 220.57 | O:1
        /// </summary>
        public static string BuildEntry(
            string symbol,
            DateTime time,
            decimal entryPrice,
            int quantity,
            decimal? stopLoss = null,
            decimal? takeProfit = null,
            int? orderId = null)
        {
            var parts = new List<string>
            {
                $"==> ENTRY {symbol} {time:yyyy-MM-dd HH:mm} @{entryPrice:F2} x{quantity}"
            };

            if (stopLoss.HasValue)
                parts.Add($"SL {stopLoss.Value:F2}");
            
            if (takeProfit.HasValue)
                parts.Add($"TP {takeProfit.Value:F2}");
            
            if (orderId.HasValue)
                parts.Add($"O:{orderId.Value}");

            return string.Join(" | ", parts);
        }

        /// <summary>
        /// Build exit highlight message (TP/SL/TS)
        /// TP Format:   <== TP AMD 2025-10-14 10:29 @220.58 | PnL +96.39 | O:2
        /// SL Format:   !! SL AMD 2025-10-14 10:30 @221.04 | PnL -133.45 | O:3
        /// TS Format:   ~~> TS AMD 2025-10-14 10:30 @221.04 | PnL -42.10 | O:4
        /// </summary>
        public static string BuildExit(
            string symbol,
            DateTime time,
            decimal exitPrice,
            decimal pnl,
            ExitType exitType,
            int? orderId = null)
        {
            var prefix = exitType switch
            {
                ExitType.TakeProfit => "<== TP",
                ExitType.StopLoss => "!! SL",
                ExitType.TrailingStop => "~~> TS",
                ExitType.Cancel => "X CANCEL",
                _ => "<==" // fallback
            };

            var pnlSign = pnl >= 0 ? "+" : "";
            var parts = new List<string>
            {
                $"{prefix} {symbol} {time:yyyy-MM-dd HH:mm} @{exitPrice:F2}",
                $"PnL {pnlSign}{pnl:F2}"
            };

            if (orderId.HasValue)
                parts.Add($"O:{orderId.Value}");

            return string.Join(" | ", parts);
        }

        /// <summary>
        /// Format metrics dictionary into bracketed suffix: [k=v, k=v]
        /// </summary>
        public static string FormatMetricsSuffix(IDictionary<string, string> metrics)
        {
            if (metrics == null || metrics.Count == 0)
                return string.Empty;

            // Sort keys alphabetically for consistent output
            var sorted = metrics.OrderBy(kvp => kvp.Key).ToList();
            
            // Build comma-separated key=value pairs
            var pairs = sorted.Select(kvp => $"{kvp.Key}={kvp.Value}");
            var content = string.Join(", ", pairs);
            
            // Truncate if too long (cap at 120 chars total)
            if (content.Length > 120)
                content = content.Substring(0, 117) + "...";
            
            return $"[{content}]";
        }
    }

    /// <summary>
    /// Exit type for trade highlights
    /// </summary>
    public enum ExitType
    {
        TakeProfit,
        StopLoss,
        TrailingStop,
        Cancel
    }
}

using System;
using System.Collections.Generic;
using System.Linq;
using System.Text.Json;
using System.Text.Json.Serialization;
using CoreAlgo.Architecture.Core.Interfaces;
using CoreAlgo.Architecture.Core.Implementations;

namespace CoreAlgo.Architecture.Core.Services
{
    /// <summary>
    /// Handles persistence of trade records to QuantConnect ObjectStore
    /// Provides simple load/save functionality for resuming algorithm state
    /// </summary>
    public class TradePersistenceService
    {
        private readonly IAlgorithmContext _context;
        private const string CurrentPositionsKey = "positions/current.json";
        private const string SnapshotKeyPrefix = "positions/snapshots/";
        
        private static readonly JsonSerializerOptions SerializerOptions = new JsonSerializerOptions
        {
            WriteIndented = true,
            DefaultIgnoreCondition = JsonIgnoreCondition.Never,
            PropertyNamingPolicy = JsonNamingPolicy.CamelCase
        };

        public TradePersistenceService(IAlgorithmContext context)
        {
            _context = context ?? throw new ArgumentNullException(nameof(context));
        }

        /// <summary>
        /// Saves current trade state to ObjectStore
        /// Only persists open and working trades (excludes closed/cancelled to minimize storage)
        /// </summary>
        public void SaveTrades(TradeTracker tracker)
        {
            if (tracker == null) return;

            try
            {
                // Only persist open and working trades to minimize ObjectStore usage
                var openAndWorking = tracker.WorkingTrades.Concat(tracker.OpenTrades).ToList();
                
                var book = new TradeBook
                {
                    Version = "1.0",
                    AsOfUtc = DateTime.UtcNow,
                    AllTrades = openAndWorking,
                    WorkingTrades = tracker.WorkingTrades.Select(t => t.OrderId).ToList(),
                    OpenTrades = tracker.OpenTrades.Select(t => t.OrderId).ToList(),
                    ClosedTrades = new List<string>() // Don't persist closed trades
                };

                var json = JsonSerializer.Serialize(book, SerializerOptions);
                var bytes = System.Text.Encoding.UTF8.GetBytes(json);
                
                _context.Algorithm.ObjectStore.SaveBytes(CurrentPositionsKey, bytes);
                
                ((dynamic)_context.Logger).Debug($"TradePersistence: Saved {openAndWorking.Count} open/working trades to ObjectStore (excluded {tracker.ClosedTrades.Count} closed)");
            }
            catch (Exception ex)
            {
                // Never break trading due to persistence errors
                ((dynamic)_context.Logger).Error($"TradePersistence: Error saving trades: {ex.Message}");
            }
        }

        /// <summary>
        /// Clears current positions from ObjectStore if running in backtest mode
        /// Live mode is protected - positions are preserved for warm restarts
        /// </summary>
        public void ClearPositionsIfBacktest()
        {
            if (_context.Algorithm.LiveMode)
            {
                return;
            }
            
            if (_context.Algorithm.ObjectStore.ContainsKey(CurrentPositionsKey))
            {
                _context.Algorithm.ObjectStore.Delete(CurrentPositionsKey);
                ((dynamic)_context.Logger).Info("TradePersistence: Cleared current positions for backtest");
            }
        }

        /// <summary>
        /// Clears all daily snapshots from ObjectStore if running in backtest mode
        /// This reclaims space from previous backtest runs without adding retention complexity
        /// Live mode is protected - snapshots are preserved for analysis
        /// </summary>
        public void ClearSnapshotsIfBacktest()
        {
            if (_context.Algorithm.LiveMode)
            {
                return;
            }

            try
            {
                var deletedCount = 0;
                var keysToDelete = new List<string>();
                
                // Enumerate all keys in ObjectStore and find snapshot keys
                foreach (var key in _context.Algorithm.ObjectStore.Keys)
                {
                    if (key.StartsWith(SnapshotKeyPrefix))
                    {
                        keysToDelete.Add(key);
                    }
                }
                
                // Delete all snapshot keys
                foreach (var key in keysToDelete)
                {
                    _context.Algorithm.ObjectStore.Delete(key);
                    deletedCount++;
                }
                
                if (deletedCount > 0)
                {
                    ((dynamic)_context.Logger).Info($"TradePersistence: Cleared {deletedCount} daily snapshots for backtest");
                }
            }
            catch (Exception ex)
            {
                ((dynamic)_context.Logger).Error($"TradePersistence: Error clearing snapshots: {ex.Message}");
            }
        }

        /// <summary>
        /// Loads trade state from ObjectStore
        /// Returns null if no saved state exists
        /// </summary>
        public TradeTracker LoadTrades()
        {
            try
            {
                if (!_context.Algorithm.ObjectStore.ContainsKey(CurrentPositionsKey))
                {
                    ((dynamic)_context.Logger).Info("TradePersistence: No saved trades found, starting fresh");
                    return null;
                }

                var bytes = _context.Algorithm.ObjectStore.ReadBytes(CurrentPositionsKey);
                var json = System.Text.Encoding.UTF8.GetString(bytes);
                var book = JsonSerializer.Deserialize<TradeBook>(json, SerializerOptions);

                if (book == null || book.AllTrades == null)
                {
                    ((dynamic)_context.Logger).Warning("TradePersistence: Invalid saved data, starting fresh");
                    return null;
                }

                var tracker = new TradeTracker
                {
                    AllTrades = book.AllTrades
                };

                // Rebuild categorized lists
                foreach (var trade in book.AllTrades)
                {
                    if (book.WorkingTrades.Contains(trade.OrderId))
                    {
                        tracker.WorkingTrades.Add(trade);
                    }
                    else if (book.OpenTrades.Contains(trade.OrderId))
                    {
                        tracker.OpenTrades.Add(trade);
                    }
                    else if (book.ClosedTrades.Contains(trade.OrderId))
                    {
                        tracker.ClosedTrades.Add(trade);
                    }
                }

                ((dynamic)_context.Logger).Info($"TradePersistence: Loaded {tracker.AllTrades.Count} trades " +
                                               $"(Working: {tracker.WorkingTrades.Count}, Open: {tracker.OpenTrades.Count}, " +
                                               $"Closed: {tracker.ClosedTrades.Count})");
                
                return tracker;
            }
            catch (Exception ex)
            {
                ((dynamic)_context.Logger).Error($"TradePersistence: Error loading trades: {ex.Message}");
                return null;
            }
        }

        /// <summary>
        /// Saves a daily snapshot of trades
        /// Only persists open and working trades (excludes closed/cancelled to minimize storage)
        /// </summary>
        public void SaveDailySnapshot(TradeTracker tracker)
        {
            if (tracker == null) return;

            try
            {
                var date = _context.Algorithm.Time.ToString("yyyy-MM-dd");
                var snapshotKey = $"{SnapshotKeyPrefix}{date}.json";

                // Only persist open and working trades to minimize ObjectStore usage
                var openAndWorking = tracker.WorkingTrades.Concat(tracker.OpenTrades).ToList();
                
                var book = new TradeBook
                {
                    Version = "1.0",
                    AsOfUtc = DateTime.UtcNow,
                    AllTrades = openAndWorking,
                    WorkingTrades = tracker.WorkingTrades.Select(t => t.OrderId).ToList(),
                    OpenTrades = tracker.OpenTrades.Select(t => t.OrderId).ToList(),
                    ClosedTrades = new List<string>() // Don't persist closed trades
                };

                var json = JsonSerializer.Serialize(book, SerializerOptions);
                var bytes = System.Text.Encoding.UTF8.GetBytes(json);
                
                _context.Algorithm.ObjectStore.SaveBytes(snapshotKey, bytes);
                
                ((dynamic)_context.Logger).Debug($"TradePersistence: Saved daily snapshot for {date} ({openAndWorking.Count} open/working trades)");
            }
            catch (Exception ex)
            {
                ((dynamic)_context.Logger).Error($"TradePersistence: Error saving daily snapshot: {ex.Message}");
            }
        }

        /// <summary>
        /// Gets summary of persisted trade data for reporting
        /// </summary>
        public TradeSummary GetTradeSummary(TradeTracker tracker)
        {
            if (tracker == null)
            {
                return new TradeSummary();
            }

            return new TradeSummary
            {
                TotalTrades = tracker.AllTrades.Count,
                WorkingCount = tracker.WorkingTrades.Count,
                OpenCount = tracker.OpenTrades.Count,
                ClosedCount = tracker.ClosedTrades.Count,
                PartialFillCount = tracker.AllTrades.Count(t => t.Status == "PartialFill"),
                CancelledCount = tracker.AllTrades.Count(t => t.Status == "Cancelled"),
                TotalPnL = tracker.ClosedTrades.Sum(t => t.PnL),
                AsOfUtc = DateTime.UtcNow
            };
        }
    }

    /// <summary>
    /// Container for serialized trade data
    /// </summary>
    public class TradeBook
    {
        public string Version { get; set; }
        public DateTime AsOfUtc { get; set; }
        public List<TradeRecord> AllTrades { get; set; } = new List<TradeRecord>();
        public List<string> WorkingTrades { get; set; } = new List<string>();
        public List<string> OpenTrades { get; set; } = new List<string>();
        public List<string> ClosedTrades { get; set; } = new List<string>();
    }

    /// <summary>
    /// Summary statistics for trade reporting
    /// </summary>
    public class TradeSummary
    {
        public int TotalTrades { get; set; }
        public int WorkingCount { get; set; }
        public int OpenCount { get; set; }
        public int ClosedCount { get; set; }
        public int PartialFillCount { get; set; }
        public int CancelledCount { get; set; }
        public decimal TotalPnL { get; set; }
        public DateTime AsOfUtc { get; set; }
    }
}

using System;
using System.Collections.Generic;
using System.Linq;
using QuantConnect;
using QuantConnect.Data;
using QuantConnect.Data.Market;
using QuantConnect.Indicators;
using QuantConnect.Orders;
using QuantConnect.Securities;
using CoreAlgo.Architecture.Core.Implementations;
using CoreAlgo.Architecture.Core.Models;
using CoreAlgo.Architecture.Core.Services;
using CoreAlgo.Architecture.QC.Helpers;

namespace CoreAlgo.Architecture.Core.Templates
{
    /// <summary>
    /// Adaptive Scalper strategy template - HFT Multi-Trade Edition
    /// Micro-scalps equities using ATR-driven adaptive stops/targets, spread quality gates, and progressive daily throttle
    /// Supports multiple concurrent trades with independent TP/SL per trade using Lean OCO pattern
    /// QC-First approach - leverages QuantConnect's native indicators, bid/ask quotes, and OrderTickets
    /// Uses TradeTracker/TradeRecord for durable state with lightweight runtime state for trailing
    /// </summary>
    public class AdaptiveScalperTemplate : SimpleBaseStrategy
    {
        /// <summary>
        /// Exit state machine for single-active-exit discipline
        /// Prevents double SL+TP fills by tracking exit lifecycle
        /// </summary>
        private enum ExitState
        {
            NoExit,       // No exit orders placed yet
            ExitActive,   // SL or TP is working (submitted)
            ExitFilling,  // First exit is filling - blocks sibling fills
            Closed        // Position closed
        }

        /// <summary>
        /// Spread quality classification
        /// </summary>
        private enum SpreadState
        {
            Tight,
            Normal,
            Wide
        }

        /// <summary>
        /// Spread evaluation result
        /// </summary>
        private struct SpreadEvaluation
        {
            public SpreadState State;
            public decimal SpreadValue;
            public decimal Mid;
            public bool HasValidQuotes;
        }

        /// <summary>
        /// Lightweight runtime state for fast-moving fields (trailing, swap coordination)
        /// Not persisted - recreated from TradeRecord on algorithm restart
        /// </summary>
        private class RuntimeState
        {
            public decimal HighestPriceSinceEntry;
            public bool TrailingArmed;
            public decimal CurrentStopPrice;
            public bool PendingSwapToTp;
            public int? TpOrderId; // Child TP order ID
            public int? SlOrderId; // Child SL order ID
        }

        // Order tag constants
        private const string TAG_ENTRY = "ENTRY";
        private const string TAG_SL_PREFIX = "SL_E";
        private const string TAG_TP_PREFIX = "TP_E";

        // TradeRecord StrategyDetails keys
        private const string KEY_STOP_DIST = "StopDist";
        private const string KEY_TP_DIST = "TpDist";
        private const string KEY_ATR_AT_ENTRY = "AtrAtEntry";
        private const string KEY_ADAPT_FACTOR = "AdaptFactor";
        private const string KEY_SPREAD_STATE = "SpreadState";
        private const string KEY_TP_TRIGGER_PRICE = "TpTriggerPrice";
        private const string KEY_SL_ORDER_ID = "SlOrderId";
        private const string KEY_TP_ORDER_ID = "TpOrderId";
        private const string KEY_EXIT_STATE = "ExitState";

        private AdaptiveScalperConfig _config;
        private Symbol _underlying;
        private AverageTrueRange _atr;
        private RollingWindow<decimal> _atrBaseline;

        // Multi-trade state management using TradeTracker + runtime maps
        private readonly Dictionary<int, RuntimeState> _runtimeByEntryId = new Dictionary<int, RuntimeState>();
        private readonly Dictionary<int, int> _childToEntryMap = new Dictionary<int, int>();

        // Rate limiting
        private DateTime _lastEntryTime = DateTime.MinValue;
        private Queue<DateTime> _orderTimestamps = new Queue<DateTime>();

        // Daily tracking
        private decimal _dailyPnL = 0m;
        private decimal _tradeThrottle = 1.0m;
        private bool _haltedForDay = false;
        private DateTime _lastTradingDay = DateTime.MinValue;

        // Performance tracking
        private int _totalEntriesToday = 0;
        private int _totalExitsToday = 0;
        
        // Debounced logging for EnsureExitOrders
        private readonly HashSet<int> _ensureExitLogged = new HashSet<int>();

        // Conservative ensure gating: cooldowns and TTLs
        private const int ENSURE_COOLDOWN_MS = 500;        // Per-entry cooldown after any exit action
        private const int UNKNOWN_TICKET_TTL_MS = 1500;    // Grace period for newly-placed tickets
        private const int ENSURE_GLOBAL_DEBOUNCE_MS = 1000; // Global ensure rate limit
        
        private readonly Dictionary<int, DateTime> _lastExitActionAt = new Dictionary<int, DateTime>();
        private DateTime _lastEnsureCheckAt = DateTime.MinValue;

        public override string Name => "Adaptive Scalper HFT";

        public override string Description =>
            "High-frequency micro-scalping strategy with multi-trade execution, ATR-driven adaptive stops/targets, spread quality gates, and progressive daily throttle";

        // ============================================================================
        // HELPER METHODS (In-place simplification)
        // ============================================================================

        /// <summary>
        /// Evaluate spread quality and compute mid-price
        /// </summary>
        private SpreadEvaluation EvaluateSpread(decimal bid, decimal ask, decimal currentPrice, decimal dynMin, decimal dynMax, bool requireQuotes)
        {
            bool hasValidQuotes = bid > 0 && ask > 0;
            decimal spread;
            decimal mid;

            if (!hasValidQuotes)
            {
                if (requireQuotes)
                {
                    // Strict mode: no quotes = cannot evaluate
                    return new SpreadEvaluation
                    {
                        State = SpreadState.Wide,
                        SpreadValue = 0,
                        Mid = currentPrice,
                        HasValidQuotes = false
                    };
                }

                // Fallback mode: use spread=0 and current price as mid
                spread = 0m;
                mid = currentPrice;
            }
            else
            {
                spread = ask - bid;
                mid = (ask + bid) / 2m;
            }

            // Classify spread quality
            SpreadState state;
            if (!hasValidQuotes)
            {
                state = SpreadState.Normal; // Treat no-quotes as NORMAL when fallback is enabled
            }
            else if (spread <= dynMin * 1.1m)
            {
                state = SpreadState.Tight;
            }
            else if (spread <= dynMax * 0.9m)
            {
                state = SpreadState.Normal;
            }
            else
            {
                state = SpreadState.Wide;
            }

            return new SpreadEvaluation
            {
                State = state,
                SpreadValue = spread,
                Mid = mid,
                HasValidQuotes = hasValidQuotes
            };
        }

        /// <summary>
        /// Compute ATR adaptation factor (protects against zero-divide)
        /// </summary>
        private decimal GetAdaptFactor()
        {
            if (!_atr.IsReady || !_atrBaseline.IsReady) return 1.0m;

            var atrValue = (decimal)_atr.Current.Value;
            var baseline = _atrBaseline.Average();
            return baseline != 0 ? atrValue / baseline : 1.0m;
        }

        /// <summary>
        /// Single entry gate - encapsulates all entry checks
        /// </summary>
        private bool ShouldEnter(Slice slice, SpreadEvaluation spreadEval, int openCount, out string reason)
        {
            // Check concurrency
            if (openCount >= _config.MaxConcurrentTrades)
            {
                reason = $"MaxConcurrent ({openCount}/{_config.MaxConcurrentTrades})";
                return false;
            }

            // Check throttle
            if (_tradeThrottle <= _config.MinThrottle)
            {
                reason = $"Throttle too low ({_tradeThrottle:P0} <= {_config.MinThrottle:P0})";
                return false;
            }

            // Check spread quality
            if (spreadEval.State != SpreadState.Tight && spreadEval.State != SpreadState.Normal)
            {
                reason = $"Spread state: {spreadEval.State}";
                return false;
            }

            // Check rate limits
            if (!CanEnterNow(slice.Time))
            {
                reason = "Rate limiting";
                return false;
            }

            // Check entry restrictions (time window, capital)
            if (!EntryRestrictions.CanEnterPosition(_underlying, slice, out reason))
            {
                return false;
            }

            // Check daily cap (if configured)
            if (_config.MaxEntriesPerDay > 0 && _totalEntriesToday >= _config.MaxEntriesPerDay)
            {
                reason = $"Daily entry cap reached ({_totalEntriesToday}/{_config.MaxEntriesPerDay})";
                return false;
            }

            reason = "";
            return true;
        }

        /// <summary>
        /// Get or create runtime state for a trade (centralized hydration)
        /// </summary>
        private RuntimeState GetOrCreateRuntime(TradeRecord trade)
        {
            var entryId = int.Parse(trade.OrderId);
            
            if (!_runtimeByEntryId.TryGetValue(entryId, out var runtime))
            {
                runtime = new RuntimeState
                {
                    HighestPriceSinceEntry = trade.OpenPrice,
                    TrailingArmed = false,
                    CurrentStopPrice = 0,
                    PendingSwapToTp = false
                };
                
                // Load child order IDs from TradeRecord if available
                if (trade.TryGetInt(KEY_SL_ORDER_ID, out var slId) && slId > 0)
                {
                    runtime.SlOrderId = slId;
                    MapChild(slId, entryId);
                }
                if (trade.TryGetInt(KEY_TP_ORDER_ID, out var tpId) && tpId > 0)
                {
                    runtime.TpOrderId = tpId;
                    MapChild(tpId, entryId);
                }
                
                _runtimeByEntryId[entryId] = runtime;
            }
            
            return runtime;
        }

        /// <summary>
        /// Record exit action timestamp for cooldown tracking
        /// </summary>
        private void RecordExitAction(int entryId, DateTime time)
        {
            _lastExitActionAt[entryId] = time;
        }

        /// <summary>
        /// Get exit state from TradeRecord (defaults to NoExit if not set)
        /// </summary>
        private ExitState GetExitState(TradeRecord trade)
        {
            if (trade.TryGetDetail(KEY_EXIT_STATE, out var stateStr) && 
                Enum.TryParse<ExitState>(stateStr, out var state))
            {
                return state;
            }
            return ExitState.NoExit;
        }

        /// <summary>
        /// Set exit state in TradeRecord and persist
        /// </summary>
        private void SetExitState(TradeRecord trade, ExitState newState)
        {
            var oldState = GetExitState(trade);
            if (oldState != newState)
            {
                trade.SetDetail(KEY_EXIT_STATE, newState.ToString());
                TradePersistence.SaveTrades(TradeTracker);
                
                if (_config.DebugMode)
                {
                    SmartDebug($"[FSM E{trade.OrderId}] {oldState} → {newState}");
                }
            }
        }

        /// <summary>
        /// Try to update a stop order (DRY helper for ticket fetch + update)
        /// </summary>
        private bool TryUpdateStop(int slOrderId, decimal newStopPrice, decimal stopLimitBuffer)
        {
            var slTicket = Algorithm.Transactions.GetOrderTicket(slOrderId);
            if (slTicket == null || slTicket.Status != OrderStatus.Submitted)
            {
                return false;
            }

            var updateFields = new UpdateOrderFields
            {
                StopPrice = newStopPrice,
                LimitPrice = newStopPrice - stopLimitBuffer
            };
            
            var response = slTicket.Update(updateFields);
            
            // Record action timestamp on successful update
            if (response.IsSuccess && _childToEntryMap.TryGetValue(slOrderId, out var entryId))
            {
                RecordExitAction(entryId, Algorithm.Time);
            }
            
            return response.IsSuccess;
        }

        /// <summary>
        /// Compute trailing stop (returns new stop price or null if no update needed)
        /// </summary>
        private decimal? ComputeTrailingStop(RuntimeState runtime, decimal tpDist, decimal tick)
        {
            if (!runtime.TrailingArmed || tpDist <= 0) return null;

            // Calculate trailing amount - smaller than initial stop to allow profit capture
            var trailingAmount = Math.Max(tick, tpDist * (1m - _config.TrailingStartFraction));
            
            // Calculate new stop based on highest price
            var candidateStop = runtime.HighestPriceSinceEntry - trailingAmount;
            var newStopPrice = PriceRounding.FloorToTick(candidateStop, tick);
            
            // Only update if stop moves up by at least 1 tick
            if (newStopPrice > runtime.CurrentStopPrice + tick)
            {
                return newStopPrice;
            }

            return null;
        }

        /// <summary>
        /// Unified entry ID resolution from tag or child map
        /// Supports: ENTRY, ENTRY_E{id}, SL_E{id}, TP_E{id}
        /// </summary>
        private bool TryExtractEntryIdFromTagOrMap(string tag, int orderId, out int entryId, out bool isEntry)
        {
            entryId = 0;
            isEntry = false;

            // Check if this is an entry order (ENTRY or ENTRY_E{id})
            if (!string.IsNullOrEmpty(tag) && tag.StartsWith(TAG_ENTRY))
            {
                isEntry = true;
                
                // Try to parse ENTRY_E{id} format
                if (tag.Length > TAG_ENTRY.Length && tag[TAG_ENTRY.Length] == '_')
                {
                    var idPart = tag.Substring(TAG_ENTRY.Length + 1);
                    if (idPart.StartsWith("E") && int.TryParse(idPart.Substring(1), out entryId))
                    {
                        return true;
                    }
                }
                
                // Fallback: use orderId as entryId for plain "ENTRY" tag
                entryId = orderId;
                return true;
            }

            // Try to extract from child tag (SL_E123 or TP_E123)
            if (!string.IsNullOrEmpty(tag))
            {
                var parts = tag.Split('_');
                if (parts.Length >= 2 && parts[1].StartsWith("E"))
                {
                    if (int.TryParse(parts[1].Substring(1), out entryId))
                    {
                        return true;
                    }
                }
            }

            // Try child-to-entry map
            if (_childToEntryMap.TryGetValue(orderId, out entryId))
            {
                return true;
            }

            return false;
        }

        /// <summary>
        /// Cancel all exit orders for open trades (used by kill switch)
        /// </summary>
        private void CancelAllExitOrdersForOpenTrades()
        {
            var openTrades = GetOpenTradesForSymbol();
            foreach (var trade in openTrades)
            {
                var entryId = int.Parse(trade.OrderId);
                
                if (_runtimeByEntryId.TryGetValue(entryId, out var runtime))
                {
                    if (runtime.TpOrderId.HasValue)
                    {
                        var tpTicket = Algorithm.Transactions.GetOrderTicket(runtime.TpOrderId.Value);
                        if (tpTicket != null && tpTicket.Status == OrderStatus.Submitted)
                        {
                            tpTicket.Cancel("Kill switch");
                        }
                    }
                    if (runtime.SlOrderId.HasValue)
                    {
                        var slTicket = Algorithm.Transactions.GetOrderTicket(runtime.SlOrderId.Value);
                        if (slTicket != null && slTicket.Status == OrderStatus.Submitted)
                        {
                            slTicket.Cancel("Kill switch");
                        }
                    }
                    
                    ClearTradeMappings(entryId, runtime);
                }
            }
        }

        // ============================================================================
        // STRATEGY LIFECYCLE
        // ============================================================================

        public override void OnInitialize()
        {
            SmartLog("AdaptiveScalperTemplate.OnInitialize() starting (HFT Multi-Trade Edition)...");

            // Configure with AdaptiveScalper-specific settings
            try
            {
                Configure<AdaptiveScalperConfig>();
                _config = (AdaptiveScalperConfig)Config;
                SmartLog("Configuration loaded successfully");
            }
            catch (Exception ex)
            {
                SmartError($"Failed to load configuration: {ex.Message}");
                throw;
            }

            // Setup underlying equity with appropriate resolution for quote data
            SmartLog($"Setting up {_config.UnderlyingSymbol} for scalping");
            var dataResolution = _config.GetUnderlyingResolution();
            SmartLog($"Using data resolution: {dataResolution} (from config: {_config.UnderlyingResolution})");

            var security = AddEquity(_config.UnderlyingSymbol, dataResolution);
            _underlying = security.Symbol;

            SmartLog($"Successfully added {_underlying} at {dataResolution} resolution");

            // Initialize ATR indicator for volatility-based stop/target calculation
            var atrResolution = _config.GetAtrResolution();
            SmartLog($"Creating ATR({_config.AtrPeriod}) at {atrResolution} resolution");

            _atr = new AverageTrueRange(_config.AtrPeriod);

            // Register for automatic updates and warm up with historical data
            Algorithm.RegisterIndicator(_underlying, _atr, atrResolution);
            Algorithm.WarmUpIndicator(_underlying, _atr, atrResolution);

            SmartLog($"ATR registered and warmed up - IsReady: {_atr.IsReady}, Current: {(_atr.IsReady ? _atr.Current.Value.ToString("F4") : "N/A")}");

            // Initialize ATR baseline rolling window for adaptive factor calculation
            _atrBaseline = new RollingWindow<decimal>(_config.VolatilityBaselineWindow);
            SmartLog($"ATR baseline window created with size {_config.VolatilityBaselineWindow}");

            // Schedule daily PnL reset at market open
            Algorithm.Schedule.On(
                Algorithm.DateRules.EveryDay(_underlying),
                Algorithm.TimeRules.AfterMarketOpen(_underlying, 1),
                ResetDailyTracking
            );

            // Format trading window status
            var tradingWindowStatus = (_config.TradingStartTime == TimeSpan.Zero && _config.TradingEndTime == TimeSpan.Zero)
                ? "DISABLED (00:00-00:00) - trades allowed all day"
                : $"{_config.TradingStartTime:hh\\:mm} - {_config.TradingEndTime:hh\\:mm} (active)";

            // Guardrail: warn if stop/TP ratios make trailing difficult
            if (_config.StopLossAtrMultiple >= _config.TakeProfitAtrMultiple)
            {
                SmartWarn(
                    $"[GUARDRAIL] StopLoss ({_config.StopLossAtrMultiple}x ATR) >= TakeProfit ({_config.TakeProfitAtrMultiple}x ATR). " +
                    $"Trailing may rarely reach breakeven before TP is hit. Consider reducing StopLoss or increasing TakeProfit multiples."
                );
            }

            SmartLog(
                $"Adaptive Scalper HFT initialized:\n" +
                $"  Underlying: {_underlying}\n" +
                $"  Data Resolution: {dataResolution}\n" +
                $"  ATR Period: {_config.AtrPeriod} at {atrResolution}\n" +
                $"  Baseline Window: {_config.VolatilityBaselineWindow}\n" +
                $"  Spread Range: ${_config.MinSpread:F3} - ${_config.MaxSpread:F3}\n" +
                $"  Target Risk: ${_config.TargetDollarRisk}\n" +
                $"  Stop Loss: max(${_config.MinStopLoss:F3}, {_config.StopLossAtrMultiple}x ATR)\n" +
                $"  Take Profit: {_config.TakeProfitAtrMultiple}x ATR\n" +
                $"  Trailing Start: {_config.TrailingStartFraction:P0} of TP\n" +
                $"  Daily Kill Switch: ${_config.DailyKillSwitch:N0}\n" +
                $"  Throttle Decay: {_config.ThrottleDecay}\n" +
                $"  Min Throttle: {_config.MinThrottle:P0}\n" +
                $"  Max Concurrent Trades: {_config.MaxConcurrentTrades}\n" +
                $"  Entry Cooldown: {_config.EntryCooldownMs}ms\n" +
                $"  Max Orders/Min: {_config.MaxOrdersPerMinute}\n" +
                $"  Entry Limit Offset: {_config.EntryLimitOffsetTicks} ticks\n" +
                $"  Stop-Limit Buffer: {_config.StopLimitBufferTicks} ticks\n" +
                $"  Trading Window: {tradingWindowStatus}\n" +
                $"  ATR Ready: {_atr.IsReady}\n" +
                $"  Live Mode: {Algorithm.LiveMode}\n" +
                $"  Algorithm Status: {Algorithm.Status}"
            );
        }

        protected override void OnPreExecuteAlways(Slice slice)
        {
            // Daily reset guard (scheduled reset is primary, this is backup)
            if (slice.Time.Date > _lastTradingDay)
            {
                ResetDailyTracking();
                _lastTradingDay = slice.Time.Date;
            }
        }

        protected override void OnExecute(Slice slice)
        {
            // Early exit for halted day
            if (_haltedForDay) return;

            // Update ATR baseline
            if (_atr.IsReady)
            {
                _atrBaseline.Add((decimal)_atr.Current.Value);
            }

            // Wait for indicators to be ready
            if (!_atr.IsReady || !_atrBaseline.IsReady) return;

            // Get current market data
            var security = Securities[_underlying];
            var currentPrice = security.Price;
            var atrValue = (decimal)_atr.Current.Value;
            var adaptFactor = GetAdaptFactor();

            // Evaluate spread quality
            var dynMin = _config.MinSpread * adaptFactor;
            var dynMax = _config.MaxSpread * adaptFactor;
            var spreadEval = EvaluateSpread(security.BidPrice, security.AskPrice, currentPrice, dynMin, dynMax, _config.RequireQuotesForEntry);

            // Early exit if spread evaluation failed (strict quote mode)
            if (_config.RequireQuotesForEntry && spreadEval.State == SpreadState.Wide && !spreadEval.HasValidQuotes)
            {
                return;
            }

            // Calculate time-of-day volatility multiplier (higher during opening hour)
            var hour = slice.Time.Hour + slice.Time.Minute / 60m;
            var volMultiplier = (hour >= 9.5m && hour <= 10.5m) ? _config.OHVolatilityMultiplier : 1.0m;

            // Calculate dynamic stop/target with tick rounding
            var tick = PriceRounding.GetMinPriceVariation(Algorithm.Securities, _underlying);
            var rawStopLoss = Math.Max(_config.MinStopLoss, atrValue * _config.StopLossAtrMultiple * volMultiplier);
            var rawTakeProfit = atrValue * _config.TakeProfitAtrMultiple * volMultiplier;
            var stopLoss = PriceRounding.CeilToTick(rawStopLoss, tick);
            var takeProfit = PriceRounding.CeilToTick(rawTakeProfit, tick);

            // Calculate risk-based position size
            var baseQty = (int)(_config.TargetDollarRisk / stopLoss);
            var tradeQty = Math.Max(1, (int)(baseQty * _tradeThrottle));

            // === ENTRY LOGIC ===
            var openTrades = GetOpenTradesForSymbol();
            var openCount = openTrades.Count;

            // Single entry gate check
            if (ShouldEnter(slice, spreadEval, openCount, out var reason))
            {
                // Place entry limit order at mid-price (or offset by config ticks)
                var entryPrice = spreadEval.Mid + (_config.EntryLimitOffsetTicks * tick);
                var entryTicket = LimitOrder(_underlying, tradeQty, entryPrice, tag: TAG_ENTRY);
                if (entryTicket == null)
                {
                    if (_config.DebugMode) SmartWarn("[ENTRY] LimitOrder returned null");
                    return;
                }

                // Track and persist entry order
                TrackWorkingOrder(entryTicket, Name);
                var trade = TradeTracker.AllTrades.FirstOrDefault(t => t.OrderId == entryTicket.OrderId.ToString());
                if (trade != null)
                {
                    trade.SetDetail(KEY_STOP_DIST, stopLoss);
                    trade.SetDetail(KEY_TP_DIST, takeProfit);
                    trade.SetDetail(KEY_ATR_AT_ENTRY, atrValue);
                    trade.SetDetail(KEY_ADAPT_FACTOR, adaptFactor);
                    trade.SetDetail(KEY_SPREAD_STATE, spreadEval.State.ToString());
                    trade.Quantity = tradeQty;
                    TradePersistence.SaveTrades(TradeTracker);
                }

                _lastEntryTime = slice.Time;
                _totalEntriesToday++;

                // Compact logging (throttled by DebugMode)
                if (_config.DebugMode && _totalEntriesToday % 10 == 1)
                {
                    SmartLog($"[ENTRY E{entryTicket.OrderId}] {spreadEval.State} | Price: ${currentPrice:F2} | ATR: {atrValue:F4} | SL: ${stopLoss:F4} | TP: ${takeProfit:F4} | Qty: {tradeQty}");
                }
            }
            else if (_config.DebugMode)
            {
                SmartLog($"[ENTRY BLOCKED] {reason}");
            }

            // === EXIT AND TRADE MANAGEMENT ===
            ManageOpenTrades(slice, currentPrice);
            
            // === EVENT-ONLY OCO: Removed periodic ensure loop to prevent double orders ===
            // EnsureExitOrders is kept in codebase but not called (event-driven flow only)
        }

        /// <summary>
        /// Manage all open trades - update trailing stops when profit threshold reached
        /// </summary>
        private void ManageOpenTrades(Slice slice, decimal currentPrice)
        {
            var tick = PriceRounding.GetMinPriceVariation(Algorithm.Securities, _underlying);
            var stopLimitBuffer = _config.StopLimitBufferTicks * tick;
            var openTrades = GetOpenTradesForSymbol();
            
            foreach (var trade in openTrades)
            {
                var entryId = int.Parse(trade.OrderId);
                var runtime = GetOrCreateRuntime(trade);

                // Update highest price tracker
                if (currentPrice > runtime.HighestPriceSinceEntry)
                {
                    runtime.HighestPriceSinceEntry = currentPrice;
                }

                // Get static details from TradeRecord
                trade.TryGetDecimal(KEY_TP_DIST, out var tpDist);
                trade.TryGetDecimal(KEY_TP_TRIGGER_PRICE, out var tpTrigger);
                var unrealizedPnL = currentPrice - trade.OpenPrice;

                // === SWAP-TO-TP LOGIC ===
                if (!runtime.TpOrderId.HasValue && !runtime.PendingSwapToTp && tpTrigger > 0 && currentPrice >= tpTrigger && runtime.SlOrderId.HasValue)
                {
                    var slTicket = Algorithm.Transactions.GetOrderTicket(runtime.SlOrderId.Value);
                    if (slTicket != null && slTicket.Status == OrderStatus.Submitted)
                    {
                        var resp = slTicket.Cancel("Swap-to-TP");
                        if (resp.IsSuccess)
                        {
                            runtime.PendingSwapToTp = true;
                            RecordExitAction(entryId, slice.Time);
                            if (_config.DebugMode) SmartLog($"[SWAP→TP E{entryId}] SL cancel requested (TP trigger @ {tpTrigger:F2})");
                        }
                    }
                }

                // Skip trailing logic if swap is pending
                if (runtime.PendingSwapToTp) continue;

                // === TRAILING STOP LOGIC ===
                // Arm trailing when price reaches threshold
                if (!runtime.TrailingArmed && tpDist > 0 && unrealizedPnL >= tpDist * _config.TrailingStartFraction && runtime.SlOrderId.HasValue)
                {
                    var newStopPrice = PriceRounding.FloorToTick(trade.OpenPrice, tick);
                    if (TryUpdateStop(runtime.SlOrderId.Value, newStopPrice, stopLimitBuffer))
                    {
                        runtime.TrailingArmed = true;
                        runtime.CurrentStopPrice = newStopPrice;
                        if (_config.DebugMode) SmartLog($"[TRAIL ARMED E{entryId}] Breakeven @ ${newStopPrice:F2}");
                    }
                }
                
                // Update trailing stop if armed
                if (runtime.TrailingArmed && runtime.SlOrderId.HasValue)
                {
                    var newStop = ComputeTrailingStop(runtime, tpDist, tick);
                    if (newStop.HasValue && TryUpdateStop(runtime.SlOrderId.Value, newStop.Value, stopLimitBuffer))
                    {
                        var oldStop = runtime.CurrentStopPrice;
                        runtime.CurrentStopPrice = newStop.Value;
                        if (_config.DebugMode && entryId % 5 == 0)
                        {
                            SmartLog($"[TRAIL E{entryId}] ${oldStop:F2} → ${newStop.Value:F2}");
                        }
                    }
                }
            }
        }

        /// <summary>
        /// Handle order fills - place child orders on entry fill, cancel siblings on exit fill
        /// Called by Main.cs via TrackOrderFilled when any order is filled
        /// </summary>
        protected override void OnOrderFilled(OrderEvent orderEvent)
        {
            var orderId = orderEvent.OrderId;
            var ticket = Algorithm.Transactions.GetOrderTicket(orderId);
            var tag = ticket?.Tag ?? "";

            // Unified entry ID resolution
            if (!TryExtractEntryIdFromTagOrMap(tag, orderId, out var entryId, out var isEntry))
            {
                return; // Not our order
            }

            // Find the trade record
            var trade = TradeTracker.AllTrades.FirstOrDefault(t => t.OrderId == entryId.ToString());
            if (trade == null)
            {
                if (_config.DebugMode) SmartWarn($"[FILL] Trade {entryId} not found");
                return;
            }

            // Entry order filled - place only SL (no TP yet for single-active-exit OCO)
            if (isEntry && trade.Status == "Open")
            {
                trade.TryGetDecimal(KEY_STOP_DIST, out var stopDist);
                trade.TryGetDecimal(KEY_TP_DIST, out var tpDist);
                
                var tpPrice = orderEvent.FillPrice + tpDist;
                var slPrice = orderEvent.FillPrice - stopDist;
                var tick = PriceRounding.GetMinPriceVariation(Algorithm.Securities, _underlying);
                var stopLimitBuffer = _config.StopLimitBufferTicks * tick;

                trade.SetDetail(KEY_TP_TRIGGER_PRICE, tpPrice);

                // Place initial protective stop-limit order
                // Diagnostic logging for stop-limit parameters
                if (_config.DebugMode)
                {
                    SmartLog($"[ENTRY SL E{entryId}] Qty:{-trade.Quantity} | Fill:{orderEvent.FillPrice:F4} | Stop:{slPrice:F4} | Limit:{slPrice - stopLimitBuffer:F4} | Buffer:{stopLimitBuffer:F4} | Tick:{tick:F4}");
                }
                
                var slTicket = StopLimitOrder(_underlying, -trade.Quantity, slPrice, slPrice - stopLimitBuffer, tag: $"{TAG_SL_PREFIX}{entryId}");
                if (slTicket != null)
                {
                    trade.SetDetail(KEY_SL_ORDER_ID, slTicket.OrderId);
                    MapChild(slTicket.OrderId, entryId);
                    RecordExitAction(entryId, Algorithm.Time);
                    
                    // FSM: Transition to ExitActive after placing SL
                    SetExitState(trade, ExitState.ExitActive);
                    
                    // Initialize runtime state
                    _runtimeByEntryId[entryId] = new RuntimeState
                    {
                        HighestPriceSinceEntry = orderEvent.FillPrice,
                        TrailingArmed = false,
                        CurrentStopPrice = slPrice,
                        PendingSwapToTp = false,
                        SlOrderId = slTicket.OrderId,
                        TpOrderId = null
                    };
                    
                    TradePersistence.SaveTrades(TradeTracker);
                    
                    // Update entry order tag to include order id for CSV traceability
                    var entryTicket = Algorithm.Transactions.GetOrderTicket(entryId);
                    if (entryTicket != null)
                    {
                        entryTicket.Update(new UpdateOrderFields { Tag = $"{TAG_ENTRY}_E{entryId}" });
                    }
                }
                else
                {
                    SmartError($"[ENTRY SL E{entryId}] StopLimitOrder returned null - no protective stop placed!");
                }

                // Trade highlight
                var logger = (dynamic)Logger;
                var scope = logger.BuildScope(_underlying, entryId);
                logger.AddHighlightMetrics(scope, new Dictionary<string, object>
                {
                    {"ATR", trade.TryGetDecimal(KEY_ATR_AT_ENTRY, out var atr) ? atr : 0},
                    {"SL", slPrice},
                    {"TP", tpPrice}
                });
                logger.HighlightTradeEvent(
                    nameof(AdaptiveScalperTemplate),
                    nameof(OnOrderFilled),
                    HighlightKind.Entry,
                    TradeHighlightFormatter.BuildEntry(_underlying.Value, orderEvent.UtcTime, orderEvent.FillPrice, trade.Quantity, slPrice, tpPrice, orderId),
                    scope
                );

                if (_config.DebugMode && _totalEntriesToday % 10 == 1)
                {
                    SmartLog($"[FILL E{entryId}] Entry ${orderEvent.FillPrice:F2} | SL ${slPrice:F2} | TP ${tpPrice:F2} (pending)");
                }

                return;
            }

            // Get runtime state
            var hasRuntime = _runtimeByEntryId.TryGetValue(entryId, out var runtime);

            // TP order filled - cancel SL and close trade
            if (tag.StartsWith(TAG_TP_PREFIX) && trade.Status == "Open")
            {
                // FSM GUARD: Check if already filling/closed
                var exitState = GetExitState(trade);
                if (exitState == ExitState.ExitFilling || exitState == ExitState.Closed)
                {
                    SmartWarn($"[FSM BLOCK TP E{entryId}] State={exitState} - TP fill blocked (duplicate event ignored)");
                    return;
                }
                
                // FSM: Transition to ExitFilling immediately (before cancel)
                SetExitState(trade, ExitState.ExitFilling);
                
                var pnl = (orderEvent.FillPrice - trade.OpenPrice) * trade.Quantity;
                _dailyPnL += pnl;
                _totalExitsToday++;

                // Cancel SL if still active
                if (hasRuntime && runtime.SlOrderId.HasValue)
                {
                    var slTicket = Algorithm.Transactions.GetOrderTicket(runtime.SlOrderId.Value);
                    if (slTicket != null && slTicket.Status == OrderStatus.Submitted) slTicket.Cancel();
                }

                // Trade highlight
                var logger = (dynamic)Logger;
                var scope = logger.BuildScope(_underlying, entryId);
                logger.AddHighlightMetrics(scope, new Dictionary<string, object> { {"Reason", "TP"} });
                logger.HighlightTradeEvent(nameof(AdaptiveScalperTemplate), nameof(OnOrderFilled), HighlightKind.TakeProfit,
                    TradeHighlightFormatter.BuildExit(_underlying.Value, orderEvent.UtcTime, orderEvent.FillPrice, pnl, ExitType.TakeProfit, orderId), scope);

                if (_config.DebugMode && _totalExitsToday % 10 == 1)
                {
                    SmartLog($"[TP EXIT E{entryId}] ${trade.OpenPrice:F2} → ${orderEvent.FillPrice:F2} | PnL: ${pnl:F2}");
                }

                TrackPositionClosed(entryId.ToString(), orderEvent.FillPrice, pnl);
                _childToEntryMap.Remove(orderId);
                if (IsTradeClosed(entryId, trade)) ClearTradeMappings(entryId, runtime);
                
                // FSM: Mark as closed
                SetExitState(trade, ExitState.Closed);
                
                AdjustThrottle();
                return;
            }

            // Stop-limit filled - cancel TP and close trade
            if (tag.StartsWith(TAG_SL_PREFIX) && trade.Status == "Open")
            {
                // FSM GUARD: Check if already filling/closed
                var exitState = GetExitState(trade);
                if (exitState == ExitState.ExitFilling || exitState == ExitState.Closed)
                {
                    SmartWarn($"[FSM BLOCK SL E{entryId}] State={exitState} - SL fill blocked (duplicate event ignored)");
                    return;
                }
                
                // FSM: Transition to ExitFilling immediately (before cancel)
                SetExitState(trade, ExitState.ExitFilling);
                
                var pnl = (orderEvent.FillPrice - trade.OpenPrice) * trade.Quantity;
                _dailyPnL += pnl;
                _totalExitsToday++;

                // Cancel TP if still active
                if (hasRuntime && runtime.TpOrderId.HasValue)
                {
                    var tpTicket = Algorithm.Transactions.GetOrderTicket(runtime.TpOrderId.Value);
                    if (tpTicket != null && tpTicket.Status == OrderStatus.Submitted) tpTicket.Cancel();
                }

                // Trade highlight
                var trailingArmed = hasRuntime && runtime.TrailingArmed;
                var logger = (dynamic)Logger;
                var scope = logger.BuildScope(_underlying, entryId);
                var exitType = trailingArmed ? "TrailHit" : "StopHit";
                logger.AddHighlightMetrics(scope, new Dictionary<string, object> { {"Reason", exitType} });
                logger.HighlightTradeEvent(nameof(AdaptiveScalperTemplate), nameof(OnOrderFilled),
                    trailingArmed ? HighlightKind.TrailingStop : HighlightKind.StopLoss,
                    TradeHighlightFormatter.BuildExit(_underlying.Value, orderEvent.UtcTime, orderEvent.FillPrice, pnl,
                        trailingArmed ? ExitType.TrailingStop : ExitType.StopLoss, orderId), scope);

                if (_config.DebugMode && _totalExitsToday % 10 == 1)
                {
                    var label = trailingArmed ? "TRAIL" : "SL";
                    SmartLog($"[{label} EXIT E{entryId}] ${trade.OpenPrice:F2} → ${orderEvent.FillPrice:F2} | PnL: ${pnl:F2}");
                }

                TrackPositionClosed(entryId.ToString(), orderEvent.FillPrice, pnl);
                _childToEntryMap.Remove(orderId);
                if (IsTradeClosed(entryId, trade)) ClearTradeMappings(entryId, runtime);
                
                // FSM: Mark as closed
                SetExitState(trade, ExitState.Closed);
                
                AdjustThrottle();
                return;
            }

            // Handle cancelled orders
            if (orderEvent.Status == OrderStatus.Canceled)
            {
                // Skip if this is a swap-to-TP cancel (handled in OnOrderEventRouted)
                if (hasRuntime && runtime.PendingSwapToTp && runtime.SlOrderId.HasValue && orderEvent.OrderId == runtime.SlOrderId.Value)
                {
                    return;
                }

                _childToEntryMap.Remove(orderId);
                if (IsTradeClosed(entryId, trade)) ClearTradeMappings(entryId, runtime);
            }
        }

        /// <summary>
        /// Receive ALL order events (fills, cancels, partials) for event-time logic
        /// Handles swap-to-TP cancel acknowledgements and ensures exit orders exist (O(1) lookup)
        /// Gated to prevent event loops during kill-switch or rapid submissions
        /// </summary>
        public override void OnOrderEventRouted(OrderEvent orderEvent)
        {
            // Handle SL cancel acknowledgements for swap-to-TP (O(1) via child map)
            if (orderEvent.Status == OrderStatus.Canceled && _childToEntryMap.TryGetValue(orderEvent.OrderId, out var entryId))
            {
                if (_runtimeByEntryId.TryGetValue(entryId, out var runtime) && runtime.PendingSwapToTp && runtime.SlOrderId.HasValue && orderEvent.OrderId == runtime.SlOrderId.Value)
                {
                    // This is our SL cancel - place TP now
                    var trade = TradeTracker.AllTrades.FirstOrDefault(t => t.OrderId == entryId.ToString());
                    if (trade != null && trade.TryGetDecimal(KEY_TP_TRIGGER_PRICE, out var tpPrice))
                    {
                        var tpTicket = LimitOrder(_underlying, -trade.Quantity, tpPrice, tag: $"{TAG_TP_PREFIX}{entryId}");
                        if (tpTicket != null)
                        {
                            runtime.TpOrderId = tpTicket.OrderId;
                            runtime.PendingSwapToTp = false;
                            trade.SetDetail(KEY_TP_ORDER_ID, tpTicket.OrderId);
                            MapChild(tpTicket.OrderId, entryId);
                            RecordExitAction(entryId, Algorithm.Time);
                            
                            // FSM: Remain in ExitActive (SL canceled, TP now active)
                            SetExitState(trade, ExitState.ExitActive);
                            
                            TradePersistence.SaveTrades(TradeTracker);
                            if (_config.DebugMode) SmartLog($"[SWAP→TP E{entryId}] TP placed @ {tpPrice:F2}");
                        }
                    }
                }
            }
            
            // CONSERVATIVE PATCH: Removed ensure from event loop to prevent race conditions
            // EnsureExitOrders is now called from OnExecute with proper cooldowns and debouncing
        }

        /// <summary>
        /// Safety net to ensure every open trade has at least one active exit order (SL or TP)
        /// Heals any drift caused by race conditions or missed events
        /// CONSERVATIVE PATCH: Uses cooldowns, TTLs, and open order scanning to prevent duplicates
        /// </summary>
        private void EnsureExitOrders()
        {
            var openTrades = GetOpenTradesForSymbol();
            if (openTrades.Count == 0) return;
            
            var now = Algorithm.Time;
            var currentPrice = Securities[_underlying].Price;
            var tick = PriceRounding.GetMinPriceVariation(Algorithm.Securities, _underlying);
            var stopLimitBuffer = _config.StopLimitBufferTicks * tick;
            
            foreach (var trade in openTrades)
            {
                var entryId = int.Parse(trade.OrderId);
                var runtime = GetOrCreateRuntime(trade);
                
                // FSM GUARD: Skip if exit is already filling or closed
                var exitState = GetExitState(trade);
                if (exitState == ExitState.ExitFilling || exitState == ExitState.Closed)
                {
                    if (_config.DebugMode && !_ensureExitLogged.Contains(entryId))
                    {
                        SmartDebug($"[ENSURE SKIP E{entryId}] FSM state={exitState}");
                        _ensureExitLogged.Add(entryId);
                    }
                    continue;
                }
                
                // Per-entry cooldown check - skip if we recently acted on this entry
                if (_lastExitActionAt.TryGetValue(entryId, out var lastAction))
                {
                    var msSinceAction = (now - lastAction).TotalMilliseconds;
                    if (msSinceAction < ENSURE_COOLDOWN_MS)
                    {
                        if (_config.DebugMode && !_ensureExitLogged.Contains(entryId))
                        {
                            SmartDebug($"[ENSURE SKIP E{entryId}] Cooldown ({msSinceAction:F0}ms < {ENSURE_COOLDOWN_MS}ms)");
                            _ensureExitLogged.Add(entryId);
                        }
                        continue;
                    }
                }
                
                // Check if we have at least one active exit order (using safe check)
                var hasActiveSL = runtime.SlOrderId.HasValue && IsOrderActiveSafe(runtime.SlOrderId.Value, entryId, now);
                var hasActiveTP = runtime.TpOrderId.HasValue && IsOrderActiveSafe(runtime.TpOrderId.Value, entryId, now);
                
                if (!hasActiveSL && !hasActiveTP && !runtime.PendingSwapToTp)
                {
                    trade.TryGetDecimal(KEY_TP_TRIGGER_PRICE, out var tpTrigger);
                    trade.TryGetDecimal(KEY_STOP_DIST, out var stopDist);
                    
                    // Debounced logging - only log once per entry ID
                    bool shouldLog = !_ensureExitLogged.Contains(entryId);
                    if (shouldLog)
                    {
                        _ensureExitLogged.Add(entryId);
                        SmartLog($"[ENSURE E{entryId}] Missing exit orders - placing now");
                    }
                    
                    // Place TP if price reached trigger, else place SL
                    if (tpTrigger > 0 && currentPrice >= tpTrigger)
                    {
                        // Idempotence guard: check for existing TP orders with this tag
                        var tpTag = $"{TAG_TP_PREFIX}{entryId}";
                        if (HasOpenOrderWithTag(tpTag))
                        {
                            if (shouldLog) SmartDebug($"[ENSURE SKIP E{entryId}] TP order already exists");
                            continue;
                        }
                        
                        var tpTicket = LimitOrder(_underlying, -trade.Quantity, tpTrigger, tag: tpTag);
                        if (tpTicket != null)
                        {
                            runtime.TpOrderId = tpTicket.OrderId;
                            trade.SetDetail(KEY_TP_ORDER_ID, tpTicket.OrderId);
                            MapChild(tpTicket.OrderId, entryId);
                            RecordExitAction(entryId, now);
                            
                            // FSM: Set ExitActive after placing TP
                            SetExitState(trade, ExitState.ExitActive);
                            
                            TradePersistence.SaveTrades(TradeTracker);
                            if (shouldLog) SmartLog($"[ENSURE E{entryId}] TP @ {tpTrigger:F2}");
                        }
                    }
                    else if (stopDist > 0)
                    {
                        // Idempotence guard: check for existing SL orders with this tag
                        var slTag = $"{TAG_SL_PREFIX}{entryId}";
                        if (HasOpenOrderWithTag(slTag))
                        {
                            if (shouldLog) SmartDebug($"[ENSURE SKIP E{entryId}] SL order already exists");
                            continue;
                        }
                        
                        var slPrice = trade.OpenPrice - stopDist;
                        var slTicket = StopLimitOrder(_underlying, -trade.Quantity, slPrice, slPrice - stopLimitBuffer, tag: slTag);
                        if (slTicket != null)
                        {
                            runtime.SlOrderId = slTicket.OrderId;
                            runtime.CurrentStopPrice = slPrice;
                            trade.SetDetail(KEY_SL_ORDER_ID, slTicket.OrderId);
                            MapChild(slTicket.OrderId, entryId);
                            RecordExitAction(entryId, now);
                            
                            // FSM: Set ExitActive after placing SL
                            SetExitState(trade, ExitState.ExitActive);
                            
                            TradePersistence.SaveTrades(TradeTracker);
                            if (shouldLog) SmartLog($"[ENSURE E{entryId}] SL @ {slPrice:F2}");
                        }
                    }
                }
            }
        }

        /// <summary>
        /// Get open trades for this strategy and symbol from TradeTracker
        /// </summary>
        private List<TradeRecord> GetOpenTradesForSymbol()
        {
            return TradeTracker.OpenTrades
                .Where(t => t.Strategy == Name && t.Symbol == _underlying.Value)
                .ToList();
        }

        /// <summary>
        /// Map child order ID to parent entry ID
        /// </summary>
        private void MapChild(int childOrderId, int entryId)
        {
            _childToEntryMap[childOrderId] = entryId;
        }

        /// <summary>
        /// Check if both TP and SL orders are in terminal states
        /// </summary>
        private bool IsTradeClosed(int entryId, TradeRecord trade)
        {
            // Get runtime state for order IDs
            if (!_runtimeByEntryId.TryGetValue(entryId, out var runtime))
            {
                // No runtime state - check TradeRecord details
                trade.TryGetInt(KEY_TP_ORDER_ID, out var tpId);
                trade.TryGetInt(KEY_SL_ORDER_ID, out var slId);
                
                return (tpId == 0 || IsOrderTerminal(tpId)) && (slId == 0 || IsOrderTerminal(slId));
            }

            return (!runtime.TpOrderId.HasValue || IsOrderTerminal(runtime.TpOrderId.Value)) &&
                   (!runtime.SlOrderId.HasValue || IsOrderTerminal(runtime.SlOrderId.Value));
        }

        /// <summary>
        /// Check if order is in terminal state
        /// </summary>
        private bool IsOrderTerminal(int orderId)
        {
            var ticket = Algorithm.Transactions.GetOrderTicket(orderId);
            if (ticket == null) return true;
            
            return ticket.Status == OrderStatus.Filled || 
                   ticket.Status == OrderStatus.Canceled ||
                   ticket.Status == OrderStatus.Invalid;
        }

        /// <summary>
        /// Check if order is active for ensure purposes - safer than IsOrderTerminal
        /// Treats recently-placed orders as active even if ticket lookup returns null (race condition protection)
        /// </summary>
        private bool IsOrderActiveSafe(int orderId, int entryId, DateTime now)
        {
            var ticket = Algorithm.Transactions.GetOrderTicket(orderId);
            
            // If we have a ticket and it's submitted, it's definitely active
            if (ticket != null && ticket.Status == OrderStatus.Submitted)
            {
                return true;
            }
            
            // If we recently took action on this entry, treat unknown tickets as active (TTL grace period)
            if (_lastExitActionAt.TryGetValue(entryId, out var lastAction))
            {
                var msSinceAction = (now - lastAction).TotalMilliseconds;
                if (msSinceAction < UNKNOWN_TICKET_TTL_MS)
                {
                    // Within grace period - assume active even if ticket is null
                    return ticket == null || ticket.Status == OrderStatus.Submitted;
                }
            }
            
            // Outside grace period - trust the ticket status
            return false;
        }

        /// <summary>
        /// Check if any open order exists with the specified tag prefix (idempotence guard)
        /// </summary>
        private bool HasOpenOrderWithTag(string tagPrefix)
        {
            var openOrders = Algorithm.Transactions.GetOpenOrders(_underlying);
            return openOrders.Any(o => o.Tag != null && o.Tag.StartsWith(tagPrefix));
        }

        /// <summary>
        /// Clear runtime and child mappings for a trade
        /// </summary>
        private void ClearTradeMappings(int entryId, RuntimeState runtime)
        {
            _runtimeByEntryId.Remove(entryId);
            
            if (runtime != null)
            {
                if (runtime.TpOrderId.HasValue)
                    _childToEntryMap.Remove(runtime.TpOrderId.Value);
                if (runtime.SlOrderId.HasValue)
                    _childToEntryMap.Remove(runtime.SlOrderId.Value);
            }
        }

        /// <summary>
        /// Check if we can enter a new trade now based on rate limits
        /// </summary>
        private bool CanEnterNow(DateTime currentTime)
        {
            // Entry cooldown check
            var msSinceLastEntry = (currentTime - _lastEntryTime).TotalMilliseconds;
            if (msSinceLastEntry < _config.EntryCooldownMs)
            {
                return false;
            }

            // Orders per minute rate limit
            var oneMinuteAgo = currentTime.AddMinutes(-1);
            while (_orderTimestamps.Count > 0 && _orderTimestamps.Peek() < oneMinuteAgo)
            {
                _orderTimestamps.Dequeue();
            }

            if (_orderTimestamps.Count >= _config.MaxOrdersPerMinute)
            {
                return false;
            }

            _orderTimestamps.Enqueue(currentTime);
            return true;
        }

        /// <summary>
        /// Adjust throttle based on daily PnL
        /// </summary>
        private void AdjustThrottle()
        {
            // Progressive throttle adjustment as losses build
            if (_dailyPnL <= _config.DailyKillSwitch * 0.5m)
            {
                var lossRatio = Math.Abs(_dailyPnL / _config.DailyKillSwitch);
                _tradeThrottle = Math.Max(_config.MinThrottle, 1m - lossRatio * _config.ThrottleDecay);

                if (_config.DebugMode && _totalExitsToday % 20 == 0)
                {
                    SmartLog($"[THROTTLE] {_tradeThrottle:P0} (PnL: ${_dailyPnL:F2})");
                }
            }

            // Kill switch - halt trading for the day
            if (_dailyPnL <= _config.DailyKillSwitch)
            {
                SmartLog($"[KILL SWITCH] Loss limit ${_dailyPnL:F2} - halting");
                
                CancelAllExitOrdersForOpenTrades();
                Algorithm.Liquidate(_underlying);
                _runtimeByEntryId.Clear();
                _childToEntryMap.Clear();
                _haltedForDay = true;
            }
        }

        private void ResetDailyTracking()
        {
            SmartLog($"=== Daily reset {Algorithm.Time.Date:yyyy-MM-dd} | Entries: {_totalEntriesToday}, Exits: {_totalExitsToday}, PnL: ${_dailyPnL:F2} ===");
            
            // Cross-check with TradeTracker
            if (TradePersistence != null)
            {
                var summary = TradePersistence.GetTradeSummary(TradeTracker);
                var openCount = GetOpenTradesForSymbol().Count;
                if (_config.DebugMode)
                {
                    SmartLog($"Tracker: Working={summary.WorkingCount}, Open={summary.OpenCount}, Closed={summary.ClosedCount}");
                }
                if (openCount != summary.OpenCount)
                {
                    SmartWarn($"Trade divergence: Local={openCount}, Tracker={summary.OpenCount}");
                }
            }
            
            // Reset daily counters
            _dailyPnL = 0m;
            _tradeThrottle = 1.0m;
            _haltedForDay = false;
            _totalEntriesToday = 0;
            _totalExitsToday = 0;
            _orderTimestamps.Clear();
            _ensureExitLogged.Clear();
            _lastExitActionAt.Clear();
            
            // CONSERVATIVE PATCH: Rehydrate runtime state from TradeTracker instead of clearing
            // This prevents ensure storms at day open and reduces divergence
            _runtimeByEntryId.Clear();
            _childToEntryMap.Clear();
            
            var openTrades = GetOpenTradesForSymbol();
            foreach (var trade in openTrades)
            {
                // GetOrCreateRuntime will rebuild state from TradeRecord
                GetOrCreateRuntime(trade);
            }
            
            if (_config.DebugMode && openTrades.Count > 0)
            {
                SmartLog($"[RESET] Rehydrated runtime for {openTrades.Count} open trades");
            }
        }

        /// <summary>
        /// Override base exit signal handler to prevent unintended liquidations.
        /// AdaptiveScalper manages exits strictly via event-driven SL/TP only.
        /// </summary>
        protected override void OnExitSignal(Symbol symbol, string reason)
        {
            // Ignore global exit signals to avoid unintended liquidations
            if (_config.DebugMode)
            {
                SmartDebug($"[EXIT IGNORED] {symbol}: {reason} (event-driven SL/TP only)");
            }
        }

        protected override void OnGetPerformanceMetrics(Dictionary<string, double> metrics)
        {
            // Core metrics for monitoring and dashboard
            metrics["DailyPnL"] = (double)_dailyPnL;
            metrics["TradeThrottle"] = (double)_tradeThrottle;
            metrics["HaltedForDay"] = _haltedForDay ? 1.0 : 0.0;
            metrics["ATRValue"] = _atr.IsReady ? (double)_atr.Current.Value : 0.0;
            metrics["OpenTrades"] = GetOpenTradesForSymbol().Count;
            metrics["TotalEntriesToday"] = _totalEntriesToday;
            metrics["TotalExitsToday"] = _totalExitsToday;

            if (_atrBaseline.IsReady)
            {
                metrics["ATRBaseline"] = (double)_atrBaseline.Average();
            }
        }
    }
}
using System;
using System.Collections.Generic;
using System.Linq;
using QuantConnect;
using QuantConnect.Algorithm;
using QuantConnect.Data;
using QuantConnect.Securities;
using QuantConnect.Securities.Future;
using CoreAlgo.Architecture.Core.Interfaces;

namespace CoreAlgo.Architecture.QC.Helpers
{
    /// <summary>
    /// Simple static helper for asset type detection and appropriate QC method calls.
    /// Handles the complexity of different asset types (equity, index, future) so strategies
    /// can switch between SPY/SPX/QQQ/ES with just a configuration change.
    /// </summary>
    public static class AssetManager
    {
        /// <summary>
        /// Cash indices that require AddIndex() instead of AddEquity()
        /// These are the QuantConnect supported cash indices
        /// </summary>
        private static readonly HashSet<string> CashIndices = new HashSet<string>
        {
            "SPX", "VIX", "NDX", "RUT", "DJX"
        };

        /// <summary>
        /// Future symbols that require AddFuture() instead of AddEquity()
        /// Expanded to include major futures across all asset classes
        /// </summary>
        private static readonly HashSet<string> Futures = new HashSet<string>
        {
            // Equity Index Futures
            "ES", "NQ", "YM", "RTY", "EMD", "NKD",
            
            // Energy Futures
            "CL", "NG", "RB", "HO", "BZ",
            
            // Metal Futures  
            "GC", "SI", "HG", "PA", "PL",
            
            // Agricultural Futures
            "ZC", "ZS", "ZW", "ZM", "ZL", "KC", "CT", "SB", "CC", "OJ",
            
            // Interest Rate & Bond Futures
            "ZB", "ZN", "ZF", "TU", "UB", "ED", "SR1", "SR3",
            
            // Currency Futures
            "6E", "6J", "6B", "6S", "6C", "6A", "6N", "6M", "E7", "J7",
            
            // Volatility Futures
            "VX",
            
            // Crypto Futures (if supported)
            "BTC", "ETH"
        };

        /// <summary>
        /// Add an asset to the algorithm using the appropriate QC method based on asset type.
        /// Automatically detects whether to use AddEquity(), AddIndex(), or AddFuture().
        /// For futures, supports continuous contracts with proper data normalization.
        /// </summary>
        /// <param name="context">The algorithm context providing access to algorithm and logger</param>
        /// <param name="symbol">The symbol to add (e.g., "SPY", "SPX", "ES")</param>
        /// <param name="resolution">Data resolution (default: Minute)</param>
        /// <param name="useContinuousContract">For futures: use continuous contract (default: true)</param>
        /// <param name="contractDepthOffset">For futures: contract depth (0=front month, 1=next month, default: 0)</param>
        /// <returns>The Security object for the added asset</returns>
        /// <exception cref="ArgumentNullException">If context or symbol is null</exception>
        /// <exception cref="ArgumentException">If symbol is empty or whitespace</exception>
        public static Security AddAsset(IAlgorithmContext context, string symbol, Resolution resolution = Resolution.Minute, 
            bool useContinuousContract = true, int contractDepthOffset = 0, bool extendedMarketHours = false)
        {
            if (context == null)
                throw new ArgumentNullException(nameof(context));
            
            if (string.IsNullOrWhiteSpace(symbol))
                throw new ArgumentException("Symbol cannot be null or empty", nameof(symbol));

            var upperSymbol = symbol.ToUpperInvariant();

            try
            {
                Security addedSecurity;
                
                // Detect asset type and call appropriate QC method
                if (CashIndices.Contains(upperSymbol))
                {
                    ((dynamic)context.Logger).Info($"AssetManager: Adding index {upperSymbol} using AddIndex()");
                    addedSecurity = context.Algorithm.AddIndex(upperSymbol, resolution);
                }
                else if (Futures.Contains(upperSymbol))
                {
                    ((dynamic)context.Logger).Info($"AssetManager: Adding future {upperSymbol} using AddFuture() with continuous contract: {useContinuousContract}, extendedMarketHours: {extendedMarketHours}");
                    
                    if (useContinuousContract)
                    {
                        // Add future with continuous contract support
                        addedSecurity = context.Algorithm.AddFuture(upperSymbol, resolution, 
                            extendedMarketHours: extendedMarketHours,
                            dataMappingMode: DataMappingMode.OpenInterest,
                            dataNormalizationMode: DataNormalizationMode.BackwardsRatio,
                            contractDepthOffset: contractDepthOffset);
                        
                        ((dynamic)context.Logger).Info($"AssetManager: Continuous contract configured - DataMapping: OpenInterest, DataNormalization: BackwardsRatio, Depth: {contractDepthOffset}");
                    }
                    else
                    {
                        // Simple future addition without continuous contract
                        addedSecurity = context.Algorithm.AddFuture(upperSymbol, resolution, extendedMarketHours: extendedMarketHours);
                    }
                }
                else
                {
                    ((dynamic)context.Logger).Info($"AssetManager: Adding equity {upperSymbol} using AddEquity()");
                    addedSecurity = context.Algorithm.AddEquity(upperSymbol, resolution);
                }

                // Verify the security was successfully added and log data availability
                if (addedSecurity != null)
                {
                    var isInSecurities = context.Algorithm.Securities.ContainsKey(addedSecurity.Symbol);
                    ((dynamic)context.Logger).Info($"AssetManager: [SUCCESS] {upperSymbol} successfully added to Securities collection: {isInSecurities}");
                    ((dynamic)context.Logger).Info($"AssetManager: Security details - Type: {addedSecurity.Type}, Resolution: {addedSecurity.Subscriptions.GetHighestResolution()}, Exchange: {addedSecurity.Exchange}");
                    
                    // Check if security has current price data (indicates data subscription is working)
                    var hasPrice = addedSecurity.Price > 0;
                    var priceStatus = hasPrice ? $"${addedSecurity.Price:F2}" : "No price data yet";
                    ((dynamic)context.Logger).Info($"AssetManager: Price data status: {priceStatus}");
                    
                    if (!hasPrice)
                    {
                        ((dynamic)context.Logger).Info($"AssetManager: [WARNING] No price data yet for {upperSymbol} - this is normal during initialization, data should arrive during backtest");
                    }
                }
                else
                {
                    ((dynamic)context.Logger).Error($"AssetManager: [FAILED] Failed to add {upperSymbol} - returned null security");
                }

                return addedSecurity;
            }
            catch (Exception ex)
            {
                ((dynamic)context.Logger).Error($"AssetManager: Failed to add asset {upperSymbol}: {ex.Message}");
                throw;
            }
        }

        /// <summary>
        /// Add an options chain for the underlying asset using the appropriate QC method.
        /// Handles special cases like SPX->SPXW mapping automatically.
        /// </summary>
        /// <param name="context">The algorithm context providing access to algorithm and logger</param>
        /// <param name="underlying">The underlying security</param>
        /// <param name="resolution">Data resolution (default: Minute)</param>
        /// <returns>The Symbol for the options chain</returns>
        /// <exception cref="ArgumentNullException">If context or underlying is null</exception>
        public static Symbol AddOptionsChain(IAlgorithmContext context, Security underlying, Resolution resolution = Resolution.Minute)
        {
            if (context == null)
                throw new ArgumentNullException(nameof(context));
            
            if (underlying == null)
                throw new ArgumentNullException(nameof(underlying));

            var symbol = underlying.Symbol.Value.ToUpperInvariant();

            try
            {
                Symbol optionSymbol;
                
                if (underlying.Type == SecurityType.Index)
                {
                    // Special case for SPX: use SPXW (weekly) options
                    if (symbol == "SPX")
                    {
                        ((dynamic)context.Logger).Info($"AssetManager: Adding SPX index options using SPXW with {resolution} resolution");
                        // Pre-add diagnostics for universe and requested resolution
                        ((dynamic)context.Logger).Info($"AssetManager: [UNIVERSE] UniverseSettings.Resolution={context.Algorithm.UniverseSettings.Resolution}, FillForward={context.Algorithm.UniverseSettings.FillForward}, ExtendedMktHours={context.Algorithm.UniverseSettings.ExtendedMarketHours}");
                        
                        // Force minute resolution explicitly for intraday option trading
                        var requestedResolution = resolution == Resolution.Daily ? Resolution.Minute : resolution;
                        if (requestedResolution != resolution)
                        {
                            ((dynamic)context.Logger).Info($"AssetManager: [RESOLUTION OVERRIDE] Changing from {resolution} to {requestedResolution} for intraday option data");
                        }
                        
                        optionSymbol = context.Algorithm.AddIndexOption(underlying.Symbol, "SPXW", requestedResolution).Symbol;
                        ((dynamic)context.Logger).Info($"AssetManager: [RESOLUTION VERIFICATION] Requested: {requestedResolution}");
                    }
                    else
                    {
                        // Other indices use standard index options
                        ((dynamic)context.Logger).Info($"AssetManager: Adding index options for {symbol} with {resolution} resolution");
                        // Pre-add diagnostics for universe and requested resolution
                        ((dynamic)context.Logger).Info($"AssetManager: [UNIVERSE] UniverseSettings.Resolution={context.Algorithm.UniverseSettings.Resolution}, FillForward={context.Algorithm.UniverseSettings.FillForward}, ExtendedMktHours={context.Algorithm.UniverseSettings.ExtendedMarketHours}");
                        
                        // Force minute resolution explicitly for intraday option trading
                        var requestedResolution = resolution == Resolution.Daily ? Resolution.Minute : resolution;
                        if (requestedResolution != resolution)
                        {
                            ((dynamic)context.Logger).Info($"AssetManager: [RESOLUTION OVERRIDE] Changing from {resolution} to {requestedResolution} for intraday option data");
                        }
                        
                        context.Algorithm.AddIndexOption(underlying.Symbol, requestedResolution);
                        optionSymbol = Symbol.CreateCanonicalOption(underlying.Symbol);
                        ((dynamic)context.Logger).Info($"AssetManager: [RESOLUTION VERIFICATION] Requested: {requestedResolution}");
                    }
                }
                else if (underlying.Type == SecurityType.Future)
                {
                    ((dynamic)context.Logger).Info($"AssetManager: Adding future options for {symbol}");
                    context.Algorithm.AddFutureOption(underlying.Symbol);
                    optionSymbol = Symbol.CreateCanonicalOption(underlying.Symbol);
                }
                else
                {
                    // Equity options
                    ((dynamic)context.Logger).Info($"AssetManager: Adding equity options for {symbol}");
                    optionSymbol = context.Algorithm.AddOption(symbol, resolution).Symbol;
                }

                // [DEBUG] COMPREHENSIVE OPTIONS CHAIN VERIFICATION
                if (optionSymbol != null)
                {
                    ((dynamic)context.Logger).Info($"AssetManager: [SUCCESS] Options chain created for {symbol}");
                    ((dynamic)context.Logger).Info($"AssetManager: [DEBUG] DETAILED Option Symbol Analysis:");
                    ((dynamic)context.Logger).Info($"   optionSymbol: {optionSymbol}");
                    ((dynamic)context.Logger).Info($"   optionSymbol.Value: {optionSymbol.Value}");
                    ((dynamic)context.Logger).Info($"   optionSymbol.SecurityType: {optionSymbol.SecurityType}");
                    ((dynamic)context.Logger).Info($"   optionSymbol.ID: {optionSymbol.ID}");
                    ((dynamic)context.Logger).Info($"   optionSymbol.HasCanonical: {optionSymbol.HasCanonical()}");
                    if (optionSymbol.HasCanonical())
                    {
                        ((dynamic)context.Logger).Info($"   optionSymbol.Canonical: {optionSymbol.Canonical}");
                    }
                    
                    // Check if option symbol is in Securities collection
                    var isInSecurities = context.Algorithm.Securities.ContainsKey(optionSymbol);
                    ((dynamic)context.Logger).Info($"AssetManager: Option chain in Securities collection: {isInSecurities}");
                    
                    // [DEBUG] VERIFY OPTION SECURITY DETAILS
                    if (isInSecurities)
                    {
                        var optionSecurity = context.Algorithm.Securities[optionSymbol];
                        
                        // Enumerate and log all subscriptions with detail
                        var subscriptionResolutions = new System.Collections.Generic.List<Resolution>();
                        foreach (var sub in optionSecurity.Subscriptions)
                        {
                            subscriptionResolutions.Add(sub.Resolution);
                            ((dynamic)context.Logger).Info($"AssetManager: [SUB] DataType={sub.Type?.Name}, Resolution={sub.Resolution}, TickType={sub.TickType}");
                        }
                        var distinctRes = subscriptionResolutions.Distinct().OrderBy(x => x).ToList();
                        
                        ((dynamic)context.Logger).Info($"AssetManager: [DEBUG] Option Security Details:");
                        ((dynamic)context.Logger).Info($"   Type: {optionSecurity.Type}");
                        ((dynamic)context.Logger).Info($"   Subscriptions: {string.Join(", ", distinctRes)}");
                        ((dynamic)context.Logger).Info($"   Exchange: {optionSecurity.Exchange}");
                        ((dynamic)context.Logger).Info($"   IsMarketOpen: {optionSecurity.Exchange.ExchangeOpen}");
                        
                        // Check if we have minute-level subscriptions for intraday trading
                        var hasMinuteData = distinctRes.Contains(Resolution.Minute) || 
                                          distinctRes.Contains(Resolution.Second);
                        
                        if (!hasMinuteData && distinctRes.All(r => r == Resolution.Daily))
                        {
                            ((dynamic)context.Logger).Warning($"AssetManager: [RESOLUTION WARNING] Only Daily resolution subscriptions found");
                            ((dynamic)context.Logger).Warning($"AssetManager: [RESOLUTION WARNING] Intraday option chains may not be available");
                            ((dynamic)context.Logger).Warning($"AssetManager: [HINT] If Minute was requested, ensure UniverseSettings.Resolution=Minute before adding options.");
                        }
                        else
                        {
                            ((dynamic)context.Logger).Info($"AssetManager: [RESOLUTION SUCCESS] Intraday subscriptions available");
                        }
                    }
                    
                    // [DEBUG] LOG ALL SECURITIES THAT CONTAIN OPTION-RELATED SYMBOLS
                    var optionRelatedSecurities = new List<Symbol>();
                    foreach (var sec in context.Algorithm.Securities.Keys)
                    {
                        if (sec.SecurityType == SecurityType.Option || 
                            sec.Value.Contains(symbol) ||
                            sec.Value.Contains("?"))
                        {
                            optionRelatedSecurities.Add(sec);
                            if (optionRelatedSecurities.Count >= 10) break;
                        }
                    }
                    
                    if (optionRelatedSecurities.Any())
                    {
                        ((dynamic)context.Logger).Info($"AssetManager: [DEBUG] Option-related securities in collection ({optionRelatedSecurities.Count}):");
                        foreach (var sec in optionRelatedSecurities)
                        {
                            ((dynamic)context.Logger).Info($"   {sec} (Type: {sec.SecurityType})");
                        }
                    }
                    else
                    {
                        ((dynamic)context.Logger).Info($"AssetManager: [ERROR] No option-related securities found in collection");
                    }
                    
                    // Additional verification for option chain subscription
                    ((dynamic)context.Logger).Info($"AssetManager: Option chain ready for filtering and data feed");
                    // Note: The following line previously suggested LOCAL data; remove misleading data-source implication
                    // ((dynamic)context.Logger).Info($"AssetManager: [DEBUG] Data Source: LOCAL - Using generated option data for testing");
                    ((dynamic)context.Logger).Info($"AssetManager: [TARGET] Symbol to use for slice.OptionChains access: {optionSymbol}");
                }
                else
                {
                    ((dynamic)context.Logger).Error($"AssetManager: [FAILED] Failed to create options chain for {symbol} - returned null symbol");
                }

                return optionSymbol;
            }
            catch (Exception ex)
            {
                ((dynamic)context.Logger).Error($"AssetManager: Failed to add options chain for {symbol}: {ex.Message}");
                throw;
            }
        }

        /// <summary>
        /// Check if a symbol is a supported cash index
        /// </summary>
        /// <param name="symbol">The symbol to check</param>
        /// <returns>True if the symbol is a cash index</returns>
        public static bool IsIndex(string symbol)
        {
            if (string.IsNullOrWhiteSpace(symbol))
                return false;
            
            return CashIndices.Contains(symbol.ToUpperInvariant());
        }

        /// <summary>
        /// Check if a symbol is a supported future
        /// </summary>
        /// <param name="symbol">The symbol to check</param>
        /// <returns>True if the symbol is a future</returns>
        public static bool IsFuture(string symbol)
        {
            if (string.IsNullOrWhiteSpace(symbol))
                return false;
            
            return Futures.Contains(symbol.ToUpperInvariant());
        }

        /// <summary>
        /// Check if a symbol is treated as an equity (default case)
        /// </summary>
        /// <param name="symbol">The symbol to check</param>
        /// <returns>True if the symbol is treated as an equity</returns>
        public static bool IsEquity(string symbol)
        {
            if (string.IsNullOrWhiteSpace(symbol))
                return false;
            
            return !IsIndex(symbol) && !IsFuture(symbol);
        }

        /// <summary>
        /// Get the asset type for a symbol
        /// </summary>
        /// <param name="symbol">The symbol to check</param>
        /// <returns>Asset type as string: "INDEX", "FUTURE", or "EQUITY"</returns>
        public static string GetAssetType(string symbol)
        {
            if (IsIndex(symbol)) return "INDEX";
            if (IsFuture(symbol)) return "FUTURE";
            return "EQUITY";
        }

        /// <summary>
        /// Get a list of all supported cash indices
        /// </summary>
        /// <returns>Array of supported cash index symbols</returns>
        public static string[] GetSupportedIndices()
        {
            return new string[CashIndices.Count];
        }

        /// <summary>
        /// Get a list of all supported futures
        /// </summary>
        /// <returns>Array of supported future symbols</returns>
        public static string[] GetSupportedFutures()
        {
            return Futures.ToArray();
        }

        /// <summary>
        /// Get futures by category for easier strategy configuration
        /// </summary>
        /// <param name="category">Category: "equity", "energy", "metals", "agricultural", "bonds", "currency", "volatility", "crypto"</param>
        /// <returns>Array of futures symbols in the specified category</returns>
        public static string[] GetFuturesByCategory(string category)
        {
            switch (category?.ToLowerInvariant())
            {
                case "equity":
                case "index":
                    return new[] { "ES", "NQ", "YM", "RTY", "EMD", "NKD" };
                    
                case "energy":
                    return new[] { "CL", "NG", "RB", "HO", "BZ" };
                    
                case "metals":
                case "metal":
                    return new[] { "GC", "SI", "HG", "PA", "PL" };
                    
                case "agricultural":
                case "agri":
                case "grains":
                    return new[] { "ZC", "ZS", "ZW", "ZM", "ZL", "KC", "CT", "SB", "CC", "OJ" };
                    
                case "bonds":
                case "rates":
                case "interest":
                    return new[] { "ZB", "ZN", "ZF", "TU", "UB", "ED", "SR1", "SR3" };
                    
                case "currency":
                case "fx":
                    return new[] { "6E", "6J", "6B", "6S", "6C", "6A", "6N", "6M", "E7", "J7" };
                    
                case "volatility":
                case "vol":
                    return new[] { "VX" };
                    
                case "crypto":
                    return new[] { "BTC", "ETH" };
                    
                default:
                    return new string[0];
            }
        }

        /// <summary>
        /// Check if a futures contract rollover is approaching based on days to expiration
        /// </summary>
        /// <param name="context">Algorithm context</param>
        /// <param name="futureSymbol">Future symbol to check</param>
        /// <param name="rolloverDays">Number of days before expiration to trigger rollover (default: 5)</param>
        /// <returns>True if rollover is needed</returns>
        public static bool ShouldRolloverContract(IAlgorithmContext context, Symbol futureSymbol, int rolloverDays = 5)
        {
            if (context?.Algorithm?.Securities == null || !context.Algorithm.Securities.ContainsKey(futureSymbol))
                return false;

            var security = context.Algorithm.Securities[futureSymbol];
            if (security?.Type != SecurityType.Future)
                return false;

            try
            {
                // For continuous contracts, QC handles rollover automatically
                // This method is for manual contract management if needed
                var daysToExpiry = (security.Symbol.ID.Date - context.Algorithm.Time).Days;
                return daysToExpiry <= rolloverDays;
            }
            catch
            {
                return false;
            }
        }

    }
}
using System;
using System.Collections.Generic;
using QuantConnect;
using QuantConnect.Algorithm;
using QuantConnect.Data;
using QuantConnect.Securities;
using CoreAlgo.Architecture.Core.Interfaces;

namespace CoreAlgo.Architecture.QC.Helpers
{
    /// <summary>
    /// Static helper for multi-asset specific parameters and calculations.
    /// Provides asset-specific defaults for strike ranges, position sizing, and option filtering
    /// to enable easy switching between SPX, QQQ, AAPL, etc. with optimal parameters for each.
    /// </summary>
    public static class MultiAssetHelper
    {
        /// <summary>
        /// Asset-specific volatility characteristics for position sizing and strike selection
        /// </summary>
        private static readonly Dictionary<string, AssetProfile> AssetProfiles = new Dictionary<string, AssetProfile>
        {
            // Major Indices - Higher volatility, wider strikes, high margin requirements
            ["SPX"] = new AssetProfile { TypicalVolatility = 0.20m, StrikeWidthMultiplier = 1.5m, MinPosition = 1, MaxPosition = 3, EstimatedMarginMultiplier = 1.7m, MinAccountSize = 120000m },
            ["NDX"] = new AssetProfile { TypicalVolatility = 0.25m, StrikeWidthMultiplier = 1.5m, MinPosition = 1, MaxPosition = 3, EstimatedMarginMultiplier = 2.2m, MinAccountSize = 200000m },
            ["RUT"] = new AssetProfile { TypicalVolatility = 0.30m, StrikeWidthMultiplier = 1.5m, MinPosition = 1, MaxPosition = 3, EstimatedMarginMultiplier = 1.5m, MinAccountSize = 100000m },
            ["VIX"] = new AssetProfile { TypicalVolatility = 0.80m, StrikeWidthMultiplier = 2.0m, MinPosition = 1, MaxPosition = 2, EstimatedMarginMultiplier = 1.0m, MinAccountSize = 50000m },
            
            // ETFs - Moderate volatility, lower margin requirements
            ["SPY"] = new AssetProfile { TypicalVolatility = 0.18m, StrikeWidthMultiplier = 1.0m, MinPosition = 1, MaxPosition = 5, EstimatedMarginMultiplier = 0.3m, MinAccountSize = 25000m },
            ["QQQ"] = new AssetProfile { TypicalVolatility = 0.22m, StrikeWidthMultiplier = 1.0m, MinPosition = 1, MaxPosition = 5, EstimatedMarginMultiplier = 0.4m, MinAccountSize = 30000m },
            ["IWM"] = new AssetProfile { TypicalVolatility = 0.28m, StrikeWidthMultiplier = 1.2m, MinPosition = 1, MaxPosition = 4, EstimatedMarginMultiplier = 0.5m, MinAccountSize = 40000m },
            
            // Individual Stocks - Variable volatility, moderate margin requirements
            ["AAPL"] = new AssetProfile { TypicalVolatility = 0.35m, StrikeWidthMultiplier = 1.0m, MinPosition = 1, MaxPosition = 10, EstimatedMarginMultiplier = 0.4m, MinAccountSize = 20000m },
            ["TSLA"] = new AssetProfile { TypicalVolatility = 0.60m, StrikeWidthMultiplier = 1.5m, MinPosition = 1, MaxPosition = 5, EstimatedMarginMultiplier = 0.6m, MinAccountSize = 50000m },
            ["AMZN"] = new AssetProfile { TypicalVolatility = 0.40m, StrikeWidthMultiplier = 1.2m, MinPosition = 1, MaxPosition = 8, EstimatedMarginMultiplier = 0.5m, MinAccountSize = 30000m },
            ["GOOGL"] = new AssetProfile { TypicalVolatility = 0.35m, StrikeWidthMultiplier = 1.1m, MinPosition = 1, MaxPosition = 8, EstimatedMarginMultiplier = 0.5m, MinAccountSize = 30000m },
            ["MSFT"] = new AssetProfile { TypicalVolatility = 0.30m, StrikeWidthMultiplier = 1.0m, MinPosition = 1, MaxPosition = 10, EstimatedMarginMultiplier = 0.4m, MinAccountSize = 25000m },
            
            // Futures - High volatility, fewer positions, high margin requirements
            ["ES"] = new AssetProfile { TypicalVolatility = 0.22m, StrikeWidthMultiplier = 1.3m, MinPosition = 1, MaxPosition = 3, EstimatedMarginMultiplier = 1.2m, MinAccountSize = 75000m },
            ["NQ"] = new AssetProfile { TypicalVolatility = 0.28m, StrikeWidthMultiplier = 1.4m, MinPosition = 1, MaxPosition = 3, EstimatedMarginMultiplier = 1.4m, MinAccountSize = 100000m },
            ["YM"] = new AssetProfile { TypicalVolatility = 0.20m, StrikeWidthMultiplier = 1.2m, MinPosition = 1, MaxPosition = 3, EstimatedMarginMultiplier = 1.1m, MinAccountSize = 60000m },
        };

        /// <summary>
        /// Add multiple assets with options chains to the algorithm.
        /// Uses AssetManager for each symbol and returns configured securities.
        /// </summary>
        /// <param name="context">The algorithm context providing access to algorithm and logger</param>
        /// <param name="symbols">Array of symbols to add</param>
        /// <param name="resolution">Data resolution</param>
        /// <returns>Dictionary mapping symbols to their Security and options Symbol</returns>
        public static Dictionary<string, (Security Security, Symbol OptionsSymbol)> AddMultiAssetOptions(
            IAlgorithmContext context, 
            string[] symbols, 
            Resolution resolution = Resolution.Minute)
        {
            if (context == null)
                throw new ArgumentNullException(nameof(context));
                
            if (symbols == null || symbols.Length == 0)
                throw new ArgumentException("Symbols array cannot be null or empty", nameof(symbols));

            var result = new Dictionary<string, (Security, Symbol)>();

            foreach (var symbol in symbols)
            {
                try
                {
                    // Add the underlying asset
                    var security = AssetManager.AddAsset(context, symbol, resolution);
                    
                    // Add options chain
                    var optionsSymbol = AssetManager.AddOptionsChain(context, security, resolution);
                    
                    result[symbol.ToUpperInvariant()] = (security, optionsSymbol);
                    
                    ((dynamic)context.Logger).Debug($"MultiAssetHelper: Successfully added {symbol} with options chain");
                }
                catch (Exception ex)
                {
                    ((dynamic)context.Logger).Error($"MultiAssetHelper: Failed to add {symbol}: {ex.Message}");
                    throw;
                }
            }

            return result;
        }

        /// <summary>
        /// Get asset-specific strike width for option selection.
        /// Returns wider ranges for higher volatility assets.
        /// </summary>
        /// <param name="symbol">The underlying symbol</param>
        /// <param name="baseStrikeWidth">Base strike width (in dollar terms or percentage)</param>
        /// <returns>Adjusted strike width for the asset</returns>
        public static decimal GetAssetStrikeWidth(string symbol, decimal baseStrikeWidth)
        {
            if (string.IsNullOrWhiteSpace(symbol))
                return baseStrikeWidth;

            var upperSymbol = symbol.ToUpperInvariant();
            
            if (AssetProfiles.TryGetValue(upperSymbol, out var profile))
            {
                return baseStrikeWidth * profile.StrikeWidthMultiplier;
            }

            // Default for unknown symbols
            return baseStrikeWidth;
        }

        /// <summary>
        /// Get asset-specific position sizing limits.
        /// Different assets have different optimal position counts based on liquidity and volatility.
        /// </summary>
        /// <param name="symbol">The underlying symbol</param>
        /// <param name="totalPortfolioValue">Total portfolio value for percentage-based sizing</param>
        /// <returns>Recommended min and max position sizes</returns>
        public static (int MinPositions, int MaxPositions, decimal RecommendedAllocation) GetAssetPositionLimits(
            string symbol, 
            decimal totalPortfolioValue)
        {
            if (string.IsNullOrWhiteSpace(symbol))
                return (1, 5, 0.1m); // Default

            var upperSymbol = symbol.ToUpperInvariant();
            
            if (AssetProfiles.TryGetValue(upperSymbol, out var profile))
            {
                // Calculate recommended allocation based on volatility
                // Higher volatility = smaller allocation per position
                var recommendedAllocation = Math.Max(0.05m, Math.Min(0.2m, 0.15m / profile.TypicalVolatility));
                
                return (profile.MinPosition, profile.MaxPosition, recommendedAllocation);
            }

            // Default for unknown symbols
            return (1, 5, 0.1m);
        }

        /// <summary>
        /// Get asset-specific delta targets for option selection.
        /// Adjusts delta ranges based on typical volatility characteristics.
        /// </summary>
        /// <param name="symbol">The underlying symbol</param>
        /// <param name="baseDeltaMin">Base minimum delta</param>
        /// <param name="baseDeltaMax">Base maximum delta</param>
        /// <returns>Adjusted delta range for the asset</returns>
        public static (decimal DeltaMin, decimal DeltaMax) GetAssetDeltaTargets(
            string symbol, 
            decimal baseDeltaMin, 
            decimal baseDeltaMax)
        {
            if (string.IsNullOrWhiteSpace(symbol))
                return (baseDeltaMin, baseDeltaMax);

            var upperSymbol = symbol.ToUpperInvariant();
            
            if (AssetProfiles.TryGetValue(upperSymbol, out var profile))
            {
                // For higher volatility assets, use slightly tighter delta ranges
                if (profile.TypicalVolatility > 0.4m)
                {
                    // High vol assets: tighter delta range
                    var adjustment = 0.05m;
                    return (baseDeltaMin + adjustment, baseDeltaMax - adjustment);
                }
                else if (profile.TypicalVolatility < 0.2m)
                {
                    // Low vol assets: wider delta range
                    var adjustment = 0.03m;
                    return (Math.Max(0.05m, baseDeltaMin - adjustment), Math.Min(0.45m, baseDeltaMax + adjustment));
                }
            }

            // Default unchanged
            return (baseDeltaMin, baseDeltaMax);
        }

        /// <summary>
        /// Check if symbol has options available and is suitable for options strategies
        /// </summary>
        /// <param name="symbol">The symbol to check</param>
        /// <returns>True if symbol is known to have liquid options</returns>
        public static bool HasLiquidOptions(string symbol)
        {
            if (string.IsNullOrWhiteSpace(symbol))
                return false;

            var upperSymbol = symbol.ToUpperInvariant();
            return AssetProfiles.ContainsKey(upperSymbol);
        }

        /// <summary>
        /// Get all supported symbols for multi-asset strategies
        /// </summary>
        /// <returns>Array of all supported symbols</returns>
        public static string[] GetSupportedSymbols()
        {
            var result = new string[AssetProfiles.Count];
            AssetProfiles.Keys.CopyTo(result, 0);
            return result;
        }

        /// <summary>
        /// Get asset profile information for debugging/logging
        /// </summary>
        /// <param name="symbol">The symbol to look up</param>
        /// <returns>Asset profile or null if not found</returns>
        public static AssetProfile GetAssetProfile(string symbol)
        {
            if (string.IsNullOrWhiteSpace(symbol))
                return null;

            var upperSymbol = symbol.ToUpperInvariant();
            return AssetProfiles.TryGetValue(upperSymbol, out var profile) ? profile : null;
        }

        /// <summary>
        /// Get asset-specific strike increment for rounding option strikes.
        /// Different assets have different strike intervals (SPX=5, SPY=1, etc.)
        /// </summary>
        /// <param name="symbol">The underlying symbol</param>
        /// <returns>Strike increment for rounding</returns>
        public static decimal GetStrikeIncrement(string symbol)
        {
            if (string.IsNullOrWhiteSpace(symbol))
                return 1m;

            var upperSymbol = symbol.ToUpperInvariant();
            
            // Asset-specific strike increments
            switch (upperSymbol)
            {
                case "SPX":
                case "NDX":
                case "RUT":
                case "VIX":
                    return 5m; // Index options typically use $5 increments
                    
                case "SPY":
                case "QQQ":
                case "IWM":
                    return 1m; // ETF options typically use $1 increments
                    
                case "AAPL":
                case "MSFT":
                case "GOOGL":
                case "AMZN":
                case "TSLA":
                    return 2.5m; // High-value stocks often use $2.50 increments
                    
                case "ES":
                case "NQ":
                case "YM":
                    return 5m; // Futures options typically use $5 increments
                    
                default:
                    return 1m; // Default for unknown symbols
            }
        }
    }

    /// <summary>
    /// Asset-specific profile containing volatility and position characteristics
    /// </summary>
    public class AssetProfile
    {
        /// <summary>
        /// Typical implied volatility for the asset (used for position sizing)
        /// </summary>
        public decimal TypicalVolatility { get; set; }

        /// <summary>
        /// Multiplier for strike width selection (1.0 = default, >1.0 = wider strikes)
        /// </summary>
        public decimal StrikeWidthMultiplier { get; set; }

        /// <summary>
        /// Minimum recommended number of positions for this asset
        /// </summary>
        public int MinPosition { get; set; }

        /// <summary>
        /// Maximum recommended number of positions for this asset
        /// </summary>
        public int MaxPosition { get; set; }

        /// <summary>
        /// Estimated margin requirement as multiplier of underlying price (e.g., 1.7 = 170% of underlying)
        /// </summary>
        public decimal EstimatedMarginMultiplier { get; set; } = 0.3m;

        /// <summary>
        /// Minimum account size recommended for trading this asset's options
        /// </summary>
        public decimal MinAccountSize { get; set; } = 10000m;

        public override string ToString()
        {
            return $"Vol: {TypicalVolatility:P1}, StrikeMultiplier: {StrikeWidthMultiplier:F1}x, Positions: {MinPosition}-{MaxPosition}, MinAccount: ${MinAccountSize:F0}";
        }
    }
}
using System;
using System.Threading;
using QuantConnect;
using QuantConnect.Algorithm;
using QuantConnect.Orders;

namespace CoreAlgo.Architecture.QC.Helpers
{
    /// <summary>
    /// Simple extension methods for order retry logic and enhanced order management.
    /// Provides minimal retry functionality without complex order management overhead.
    /// </summary>
    public static class OrderExtensions
    {
        /// <summary>
        /// Submit an order with automatic retry logic on failures.
        /// Retries failed orders with configurable attempts and delays.
        /// </summary>
        /// <param name="algorithm">The QC algorithm instance</param>
        /// <param name="request">The order request to submit</param>
        /// <param name="maxRetries">Maximum number of retry attempts (default: 3)</param>
        /// <param name="retryDelay">Delay between retry attempts (default: 1 second)</param>
        /// <returns>The OrderTicket from the successful submission, or null if all retries failed</returns>
        public static OrderTicket SubmitWithRetry(this QCAlgorithm algorithm, SubmitOrderRequest request, 
            int maxRetries = 3, TimeSpan retryDelay = default)
        {
            if (algorithm == null)
                throw new ArgumentNullException(nameof(algorithm));
            
            if (request == null)
                throw new ArgumentNullException(nameof(request));

            if (retryDelay == default)
                retryDelay = TimeSpan.FromSeconds(1);

            OrderTicket ticket = null;
            Exception lastException = null;

            for (int attempt = 1; attempt <= maxRetries + 1; attempt++)
            {
                try
                {
                    algorithm.Debug($"OrderExtensions: Submitting order attempt {attempt}/{maxRetries + 1} for {request.Symbol}");
                    
                    // Submit the order using QC's standard method
                    if (request.OrderType == OrderType.Market)
                    {
                        ticket = algorithm.MarketOrder(request.Symbol, request.Quantity, tag: request.Tag);
                    }
                    else if (request.OrderType == OrderType.Limit)
                    {
                        ticket = algorithm.LimitOrder(request.Symbol, request.Quantity, request.LimitPrice, tag: request.Tag);
                    }
                    else
                    {
                        // For other order types, try the basic Submit method
                        ticket = algorithm.MarketOrder(request.Symbol, request.Quantity, tag: request.Tag);
                    }
                    
                    if (ticket != null)
                    {
                        if (attempt > 1)
                        {
                            algorithm.Debug($"OrderExtensions: Order submitted successfully on attempt {attempt}");
                        }
                        return ticket;
                    }
                    else
                    {
                        algorithm.Debug($"OrderExtensions: Order submission returned null ticket on attempt {attempt}");
                    }
                }
                catch (Exception ex)
                {
                    lastException = ex;
                    algorithm.Debug($"OrderExtensions: Order submission failed on attempt {attempt}: {ex.Message}");
                    
                    // Don't retry on the last attempt
                    if (attempt <= maxRetries)
                    {
                        algorithm.Debug($"OrderExtensions: Waiting {retryDelay.TotalSeconds} seconds before retry");
                        Thread.Sleep(retryDelay);
                    }
                }
            }

            // All attempts failed
            algorithm.Error($"OrderExtensions: Failed to submit order after {maxRetries + 1} attempts. Last error: {lastException?.Message}");
            return null;
        }

        /// <summary>
        /// Submit a market order with retry logic.
        /// Convenience method for the most common order type.
        /// </summary>
        /// <param name="algorithm">The QC algorithm instance</param>
        /// <param name="symbol">The symbol to trade</param>
        /// <param name="quantity">The quantity to trade</param>
        /// <param name="maxRetries">Maximum number of retry attempts (default: 3)</param>
        /// <param name="retryDelay">Delay between retry attempts (default: 1 second)</param>
        /// <returns>The OrderTicket from successful submission, or null if failed</returns>
        public static OrderTicket MarketOrderWithRetry(this QCAlgorithm algorithm, Symbol symbol, int quantity,
            int maxRetries = 3, TimeSpan retryDelay = default)
        {
            var request = new SubmitOrderRequest(OrderType.Market, algorithm.Securities[symbol].Type, 
                symbol, quantity, 0, 0, algorithm.UtcTime, $"Market order with retry");
            
            return algorithm.SubmitWithRetry(request, maxRetries, retryDelay);
        }

        /// <summary>
        /// Submit a limit order with retry logic.
        /// Convenience method for limit orders.
        /// </summary>
        /// <param name="algorithm">The QC algorithm instance</param>
        /// <param name="symbol">The symbol to trade</param>
        /// <param name="quantity">The quantity to trade</param>
        /// <param name="limitPrice">The limit price</param>
        /// <param name="maxRetries">Maximum number of retry attempts (default: 3)</param>
        /// <param name="retryDelay">Delay between retry attempts (default: 1 second)</param>
        /// <returns>The OrderTicket from successful submission, or null if failed</returns>
        public static OrderTicket LimitOrderWithRetry(this QCAlgorithm algorithm, Symbol symbol, int quantity, 
            decimal limitPrice, int maxRetries = 3, TimeSpan retryDelay = default)
        {
            var request = new SubmitOrderRequest(OrderType.Limit, algorithm.Securities[symbol].Type, 
                symbol, quantity, 0, limitPrice, algorithm.UtcTime, $"Limit order with retry");
            
            return algorithm.SubmitWithRetry(request, maxRetries, retryDelay);
        }

        /// <summary>
        /// Check if an order ticket represents a successful order.
        /// Provides simple success/failure checking.
        /// </summary>
        /// <param name="ticket">The order ticket to check</param>
        /// <returns>True if the order was successful, false otherwise</returns>
        public static bool WasSuccessful(this OrderTicket ticket)
        {
            if (ticket == null)
                return false;

            // Check order status for success indicators
            var status = ticket.Status;
            return status == OrderStatus.Filled || 
                   status == OrderStatus.PartiallyFilled || 
                   status == OrderStatus.Submitted;
        }

        /// <summary>
        /// Check if an order ticket represents a failed order.
        /// Provides simple failure checking.
        /// </summary>
        /// <param name="ticket">The order ticket to check</param>
        /// <returns>True if the order failed, false otherwise</returns>
        public static bool HasFailed(this OrderTicket ticket)
        {
            if (ticket == null)
                return true;

            var status = ticket.Status;
            return status == OrderStatus.Invalid || 
                   status == OrderStatus.Canceled || 
                   status == OrderStatus.CancelPending;
        }

        /// <summary>
        /// Get a human-readable description of the order status.
        /// Useful for logging and debugging.
        /// </summary>
        /// <param name="ticket">The order ticket to describe</param>
        /// <returns>A descriptive string of the order status</returns>
        public static string GetStatusDescription(this OrderTicket ticket)
        {
            if (ticket == null)
                return "Null ticket";

            var status = ticket.Status;
            var filled = ticket.QuantityFilled;
            var remaining = ticket.Quantity - filled;

            return status switch
            {
                OrderStatus.New => "Order created but not yet submitted",
                OrderStatus.Submitted => $"Order submitted, waiting for fill",
                OrderStatus.PartiallyFilled => $"Partially filled: {filled}/{ticket.Quantity}, {remaining} remaining",
                OrderStatus.Filled => $"Completely filled: {filled} shares",
                OrderStatus.Canceled => "Order was canceled",
                OrderStatus.None => "Order status unknown",
                OrderStatus.Invalid => "Order is invalid",
                OrderStatus.CancelPending => "Cancel request pending",
                OrderStatus.UpdateSubmitted => "Order update submitted",
                _ => $"Unknown status: {status}"
            };
        }

        /// <summary>
        /// Submit a Market-On-Close order with basic error handling and logging.
        /// </summary>
        public static OrderTicket MarketOnCloseWithRetry(this QCAlgorithm algorithm, Symbol symbol, int quantity,
            int maxRetries = 1, TimeSpan retryDelay = default, string tag = "")
        {
            if (retryDelay == default) retryDelay = TimeSpan.FromSeconds(1);
            OrderTicket ticket = null;
            for (int attempt = 1; attempt <= Math.Max(1, maxRetries); attempt++)
            {
                try
                {
                    algorithm.Debug($"OrderExtensions: Submitting MOC attempt {attempt} for {symbol}");
                    ticket = algorithm.MarketOnCloseOrder(symbol, quantity, tag: tag);
                    if (ticket != null) return ticket;
                }
                catch (Exception ex)
                {
                    algorithm.Debug($"OrderExtensions: MOC submission failed attempt {attempt}: {ex.Message}");
                    if (attempt < maxRetries) Thread.Sleep(retryDelay);
                }
            }
            algorithm.Error($"OrderExtensions: Failed to submit MOC for {symbol}");
            return ticket;
        }

        /// <summary>
        /// Submit a Market-On-Open order with basic error handling and logging.
        /// </summary>
        public static OrderTicket MarketOnOpenWithRetry(this QCAlgorithm algorithm, Symbol symbol, int quantity,
            int maxRetries = 1, TimeSpan retryDelay = default, string tag = "")
        {
            if (retryDelay == default) retryDelay = TimeSpan.FromSeconds(1);
            OrderTicket ticket = null;
            for (int attempt = 1; attempt <= Math.Max(1, maxRetries); attempt++)
            {
                try
                {
                    algorithm.Debug($"OrderExtensions: Submitting MOO attempt {attempt} for {symbol}");
                    ticket = algorithm.MarketOnOpenOrder(symbol, quantity, tag: tag);
                    if (ticket != null) return ticket;
                }
                catch (Exception ex)
                {
                    algorithm.Debug($"OrderExtensions: MOO submission failed attempt {attempt}: {ex.Message}");
                    if (attempt < maxRetries) Thread.Sleep(retryDelay);
                }
            }
            algorithm.Error($"OrderExtensions: Failed to submit MOO for {symbol}");
            return ticket;
        }
    }
}
using System;
using QuantConnect.Securities;

namespace CoreAlgo.Architecture.QC.Helpers
{
    /// <summary>
    /// Simple static helper for position size calculations.
    /// Provides percentage-based and fixed sizing methods for different asset types.
    /// Handles the complexity of options contract multipliers and different security types.
    /// </summary>
    public static class PositionSizer
    {
        /// <summary>
        /// Standard options contract multiplier (100 shares per contract)
        /// </summary>
        public const int StandardOptionsMultiplier = 100;

        /// <summary>
        /// Calculate position quantity based on percentage allocation of portfolio.
        /// Works for stocks, futures, and other direct securities.
        /// </summary>
        /// <param name="portfolio">The algorithm's portfolio manager</param>
        /// <param name="allocationPercent">Percentage of portfolio to allocate (e.g., 0.1 = 10%)</param>
        /// <param name="price">Current price of the security</param>
        /// <returns>Quantity to purchase (number of shares/contracts)</returns>
        /// <exception cref="ArgumentNullException">If portfolio is null</exception>
        /// <exception cref="ArgumentException">If allocation or price is invalid</exception>
        public static int CalculateQuantity(SecurityPortfolioManager portfolio, decimal allocationPercent, decimal price)
        {
            if (portfolio == null)
                throw new ArgumentNullException(nameof(portfolio));
            
            if (allocationPercent <= 0 || allocationPercent > 1)
                throw new ArgumentException("Allocation percent must be between 0 and 1", nameof(allocationPercent));
            
            if (price <= 0)
                throw new ArgumentException("Price must be greater than 0", nameof(price));

            // Calculate allocation amount from total portfolio value
            var totalValue = portfolio.TotalPortfolioValue;
            var allocationAmount = totalValue * allocationPercent;
            
            // Calculate quantity based on price
            var quantity = (int)Math.Floor(allocationAmount / price);
            
            return Math.Max(0, quantity);
        }

        /// <summary>
        /// Calculate position quantity for options contracts.
        /// Accounts for the contract multiplier (typically 100 shares per contract).
        /// </summary>
        /// <param name="portfolio">The algorithm's portfolio manager</param>
        /// <param name="allocationPercent">Percentage of portfolio to allocate (e.g., 0.1 = 10%)</param>
        /// <param name="premium">Premium price per contract</param>
        /// <param name="multiplier">Contract multiplier (default: 100 for standard options)</param>
        /// <returns>Number of options contracts to purchase</returns>
        /// <exception cref="ArgumentNullException">If portfolio is null</exception>
        /// <exception cref="ArgumentException">If allocation, premium, or multiplier is invalid</exception>
        public static int CalculateOptionsQuantity(SecurityPortfolioManager portfolio, decimal allocationPercent, 
            decimal premium, int multiplier = StandardOptionsMultiplier)
        {
            if (portfolio == null)
                throw new ArgumentNullException(nameof(portfolio));
            
            if (allocationPercent <= 0 || allocationPercent > 1)
                throw new ArgumentException("Allocation percent must be between 0 and 1", nameof(allocationPercent));
            
            if (premium <= 0)
                throw new ArgumentException("Premium must be greater than 0", nameof(premium));
            
            if (multiplier <= 0)
                throw new ArgumentException("Multiplier must be greater than 0", nameof(multiplier));

            // Calculate allocation amount from total portfolio value
            var totalValue = portfolio.TotalPortfolioValue;
            var allocationAmount = totalValue * allocationPercent;
            
            // Calculate cost per contract (premium * multiplier)
            var costPerContract = premium * multiplier;
            
            // Calculate number of contracts
            var contracts = (int)Math.Floor(allocationAmount / costPerContract);
            
            return Math.Max(0, contracts);
        }

        /// <summary>
        /// Calculate position quantity using a fixed dollar amount.
        /// Alternative to percentage-based sizing.
        /// </summary>
        /// <param name="dollarAmount">Fixed dollar amount to invest</param>
        /// <param name="price">Current price of the security</param>
        /// <returns>Quantity to purchase</returns>
        /// <exception cref="ArgumentException">If dollar amount or price is invalid</exception>
        public static int CalculateFixedDollarQuantity(decimal dollarAmount, decimal price)
        {
            if (dollarAmount <= 0)
                throw new ArgumentException("Dollar amount must be greater than 0", nameof(dollarAmount));
            
            if (price <= 0)
                throw new ArgumentException("Price must be greater than 0", nameof(price));

            var quantity = (int)Math.Floor(dollarAmount / price);
            return Math.Max(0, quantity);
        }

        /// <summary>
        /// Calculate options quantity using a fixed dollar amount.
        /// </summary>
        /// <param name="dollarAmount">Fixed dollar amount to invest</param>
        /// <param name="premium">Premium price per contract</param>
        /// <param name="multiplier">Contract multiplier (default: 100 for standard options)</param>
        /// <returns>Number of options contracts to purchase</returns>
        /// <exception cref="ArgumentException">If parameters are invalid</exception>
        public static int CalculateFixedDollarOptionsQuantity(decimal dollarAmount, decimal premium, 
            int multiplier = StandardOptionsMultiplier)
        {
            if (dollarAmount <= 0)
                throw new ArgumentException("Dollar amount must be greater than 0", nameof(dollarAmount));
            
            if (premium <= 0)
                throw new ArgumentException("Premium must be greater than 0", nameof(premium));
            
            if (multiplier <= 0)
                throw new ArgumentException("Multiplier must be greater than 0", nameof(multiplier));

            // Calculate cost per contract
            var costPerContract = premium * multiplier;
            
            // Calculate number of contracts
            var contracts = (int)Math.Floor(dollarAmount / costPerContract);
            
            return Math.Max(0, contracts);
        }

        /// <summary>
        /// Calculate the maximum safe position size to avoid overcommitting portfolio.
        /// Includes a safety buffer to account for price movements and fees.
        /// </summary>
        /// <param name="portfolio">The algorithm's portfolio manager</param>
        /// <param name="allocationPercent">Desired percentage allocation</param>
        /// <param name="price">Current price of the security</param>
        /// <param name="safetyBuffer">Safety buffer as percentage (e.g., 0.05 = 5% buffer)</param>
        /// <returns>Safe quantity to purchase</returns>
        public static int CalculateSafeQuantity(SecurityPortfolioManager portfolio, decimal allocationPercent, 
            decimal price, decimal safetyBuffer = 0.05m)
        {
            if (safetyBuffer < 0 || safetyBuffer > 0.5m)
                throw new ArgumentException("Safety buffer must be between 0 and 0.5", nameof(safetyBuffer));

            // Adjust allocation for safety buffer
            var adjustedAllocation = allocationPercent * (1 - safetyBuffer);
            
            return CalculateQuantity(portfolio, adjustedAllocation, price);
        }

        /// <summary>
        /// Calculate safe options quantity with buffer.
        /// </summary>
        /// <param name="portfolio">The algorithm's portfolio manager</param>
        /// <param name="allocationPercent">Desired percentage allocation</param>
        /// <param name="premium">Premium price per contract</param>
        /// <param name="safetyBuffer">Safety buffer as percentage (default: 5%)</param>
        /// <param name="multiplier">Contract multiplier (default: 100)</param>
        /// <returns>Safe number of options contracts to purchase</returns>
        public static int CalculateSafeOptionsQuantity(SecurityPortfolioManager portfolio, decimal allocationPercent, 
            decimal premium, decimal safetyBuffer = 0.05m, int multiplier = StandardOptionsMultiplier)
        {
            if (safetyBuffer < 0 || safetyBuffer > 0.5m)
                throw new ArgumentException("Safety buffer must be between 0 and 0.5", nameof(safetyBuffer));

            // Adjust allocation for safety buffer
            var adjustedAllocation = allocationPercent * (1 - safetyBuffer);
            
            return CalculateOptionsQuantity(portfolio, adjustedAllocation, premium, multiplier);
        }

        /// <summary>
        /// Get the effective buying power for position sizing.
        /// Accounts for existing positions and available cash.
        /// </summary>
        /// <param name="portfolio">The algorithm's portfolio manager</param>
        /// <returns>Available buying power for new positions</returns>
        public static decimal GetAvailableBuyingPower(SecurityPortfolioManager portfolio)
        {
            if (portfolio == null)
                throw new ArgumentNullException(nameof(portfolio));

            // Use QC's available cash as buying power
            return portfolio.Cash;
        }

        /// <summary>
        /// Calculate position value for risk management.
        /// Useful for tracking total exposure.
        /// </summary>
        /// <param name="quantity">Number of shares/contracts</param>
        /// <param name="price">Current price</param>
        /// <param name="multiplier">Contract multiplier (1 for stocks, 100 for options)</param>
        /// <returns>Total position value</returns>
        public static decimal CalculatePositionValue(int quantity, decimal price, int multiplier = 1)
        {
            if (quantity < 0)
                throw new ArgumentException("Quantity cannot be negative", nameof(quantity));
            
            if (price < 0)
                throw new ArgumentException("Price cannot be negative", nameof(price));
            
            if (multiplier <= 0)
                throw new ArgumentException("Multiplier must be greater than 0", nameof(multiplier));

            return quantity * price * multiplier;
        }
    }
}
using System;
using System.Collections.Generic;
using System.Linq;
using QuantConnect;
using QuantConnect.Orders;
using QuantConnect.Securities;

namespace CoreAlgo.Architecture.QC.Helpers
{
    /// <summary>
    /// Utility helpers for aligning order prices to brokerage tick size requirements.
    /// Centralizes interaction with QuantConnect price variation models so strategies,
    /// wrappers, and services share the same rounding semantics.
    /// </summary>
    public static class PriceRounding
    {
        /// <summary>
        /// Resolve the minimum price variation (tick size) for a security using QC-native
        /// price variation models, falling back to symbol properties and pragmatic defaults.
        /// </summary>
        public static decimal GetMinPriceVariation(Security security)
        {
            if (security == null)
            {
                return 0.01m;
            }

            try
            {
                var tick = security.PriceVariationModel?.GetMinimumPriceVariation(new GetMinimumPriceVariationParameters(security, security.Price)) ?? 0m;
                if (tick <= 0m)
                {
                    tick = security.SymbolProperties?.MinimumPriceVariation ?? 0m;
                }

                if (tick > 0m)
                {
                    return tick;
                }

                if (security.Type == SecurityType.Equity)
                {
                    return security.Price < 1m ? 0.0001m : 0.01m;
                }
            }
            catch
            {
                // Swallow and fall back to defaults below.
            }

            return 0.01m;
        }

        /// <summary>
        /// Convenience overload using SecuritiesManager for easy access within strategies.
        /// </summary>
        public static decimal GetMinPriceVariation(SecurityManager securities, Symbol symbol)
        {
            if (securities == null || symbol == null)
            {
                return 0.01m;
            }

            return securities.TryGetValue(symbol, out var security)
                ? GetMinPriceVariation(security)
                : 0.01m;
        }

        public static decimal FloorToTick(decimal price, decimal tick)
        {
            return tick > 0m ? Math.Floor(price / tick) * tick : price;
        }

        public static decimal CeilToTick(decimal price, decimal tick)
        {
            return tick > 0m ? Math.Ceiling(price / tick) * tick : price;
        }

        public static decimal RoundToNearestTick(decimal price, decimal tick)
        {
            return tick > 0m ? Math.Round(price / tick, MidpointRounding.AwayFromZero) * tick : price;
        }

        /// <summary>
        /// Direction-aware rounding for limit prices (buy=floor, sell=ceil).
        /// </summary>
        public static decimal RoundLimitPrice(SecurityManager securities, Symbol symbol, decimal quantity, decimal limitPrice)
        {
            var tick = GetMinPriceVariation(securities, symbol);
            return quantity >= 0 ? FloorToTick(limitPrice, tick) : CeilToTick(limitPrice, tick);
        }

        /// <summary>
        /// Direction-aware rounding for stop prices (buy=ceil, sell=floor).
        /// </summary>
        public static decimal RoundStopPrice(SecurityManager securities, Symbol symbol, decimal quantity, decimal stopPrice)
        {
            var tick = GetMinPriceVariation(securities, symbol);
            return quantity >= 0 ? CeilToTick(stopPrice, tick) : FloorToTick(stopPrice, tick);
        }

        /// <summary>
        /// Round trailing stop amount to ensure dynamically calculated stop prices align with tick size.
        /// For dollar trailing, we use ceiling to ensure sufficient protection distance.
        /// </summary>
        public static decimal RoundTrailingStopPrice(SecurityManager securities, Symbol symbol, decimal quantity, decimal trailingAmount, bool isDollarTrailing)
        {
            var tick = GetMinPriceVariation(securities, symbol);
            // Always ceil trailing amounts to ensure adequate stop distance and tick alignment
            return CeilToTick(trailingAmount, tick);
        }

        /// <summary>
        /// Direction-aware rounding for stop-limit orders combining stop and limit semantics.
        /// Enforces valid stop/limit relationships post-rounding to prevent order rejection:
        /// - Buy stop-limit: limit >= stop + tick (limit must be at or above trigger)
        /// - Sell stop-limit: limit <= stop - tick (limit must be at or below trigger)
        /// Hardened to handle edge cases: tick<=0, negative prices, and insufficient buffers
        /// </summary>
        public static (decimal RoundedStop, decimal RoundedLimit) RoundStopLimitPrices(
            SecurityManager securities,
            Symbol symbol,
            decimal quantity,
            decimal stopPrice,
            decimal limitPrice)
        {
            var tick = GetMinPriceVariation(securities, symbol);
            
            // Harden: ensure tick is always valid (fallback to 0.01 if tick resolution fails)
            if (tick <= 0m)
            {
                tick = 0.01m;
            }
            
            // Guard: reject negative or zero prices
            if (stopPrice <= 0m || limitPrice <= 0m)
            {
                throw new ArgumentException($"Invalid stop-limit prices: stop={stopPrice}, limit={limitPrice} must be > 0");
            }

            if (quantity >= 0)
            {
                // Buy stop-limit: stop rounds up, limit rounds down
                var roundedStop = CeilToTick(stopPrice, tick);
                var roundedLimit = FloorToTick(limitPrice, tick);
                
                // Enforce limit >= stop + tick for buy stop-limits
                if (roundedLimit < roundedStop + tick)
                {
                    roundedLimit = roundedStop + tick;
                }
                
                // Final safety: if still invalid, nudge limit up by one more tick
                if (roundedLimit < roundedStop)
                {
                    roundedLimit = roundedStop + tick;
                }
                
                return (roundedStop, roundedLimit);
            }
            else
            {
                // Sell stop-limit: stop rounds down, limit rounds up
                var roundedStop = FloorToTick(stopPrice, tick);
                var roundedLimit = CeilToTick(limitPrice, tick);
                
                // Enforce limit <= stop - tick for sell stop-limits
                // Critical: For sell stop-limits, the limit MUST be below the stop
                if (roundedLimit > roundedStop - tick)
                {
                    roundedLimit = roundedStop - tick;
                }
                
                // Additional safety check: if they're still equal or inverted after clamping,
                // force a valid separation (this can happen with very coarse ticks)
                if (roundedLimit >= roundedStop)
                {
                    roundedLimit = roundedStop - tick;
                }
                
                // Final safety: ensure at least 1 tick separation for sells
                if (roundedLimit >= roundedStop - tick)
                {
                    roundedLimit = roundedStop - tick;
                }
                
                // Guard: if limit is now <= 0 after clamping, this order is invalid
                if (roundedLimit <= 0m)
                {
                    throw new ArgumentException($"Stop-limit prices invalid after rounding: stop={roundedStop}, limit={roundedLimit}, tick={tick}. Buffer too small for tick size.");
                }
                
                return (roundedStop, roundedLimit);
            }
        }

        /// <summary>
        /// Calculate a combo order net-price tick size based on member legs.
        /// Uses the smallest leg tick as pragmatic baseline.
        /// </summary>
        public static decimal GetComboNetTick(SecurityManager securities, IReadOnlyList<Leg> legs)
        {
            if (legs == null || legs.Count == 0)
            {
                return 0.01m;
            }

            var ticks = legs
                .Select(leg => securities != null && securities.TryGetValue(leg.Symbol, out var security)
                    ? GetMinPriceVariation(security)
                    : 0.01m)
                .Where(t => t > 0m);

            var minTick = ticks.DefaultIfEmpty(0.01m).Min();
            return minTick > 0m ? minTick : 0.01m;
        }

        /// <summary>
        /// Round combo net price (debit=floor, credit=ceil) based on aggregate tick size.
        /// </summary>
        public static decimal RoundComboNetPrice(
            SecurityManager securities,
            IReadOnlyList<Leg> legs,
            decimal netPrice,
            bool isDebit)
        {
            var netTick = GetComboNetTick(securities, legs);
            return isDebit ? FloorToTick(netPrice, netTick) : CeilToTick(netPrice, netTick);
        }
    }
}

using System;
using System.Collections.Generic;
using System.Linq;
using CoreAlgo.Architecture.Core.Models;
using QuantConnect;
using QuantConnect.Algorithm;
using QuantConnect.Data.Market;
using QuantConnect.Securities.Option;

namespace CoreAlgo.Architecture.QC.Helpers
{
    /// <summary>
    /// Calculates optimal strike ranges based on delta targets and market conditions
    /// </summary>
    public class StrikeRangeCalculator
    {
        private readonly QCAlgorithm _algorithm;
        private readonly decimal _highVolThreshold;
        private readonly decimal _volAdjustment;

        public StrikeRangeCalculator(QCAlgorithm algorithm, 
            decimal highVolThreshold = 0.30m, decimal volAdjustment = 0.05m)
        {
            _algorithm = algorithm;
            _highVolThreshold = highVolThreshold;
            _volAdjustment = volAdjustment;
        }

        /// <summary>
        /// Represents delta targets for option strategies
        /// </summary>
        public class DeltaTargets
        {
            public decimal ShortPut { get; set; }
            public decimal LongPut { get; set; }
            public decimal ShortCall { get; set; }
            public decimal LongCall { get; set; }
        }

        /// <summary>
        /// Represents selected strikes for a strategy
        /// </summary>
        public class StrikeRange
        {
            public decimal ShortPutStrike { get; set; }
            public decimal LongPutStrike { get; set; }
            public decimal ShortCallStrike { get; set; }
            public decimal LongCallStrike { get; set; }
            public decimal ATMStrike { get; set; }
        }

        /// <summary>
        /// Gets asset-specific delta targets adjusted for current volatility
        /// </summary>
        public DeltaTargets GetAssetSpecificDeltas(string symbol, decimal currentVolatility)
        {
            // Get base delta targets from MultiAssetHelper
            var (deltaMin, deltaMax) = MultiAssetHelper.GetAssetDeltaTargets(symbol, 0.15m, 0.25m);
            
            // Adjust deltas based on volatility regime
            var adjustment = currentVolatility > _highVolThreshold ? _volAdjustment : 0m;
            
            return new DeltaTargets
            {
                // In high volatility, move strikes further OTM
                ShortPut = Math.Max(0.05m, deltaMin - adjustment),
                LongPut = Math.Max(0.01m, deltaMin - adjustment - 0.05m),
                ShortCall = Math.Max(0.05m, deltaMin + adjustment),
                LongCall = Math.Max(0.01m, deltaMin + adjustment + 0.05m)
            };
        }

        /// <summary>
        /// Calculates optimal strike range for the given option chain
        /// </summary>
        public StrikeRange CalculateStrikeRange(OptionChain chain, DeltaTargets targets)
        {
            if (chain == null || !chain.Any())
            {
                throw new InvalidOperationException("Option chain is empty");
            }

            var underlying = chain.Underlying;
            var atmStrike = GetATMStrike(chain);
            var strikes = new StrikeRange { ATMStrike = atmStrike };

            // Separate puts and calls
            var puts = chain.Where(x => x.Right == OptionRight.Put)
                           .OrderBy(x => x.Strike)
                           .ToList();
            var calls = chain.Where(x => x.Right == OptionRight.Call)
                            .OrderBy(x => x.Strike)
                            .ToList();

            // Find strikes closest to target deltas
            strikes.ShortPutStrike = FindStrikeByDelta(puts, targets.ShortPut, atmStrike);
            strikes.LongPutStrike = FindStrikeByDelta(puts, targets.LongPut, atmStrike);
            strikes.ShortCallStrike = FindStrikeByDelta(calls, -targets.ShortCall, atmStrike); // Call deltas are negative
            strikes.LongCallStrike = FindStrikeByDelta(calls, -targets.LongCall, atmStrike);

            // Validate and adjust strikes
            return ValidateAndAdjustStrikes(strikes, chain.Symbol.Underlying.Value);
        }

        /// <summary>
        /// Gets the at-the-money strike price
        /// </summary>
        public decimal GetATMStrike(OptionChain chain)
        {
            var underlyingPrice = chain.Underlying.Price;
            
            // Find the strike closest to the underlying price
            var atmStrike = chain
                .Select(x => x.Strike)
                .Distinct()
                .OrderBy(strike => Math.Abs(strike - underlyingPrice))
                .FirstOrDefault();

            return atmStrike > 0 ? atmStrike : underlyingPrice;
        }

        /// <summary>
        /// Finds the strike price closest to the target delta
        /// </summary>
        private decimal FindStrikeByDelta(List<OptionContract> contracts, decimal targetDelta, decimal atmStrike)
        {
            if (!contracts.Any()) return atmStrike;

            OptionContract bestContract = null;
            decimal bestDeltaDiff = decimal.MaxValue;

            foreach (var contract in contracts)
            {
                // Skip if Greeks aren't available
                if (contract.Greeks?.Delta == null) continue;

                var deltaDiff = Math.Abs(contract.Greeks.Delta - targetDelta);
                if (deltaDiff < bestDeltaDiff)
                {
                    bestDeltaDiff = deltaDiff;
                    bestContract = contract;
                }
            }

            // If no contract with Greeks found, use strike selection based on distance from ATM
            if (bestContract == null)
            {
                return EstimateStrikeByDelta(contracts, targetDelta, atmStrike);
            }

            return bestContract.Strike;
        }

        /// <summary>
        /// Estimates strike when Greeks aren't available
        /// </summary>
        private decimal EstimateStrikeByDelta(List<OptionContract> contracts, decimal targetDelta, decimal atmStrike)
        {
            var isPut = contracts.FirstOrDefault()?.Right == OptionRight.Put;
            var absTargetDelta = Math.Abs(targetDelta);

            // Rough approximation: 0.50 delta at ATM, decreases as we move OTM
            decimal targetDistance;
            if (absTargetDelta >= 0.40m) targetDistance = 0.02m;      // 2% OTM
            else if (absTargetDelta >= 0.30m) targetDistance = 0.04m; // 4% OTM
            else if (absTargetDelta >= 0.20m) targetDistance = 0.06m; // 6% OTM
            else if (absTargetDelta >= 0.10m) targetDistance = 0.10m; // 10% OTM
            else targetDistance = 0.15m;                               // 15% OTM

            var targetStrike = isPut 
                ? atmStrike * (1 - targetDistance)
                : atmStrike * (1 + targetDistance);

            // Find closest available strike
            return contracts
                .Select(x => x.Strike)
                .OrderBy(strike => Math.Abs(strike - targetStrike))
                .FirstOrDefault();
        }

        /// <summary>
        /// Validates and adjusts strikes to ensure proper spread structure
        /// </summary>
        private StrikeRange ValidateAndAdjustStrikes(StrikeRange strikes, string symbol)
        {
            var increment = MultiAssetHelper.GetStrikeIncrement(symbol);
            var minSpreadWidth = increment * 2; // Minimum 2 strikes apart

            // Ensure put spreads are valid
            if (strikes.ShortPutStrike - strikes.LongPutStrike < minSpreadWidth)
            {
                strikes.LongPutStrike = strikes.ShortPutStrike - minSpreadWidth;
            }

            // Ensure call spreads are valid
            if (strikes.LongCallStrike - strikes.ShortCallStrike < minSpreadWidth)
            {
                strikes.LongCallStrike = strikes.ShortCallStrike + minSpreadWidth;
            }

            // Round strikes to proper increments
            strikes.ShortPutStrike = RoundToIncrement(strikes.ShortPutStrike, increment);
            strikes.LongPutStrike = RoundToIncrement(strikes.LongPutStrike, increment);
            strikes.ShortCallStrike = RoundToIncrement(strikes.ShortCallStrike, increment);
            strikes.LongCallStrike = RoundToIncrement(strikes.LongCallStrike, increment);

            return strikes;
        }

        /// <summary>
        /// Rounds a strike price to the nearest valid increment
        /// </summary>
        private decimal RoundToIncrement(decimal strike, decimal increment)
        {
            return Math.Round(strike / increment) * increment;
        }

        /// <summary>
        /// Validates strike spacing using structural analysis instead of strategy names
        /// </summary>
        public bool ValidateStrikeSpacing(StrikeRange strikes)
        {
            // Detect strategy pattern from strike structure
            var hasLongStrikes = strikes.LongPutStrike > 0 || strikes.LongCallStrike > 0;
            var hasShortStrikes = strikes.ShortPutStrike > 0 || strikes.ShortCallStrike > 0;
            var hasBothPutAndCall = strikes.ShortPutStrike > 0 && strikes.ShortCallStrike > 0;
            
            // 4-strike pattern (Iron Condor/Butterfly structure)
            if (hasLongStrikes && hasShortStrikes && hasBothPutAndCall && 
                strikes.LongPutStrike > 0 && strikes.LongCallStrike > 0)
            {
                // Validate 4-leg structure: Long Put < Short Put < ATM < Short Call < Long Call
                return strikes.LongPutStrike < strikes.ShortPutStrike &&
                       strikes.ShortPutStrike < strikes.ATMStrike &&
                       strikes.ATMStrike < strikes.ShortCallStrike &&
                       strikes.ShortCallStrike < strikes.LongCallStrike;
            }
            
            // 2-strike pattern with both puts and calls (Strangle/Straddle structure)
            if (hasShortStrikes && hasBothPutAndCall && !hasLongStrikes)
            {
                // Validate short strikes are on opposite sides of ATM
                return strikes.ShortPutStrike < strikes.ATMStrike &&
                       strikes.ShortCallStrike > strikes.ATMStrike;
            }
            
            // Single-leg or spread patterns - basic validation
            return ValidateBasicStrikeOrder(strikes);
        }
        
        /// <summary>
        /// Validates basic strike ordering for any strategy pattern
        /// </summary>
        private bool ValidateBasicStrikeOrder(StrikeRange strikes)
        {
            // Ensure put strikes are below ATM and call strikes are above ATM
            bool validPutSide = strikes.ShortPutStrike <= 0 || strikes.ShortPutStrike < strikes.ATMStrike;
            bool validCallSide = strikes.ShortCallStrike <= 0 || strikes.ShortCallStrike > strikes.ATMStrike;
            
            // Ensure long strikes are outside short strikes if both exist
            bool validLongPut = strikes.LongPutStrike <= 0 || strikes.ShortPutStrike <= 0 || 
                               strikes.LongPutStrike < strikes.ShortPutStrike;
            bool validLongCall = strikes.LongCallStrike <= 0 || strikes.ShortCallStrike <= 0 || 
                                strikes.LongCallStrike > strikes.ShortCallStrike;
            
            return validPutSide && validCallSide && validLongPut && validLongCall;
        }

        /// <summary>
        /// Gets strike selection statistics for reporting
        /// </summary>
        public Dictionary<string, object> GetStrikeStats(StrikeRange strikes)
        {
            return new Dictionary<string, object>
            {
                ["ATMStrike"] = strikes.ATMStrike,
                ["PutSpreadWidth"] = strikes.ShortPutStrike - strikes.LongPutStrike,
                ["CallSpreadWidth"] = strikes.LongCallStrike - strikes.ShortCallStrike,
                ["TotalWidth"] = strikes.LongCallStrike - strikes.LongPutStrike,
                ["PutDistance"] = (strikes.ATMStrike - strikes.ShortPutStrike) / strikes.ATMStrike,
                ["CallDistance"] = (strikes.ShortCallStrike - strikes.ATMStrike) / strikes.ATMStrike
            };
        }
    }
}
using System;
using System.Collections.Concurrent;
using System.Collections.Generic;
using System.Linq;
using QuantConnect;
using QuantConnect.Algorithm;
using QuantConnect.Data.Market;
using QuantConnect.Data.UniverseSelection;
using QuantConnect.Securities;

namespace CoreAlgo.Architecture.QC.Helpers
{
    /// <summary>
    /// Helper class for optimizing large universe strategies.
    /// Provides efficient batch processing, memory management, and QC-native patterns.
    /// </summary>
    public static class UniverseOptimizer
    {
        /// <summary>
        /// Fetches historical data in batches to avoid memory issues with large universes.
        /// Processes symbols in chunks to prevent timeouts and optimize performance.
        /// </summary>
        /// <param name="algorithm">The QCAlgorithm instance</param>
        /// <param name="symbols">List of symbols to fetch data for</param>
        /// <param name="days">Number of days of history</param>
        /// <param name="resolution">Data resolution</param>
        /// <param name="batchSize">Number of symbols to process per batch (default: 500)</param>
        /// <returns>Dictionary mapping Symbol to list of TradeBar volumes</returns>
        public static Dictionary<Symbol, List<decimal>> BatchFetchHistory(
            QCAlgorithm algorithm,
            IEnumerable<Symbol> symbols,
            int days,
            Resolution resolution = Resolution.Daily,
            int batchSize = 500)
        {
            var results = new ConcurrentDictionary<Symbol, List<decimal>>();
            var symbolList = symbols.ToList();
            
            // Process in batches to avoid memory issues and timeouts
            for (int i = 0; i < symbolList.Count; i += batchSize)
            {
                var batch = symbolList.Skip(i).Take(batchSize).ToList();
                
                try
                {
                    var history = algorithm.History<TradeBar>(batch, days, resolution);
                    
                    foreach (var dateGroup in history)
                    {
                        foreach (var symbolData in dateGroup)
                        {
                            var symbol = symbolData.Key;
                            var bar = symbolData.Value;
                            
                            if (!results.ContainsKey(symbol))
                            {
                                results[symbol] = new List<decimal>();
                            }
                            
                            results[symbol].Add(bar.Volume);
                        }
                    }
                }
                catch (Exception ex)
                {
                    algorithm.Log($"Error fetching history for batch starting at index {i}: {ex.Message}");
                }
            }
            
            return results.ToDictionary(kvp => kvp.Key, kvp => kvp.Value);
        }

        /// <summary>
        /// Calculates Average Daily Volume (ADV) for multiple symbols efficiently.
        /// Uses batch processing to handle large universes without memory issues.
        /// </summary>
        /// <param name="algorithm">The QCAlgorithm instance</param>
        /// <param name="symbols">Symbols to calculate ADV for</param>
        /// <param name="days">Number of days for ADV calculation</param>
        /// <returns>Dictionary mapping Symbol to ADV value</returns>
        public static Dictionary<Symbol, decimal> CalculateBatchedADV(
            QCAlgorithm algorithm,
            IEnumerable<Symbol> symbols,
            int days = 21)
        {
            var historyData = BatchFetchHistory(algorithm, symbols, days, Resolution.Daily);
            var advResults = new Dictionary<Symbol, decimal>();
            
            foreach (var kvp in historyData)
            {
                var symbol = kvp.Key;
                var volumes = kvp.Value;
                
                if (volumes.Any())
                {
                    advResults[symbol] = volumes.Average();
                }
                else
                {
                    advResults[symbol] = 0;
                }
            }
            
            return advResults;
        }

        /// <summary>
        /// Optimized volume shock calculation for large universes.
        /// Compares current intraday volume to historical average efficiently.
        /// </summary>
        /// <param name="algorithm">The QCAlgorithm instance</param>
        /// <param name="intradayVolumes">Current intraday volume data</param>
        /// <param name="symbols">Universe symbols to process</param>
        /// <param name="advDays">Days for ADV calculation</param>
        /// <returns>Dictionary mapping Symbol to volume shock ratio</returns>
        public static Dictionary<Symbol, decimal> CalculateVolumeShock(
            QCAlgorithm algorithm,
            ConcurrentDictionary<Symbol, long> intradayVolumes,
            IEnumerable<Symbol> symbols,
            int advDays = 21)
        {
            // Get ADV for all symbols in batch
            var advData = CalculateBatchedADV(algorithm, symbols, advDays);
            var shockRatios = new Dictionary<Symbol, decimal>();
            
            foreach (var symbol in symbols)
            {
                var intradayVol = intradayVolumes.GetValueOrDefault(symbol, 0L);
                var adv = advData.GetValueOrDefault(symbol, 0m);
                
                var ratio = adv > 0 ? (decimal)intradayVol / adv : 0m;
                shockRatios[symbol] = ratio;
            }
            
            return shockRatios;
        }

        /// <summary>
        /// Efficiently cleans up removed securities from tracking structures.
        /// Prevents memory leaks in large universe strategies.
        /// </summary>
        /// <param name="algorithm">The QCAlgorithm instance</param>
        /// <param name="changes">Security changes from OnSecuritiesChanged</param>
        /// <param name="trackingDictionaries">Collection of dictionaries to clean up</param>
        public static void CleanupRemovedSecurities(
            QCAlgorithm algorithm,
            SecurityChanges changes,
            params object[] trackingDictionaries)
        {
            foreach (var removed in changes.RemovedSecurities)
            {
                // Liquidate any positions in removed securities
                if (algorithm.Portfolio[removed.Symbol].Invested)
                {
                    algorithm.Log($"Liquidating position in removed security: {removed.Symbol}");
                    algorithm.Liquidate(symbol: removed.Symbol, tag: "Security removed from universe");
                }
                
                // Clean up tracking dictionaries
                foreach (var dict in trackingDictionaries)
                {
                    switch (dict)
                    {
                        case ConcurrentDictionary<Symbol, long> longDict:
                            longDict.TryRemove(removed.Symbol, out _);
                            break;
                        case ConcurrentDictionary<Symbol, decimal> decimalDict:
                            decimalDict.TryRemove(removed.Symbol, out _);
                            break;
                        case Dictionary<Symbol, long> simpleLongDict:
                            simpleLongDict.Remove(removed.Symbol);
                            break;
                        case Dictionary<Symbol, decimal> simpleDecimalDict:
                            simpleDecimalDict.Remove(removed.Symbol);
                            break;
                        case HashSet<Symbol> symbolSet:
                            symbolSet.Remove(removed.Symbol);
                            break;
                    }
                }
            }
        }

        /// <summary>
        /// Sets up optimized universe settings for large universe strategies.
        /// Configures async selection and other performance optimizations.
        /// </summary>
        /// <param name="algorithm">The QCAlgorithm instance</param>
        /// <param name="resolution">Universe data resolution</param>
        /// <param name="enableAsync">Enable asynchronous universe selection</param>
        /// <param name="extendedHours">Enable extended market hours</param>
        public static void SetupOptimizedUniverse(
            QCAlgorithm algorithm,
            Resolution resolution = Resolution.Minute,
            bool enableAsync = true,
            bool extendedHours = false)
        {
            algorithm.UniverseSettings.Resolution = resolution;
            algorithm.UniverseSettings.Asynchronous = enableAsync;
            algorithm.UniverseSettings.ExtendedMarketHours = extendedHours;
            
            algorithm.Log($"Universe configured: Resolution={resolution}, Async={enableAsync}, ExtendedHours={extendedHours}");
        }

        /// <summary>
        /// Processes a large universe in parallel for improved performance.
        /// Useful for computationally intensive universe selection logic.
        /// </summary>
        /// <typeparam name="TInput">Input data type</typeparam>
        /// <typeparam name="TResult">Result data type</typeparam>
        /// <param name="data">Input data collection</param>
        /// <param name="processor">Function to process each item</param>
        /// <param name="batchSize">Batch size for processing</param>
        /// <returns>Collection of results</returns>
        public static IEnumerable<TResult> ProcessInParallel<TInput, TResult>(
            IEnumerable<TInput> data,
            Func<TInput, TResult> processor,
            int batchSize = 500)
        {
            var results = new ConcurrentBag<TResult>();
            var batches = data.Batch(batchSize);
            
            System.Threading.Tasks.Parallel.ForEach(batches, batch =>
            {
                foreach (var item in batch)
                {
                    try
                    {
                        var result = processor(item);
                        results.Add(result);
                    }
                    catch
                    {
                        // Log error but continue processing
                    }
                }
            });
            
            return results;
        }
    }

    /// <summary>
    /// Extension methods for universe optimization
    /// </summary>
    public static class UniverseExtensions
    {
        /// <summary>
        /// Batches an enumerable into chunks of specified size
        /// </summary>
        public static IEnumerable<IEnumerable<T>> Batch<T>(this IEnumerable<T> source, int batchSize)
        {
            var batch = new List<T>(batchSize);
            foreach (var item in source)
            {
                batch.Add(item);
                if (batch.Count == batchSize)
                {
                    yield return batch;
                    batch = new List<T>(batchSize);
                }
            }
            if (batch.Count > 0)
                yield return batch;
        }
    }
}
#region imports
    using System;
    using System.Collections;
    using System.Collections.Generic;
    using System.Linq;
    using System.Globalization;
    using System.Drawing;
    using QuantConnect;
    using QuantConnect.Algorithm.Framework;
    using QuantConnect.Algorithm.Framework.Selection;
    using QuantConnect.Algorithm.Framework.Alphas;
    using QuantConnect.Algorithm.Framework.Portfolio;
    using QuantConnect.Algorithm.Framework.Portfolio.SignalExports;
    using QuantConnect.Algorithm.Framework.Execution;
    using QuantConnect.Algorithm.Framework.Risk;
    using QuantConnect.Algorithm.Selection;
    using QuantConnect.Api;
    using QuantConnect.Parameters;
    using QuantConnect.Benchmarks;
    using QuantConnect.Brokerages;
    using QuantConnect.Commands;
    using QuantConnect.Configuration;
    using QuantConnect.Util;
    using QuantConnect.Interfaces;
    using QuantConnect.Algorithm;
    using QuantConnect.Indicators;
    using QuantConnect.Data;
    using QuantConnect.Data.Auxiliary;
    using QuantConnect.Data.Consolidators;
    using QuantConnect.Data.Custom;
    using QuantConnect.Data.Custom.IconicTypes;
    using QuantConnect.DataSource;
    using QuantConnect.Data.Fundamental;
    using QuantConnect.Data.Market;
    using QuantConnect.Data.Shortable;
    using QuantConnect.Data.UniverseSelection;
    using QuantConnect.Notifications;
    using QuantConnect.Orders;
    using QuantConnect.Orders.Fees;
    using QuantConnect.Orders.Fills;
    using QuantConnect.Orders.OptionExercise;
    using QuantConnect.Orders.Slippage;
    using QuantConnect.Orders.TimeInForces;
    using QuantConnect.Python;
    using QuantConnect.Scheduling;
    using QuantConnect.Securities;
    using QuantConnect.Securities.Equity;
    using QuantConnect.Securities.Future;
    using QuantConnect.Securities.Option;
    using QuantConnect.Securities.Positions;
    using QuantConnect.Securities.Forex;
    using QuantConnect.Securities.Crypto;
    using QuantConnect.Securities.CryptoFuture;
    using QuantConnect.Securities.IndexOption;
    using QuantConnect.Securities.Interfaces;
    using QuantConnect.Securities.Volatility;
    using QuantConnect.Storage;
    using QuantConnect.Statistics;
    using QCAlgorithmFramework = QuantConnect.Algorithm.QCAlgorithm;
    using QCAlgorithmFrameworkBridge = QuantConnect.Algorithm.QCAlgorithm;
    using Calendar = QuantConnect.Data.Consolidators.Calendar;
    using CoreAlgo.Architecture.Core.Interfaces;
    using CoreAlgo.Architecture.Core.Implementations;
    using CoreAlgo.Architecture.Core.Templates;
    using CoreAlgo.Architecture.Core.Models;
    using CoreAlgo.Architecture.Core.Services;
#endregion
namespace QuantConnect.Algorithm.CSharp
{
    public class CoreAlgo : QCAlgorithm
    {
        private IStrategy _strategy;
        private int _debugCallCount = 0;
        public QCLogger<CoreAlgo> Logger { get; private set; }
        private bool _postWarmupSliceLogged = false;
        private bool _warmupCompleteLogged = false;
        private bool _dailySnapshotSaved = false;
        
        // Static constructor to verify class loading
        static CoreAlgo()
        {
            // This should execute when the class is first loaded
            System.Diagnostics.Debug.WriteLine("=== STATIC CONSTRUCTOR: CoreAlgo class loaded ===");
        }
        
        // Constructor to verify instance creation
        public CoreAlgo()
        {
            // This should execute when instance is created
            System.Diagnostics.Debug.WriteLine("=== CONSTRUCTOR: CoreAlgo instance created ===");
        }

        public override void Initialize()
        {
            try 
            {
                // Apply basic algorithm configuration using GetParameter with defaults
                var environment = GetParameter("Environment", "development");
                var startDate = GetParameter("StartDate", "2023-12-25");
                var endDate = GetParameter("EndDate", "2024-12-25");
                var accountSize = GetParameter("AccountSize", 100000m);
                
                SetStartDate(DateTime.Parse(startDate));
                SetEndDate(DateTime.Parse(endDate));
                SetCash(accountSize);

                SetBrokerageModel(BrokerageName.InteractiveBrokersBrokerage, AccountType.Margin);

                // CRITICAL FIX: Disable LEAN's broken position group buying power model
                // LEAN doesn't recognize combo orders as spreads and calculates margin incorrectly
                Portfolio.SetPositions(SecurityPositionGroupModel.Null);
                
                // Global security initializer - disables buying power for all securities (matches Python)
                SetSecurityInitializer(CompleteSecurityInitializer);
                // Ensure universe-added securities (including option chain universes) are intraday
                UniverseSettings.Resolution = Resolution.Minute;
                UniverseSettings.ExtendedMarketHours = false;
                UniverseSettings.DataNormalizationMode = DataNormalizationMode.Raw;
                Log($"UNIVERSE SETTINGS -> Resolution: {UniverseSettings.Resolution}, FillForward: {UniverseSettings.FillForward}, ExtendedMktHours: {UniverseSettings.ExtendedMarketHours}, DataNormalizationMode: {UniverseSettings.DataNormalizationMode}");
                Logger?.Info($"UniverseSettings configured. Resolution={UniverseSettings.Resolution}, FillForward={UniverseSettings.FillForward}, ExtendedMarketHours={UniverseSettings.ExtendedMarketHours}, DataNormalizationMode={UniverseSettings.DataNormalizationMode}");
                
                Log("BASIC SETUP COMPLETED");
                
                // Initialize smart logger with debug logging
                Log("=== DEBUG: About to create QCLogger ===");
                var logLevel = GetParameter("LogLevel", 3); // Default to Info level (2 = Info, 3 = Debug)
                var verboseMode = bool.Parse(GetParameter("VerboseMode", "false")); // Allow forcing verbose output in backtest
                
                if (verboseMode)
                {
                    Log("=== VERBOSE MODE ENABLED - All INFO logs will output immediately ===");
                    Log("=== Use VerboseMode only for debugging - it may exceed cloud log limits ===");
                }
                
                Logger = new QCLogger<CoreAlgo>(this, logLevel);
                Log($"=== DEBUG: Smart logger created: {Logger != null} ===");
                Log($"=== DEBUG: LogLevel: {logLevel}, VerboseMode: {verboseMode}, LiveMode: {LiveMode} ===");
                
                // Test smart logger immediately
                Logger.Info($"Algorithm initialized for environment: {environment}");
                
                Log("ABOUT TO INITIALIZE STRATEGY");
            }
            catch (Exception ex)
            {
                Error($"INITIALIZE FAILED: {ex.Message}");
                Error($"STACK TRACE: {ex.StackTrace}");
                throw;
            }
            
            // Strategy Selection - dynamically discovered from Templates folder
            var strategyType = GetParameter("Strategy", "IronCondor");
            Log($"Initializing strategy: {strategyType}");
            Logger.Info($"Initializing strategy: {strategyType}");
            
            try
            {
                _strategy = StrategyDiscovery.CreateStrategy(strategyType);
            }
            catch (ArgumentException ex)
            {
                Log($"Unknown strategy '{strategyType}', defaulting to IronCondor. {ex.Message}");
                Logger.Warning($"Unknown strategy '{strategyType}', defaulting to IronCondor. {ex.Message}");
                _strategy = StrategyDiscovery.CreateStrategy("IronCondor");
            }
            
            // Initialize the strategy with this algorithm instance
            try
            {
                // Inject the centralized logger into the strategy BEFORE initialization (context pattern)
                if (_strategy is SimpleBaseStrategy baseStrategy)
                {
                    baseStrategy.SetContext(Logger);
                    Logger.Debug("Logger injected into strategy via context pattern");
                }
                
                // Allow strategy configs to override UniverseSettings prior to initialization
                if (_strategy is SimpleBaseStrategy simpleStrategy)
                {
                    var desiredResolution = simpleStrategy?.Config?.GetUnderlyingResolution() ?? Resolution.Minute;
                    UniverseSettings.Resolution = desiredResolution;
                    Logger.Debug($"Strategy requested UniverseSettings resolution: {desiredResolution}");
                }

                _strategy.Initialize(this);
                
                Log($"Strategy '{_strategy.Name}' initialized successfully");
                Logger.Info($"Strategy '{_strategy.Name}' initialized successfully");
                
                Log($"Strategy state: {_strategy.State}");
                Logger.Info($"Strategy state: {_strategy.State}");
                
                Log($"Description: {_strategy.Description}");
                Logger.Info($"Description: {_strategy.Description}");
                
                Log("Ready for options trading!");
                Logger.Info("Ready for options trading!");
                
                // Initialize SmartPricing if configured
                if (_strategy is SimpleBaseStrategy baseStrategy2)
                {
                    Logger.Info("Calling EnsureSmartPricingInitialized...");
                    baseStrategy2.EnsureSmartPricingInitialized();
                    Logger.Info("EnsureSmartPricingInitialized completed");
                    
                    // Ensure ObjectStore positions are cleared for backtests (guarantee clean state)
                    baseStrategy2.TradePersistence?.ClearPositionsIfBacktest();
                    baseStrategy2.TradePersistence?.ClearSnapshotsIfBacktest();
                    Logger.Info("ObjectStore backtest cleanup completed");
                }
                else
                {
                    Logger.Warning($"Strategy is not SimpleBaseStrategy: {_strategy?.GetType().Name}");
                }
            }
            catch (Exception ex)
            {
                Error($"Failed to initialize strategy: {ex.Message}");
                Logger.LogError(ex, "Failed to initialize strategy");
                
                _strategy = null; // Prevent strategy execution
                Error("Strategy initialization failed - algorithm cannot trade");
                Logger.Error("Strategy initialization failed - algorithm cannot trade");
            }
        }

        /// OnData event is the primary entry point for your algorithm. Each new data point will be pumped in here.
        /// Slice object keyed by symbol containing the stock data
        public override void OnData(Slice data)
        {
            // SMART DEBUG: Track execution flow with automatic deduplication
            _debugCallCount++;
            
            // Skip strategy execution during warmup period
            if (IsWarmingUp)
            {
                return;
            }
            // One-time warmup completion diagnostics with option chain visibility
            if (!_warmupCompleteLogged)
            {
                var chainsCount = data.OptionChains.Count;
                var keys = chainsCount > 0 ? string.Join(", ", data.OptionChains.Keys) : "<none>";
                Logger?.Info($"WARMUP COMPLETED at {Time:yyyy-MM-dd HH:mm:ss} - LiveMode: {LiveMode}, Resolution: {UniverseSettings.Resolution}");
                Logger?.Info($"Option chain snapshot - Count: {chainsCount}, Keys: [{keys}]");
                _warmupCompleteLogged = true;
            }
            
            // Keep existing post-warmup slice logging for additional diagnostics
            if (!_postWarmupSliceLogged)
            {
                var chainsCount = data.OptionChains.Count;
                var keys = chainsCount > 0 ? string.Join(", ", data.OptionChains.Keys) : "<none>";
                Logger?.Info($"POST-WARMUP SLICE: OptionChains.Count={chainsCount}; Keys=[{keys}] at {Time:yyyy-MM-dd HH:mm:ss}");
                _postWarmupSliceLogged = true;
            }
            
            			// Only log option chain activity when chains are available (demoted to Debug to reduce noise)
			if (data.OptionChains.Count > 0)
			{
				Logger.Debug($"Options chains available: {data.OptionChains.Count}");
			}
            
            // Execute strategy if properly initialized
            if (_strategy != null)
            {
                try
                {
                    _strategy.Execute(data);
                }
                catch (Exception ex)
                {
                    Logger.LogError(ex, "Strategy execution error");
                }
            }
            else
            {
                // CRITICAL: Strategy failed to initialize - algorithm cannot trade
                if (_debugCallCount <= 5)
                {
                    Logger.Error("CRITICAL: No strategy initialized - algorithm cannot trade");
                    Logger.Error("Check strategy initialization logs above for errors");
                }
            }
        }

        /// <summary>
        /// Handle security changes from universe selection and forward to strategy
        /// </summary>
        public override void OnSecuritiesChanged(SecurityChanges changes)
        {
            base.OnSecuritiesChanged(changes);
            
            // Forward to strategy if it implements universe handling
            if (_strategy is SimpleBaseStrategy baseStrategy)
            {
                try
                {
                    baseStrategy.OnSecuritiesChanged(changes);
                }
                catch (Exception ex)
                {
                    Logger.LogError(ex, "Error in strategy OnSecuritiesChanged");
                }
            }
        }

        /// <summary>
        /// Called when order events occur (fills, cancellations, etc.)
        /// Routes order events to SmartOrderManager for progressive pricing and trade tracking
        /// </summary>
        public override void OnOrderEvent(OrderEvent orderEvent)
        {
            // Let the base class handle the event first
            base.OnOrderEvent(orderEvent);
            
            // Single cast - exit early if not a SimpleBaseStrategy
            if (!(_strategy is SimpleBaseStrategy s)) return;
            
            // Route to SmartOrderManager if available
            s.SmartOrderManager?.OnOrderEvent(orderEvent);
            
            // Track order events for trade tracking system (like Python position tracking)
            var orderId = orderEvent.OrderId.ToString();
            
            switch (orderEvent.Status)
            {
                case OrderStatus.Submitted:
                    // Working orders are tracked at order placement via TrackWorkingOrder
                    // Just persist state change here
                    s.TradePersistence.SaveTrades(s.TradeTracker);
                    break;
                    
                case OrderStatus.PartiallyFilled:
                    var trade = s.TradeTracker.AllTrades.FirstOrDefault(t => t.OrderId == orderId);
                    if (trade != null)
                    {
                        trade.MarkAsPartialFill((int)orderEvent.FillQuantity, orderEvent.FillPrice);
                        Logger.Debug($"Partial fill: {orderId} - {orderEvent.FillQuantity} @ {orderEvent.FillPrice}");
                        s.TradePersistence.SaveTrades(s.TradeTracker);
                    }
                    break;
                    
                case OrderStatus.Filled:
                    s.TrackOrderFilled(orderEvent);
                    break;
                    
                case OrderStatus.Canceled:
                    s.TrackOrderCancelled(orderId);
                    break;
            }
            
            // Route ALL events to strategy for event-time logic (swap-to-TP, etc.)
            s.OnOrderEventRouted(orderEvent);
        }
        
        /// <summary>
        /// Called at the end of each trading day for each symbol
        /// Always process batched logs (DEBUG and non-verbose INFO)
        /// Even in LiveMode we want daily summaries of DEBUG messages
        /// </summary>
        public override void OnEndOfDay(Symbol symbol)
        {
            // Debug message to verify OnEndOfDay is called
            Logger?.Debug($"OnEndOfDay called for symbol {symbol}");
            
            // Save daily snapshot of trades (only once per day, not per symbol)
            if (_strategy is SimpleBaseStrategy strategy)
            {
                if (strategy.TradePersistence != null && !_dailySnapshotSaved)
                {
                    try
                    {
                        strategy.TradePersistence.SaveDailySnapshot(strategy.TradeTracker);
                        _dailySnapshotSaved = true;
                        
                        // Log daily trade report
                        var summary = strategy.TradePersistence.GetTradeSummary(strategy.TradeTracker);
                        Logger?.Info($"=== EOD Trade Summary {Time:yyyy-MM-dd} ===\n" +
                            $"Total: {summary.TotalTrades}, Working: {summary.WorkingCount}, \n" +
                            $"Open: {summary.OpenCount}, Closed: {summary.ClosedCount}, \n" +
                            $"P&L: ${summary.TotalPnL:F2}");
                    }
                    catch (Exception ex)
                    {
                        Logger?.Error($"Error in EOD trade snapshot: {ex.Message}");
                        Log($"ERROR in EOD trade snapshot: {ex.Message}");
                    }
                }
                else if (strategy.TradePersistence == null)
                {
                    // Debug why persistence is null
                    Log($"DEBUG: TradePersistence is null in OnEndOfDay for {strategy.Name}");
                }
            }
            
            // Always process batched logs for daily summary
            // This includes DEBUG logs (always batched) and INFO logs (when not verbose)
            // Even in LiveMode, we want the DEBUG daily summary
            SmartLoggerStore.ProcessDailyLogs(this);
            
            // Reset daily snapshot flag for next day
            _dailySnapshotSaved = false;
        }

        public override void OnEndOfAlgorithm()
        {
            // DEBUG: Immediate verification that OnEndOfAlgorithm() is called
            Log("=== DEBUG: OnEndOfAlgorithm() CALLED - IMMEDIATE CONFIRMATION ===");
            
            // Save final trade state to ObjectStore
            if (_strategy is SimpleBaseStrategy strategy)
            {
                if (strategy.TradePersistence != null)
                {
                    try
                    {
                        strategy.TradePersistence.SaveTrades(strategy.TradeTracker);
                        var summary = strategy.TradePersistence.GetTradeSummary(strategy.TradeTracker);
                        Logger?.Info($"=== Final Trade Summary ===\n" +
                            $"Total Trades: {summary.TotalTrades}\n" +
                            $"  Working: {summary.WorkingCount}, Open: {summary.OpenCount}, Closed: {summary.ClosedCount}\n" +
                            $"  Total P&L: ${summary.TotalPnL:F2}");
                        Logger?.Info("=== Final Trade Summary saved to ObjectStore ===");
                    }
                    catch (Exception ex)
                    {
                        Logger?.Error($"=== ERROR saving final trade state: {ex.Message} ===");
                    }
                }
                else
                {
                    Logger?.Error("=== DEBUG: TradePersistence is null in OnEndOfAlgorithm ===");
                }
            }
            
            // Check smart logger state
            Log($"=== DEBUG: Smart logger null check: {Logger == null} ===");
            
            // Check static collection state before processing
            var (dailyCount, groupCount) = SmartLoggerStore.GetCollectionCounts();
            Log($"=== DEBUG: Collection counts - Daily: {dailyCount}, Groups: {groupCount} ===");
            
            // Process any remaining accumulated smart logs (fallback for symbols that didn't trigger OnEndOfDay)
            Log("=== DEBUG: About to call ProcessDailyLogs ===");
            SmartLoggerStore.ProcessDailyLogs(this);
            Log("=== DEBUG: ProcessDailyLogs completed ===");
            
            // Test smart logger one more time
            if (Logger != null)
            {
                Logger.Info($"Algorithm finished. Final portfolio value: ${Portfolio.TotalPortfolioValue:N2}");
                Logger.Info($"Total trades: {Transactions.GetOrderTickets().Count()}");
                Log("=== DEBUG: Smart logger calls in OnEndOfAlgorithm completed ===");
            }
            else
            {
                Log("=== DEBUG: Smart logger is NULL in OnEndOfAlgorithm ===");
            }
            
            // Final confirmation
            Log("=== DEBUG: OnEndOfAlgorithm() FINISHED ===");
        }

        /// <summary>
        /// Forward assignment events to the strategy for handling
        /// </summary>
        public override void OnAssignmentOrderEvent(OrderEvent assignmentEvent)
        {
            Log($"Assignment event forwarded to strategy: {assignmentEvent.Symbol}");
            
            // Forward to strategy if it supports assignment handling
            if (_strategy is SimpleBaseStrategy strategy)
            {
                strategy.OnAssignmentOrderEvent(assignmentEvent);
            }
            else
            {
                Log($"Strategy does not support assignment handling: {_strategy?.GetType().Name ?? "null"}");
            }
        }
        
        /// <summary>
        /// Global security initializer that matches Python setupbasestructure.py CompleteSecurityInitializer
        /// Disables buying power for most securities and applies backtesting configurations
        /// Special handling for SPX index options (cash-settled)
        /// </summary>
        private void CompleteSecurityInitializer(Security security)
        {
            Logger?.Debug($"CompleteSecurityInitializer: {security.Symbol} ({security.Type})");
            
            // Special handling for SPX index options (cash-settled European-style options)
            if (security.Type == SecurityType.IndexOption)
            {
                Logger?.Info($"Configuring SPX index option: {security.Symbol}");
                
                // Use null buying power model for index options (no margin requirements)
                security.SetBuyingPowerModel(BuyingPowerModel.Null);
                
                // CRITICAL FIX: Disable option assignment for cash-settled index options
                // SPX options are cash-settled and should not simulate physical assignment
                if (security is IndexOption indexOption)
                {
                    indexOption.SetOptionAssignmentModel(new NullOptionAssignmentModel());
                    Logger?.Debug($"Applied NullOptionAssignmentModel to {security.Symbol}");
                }
            }
            else
            {
                // CRITICAL: Disable buying power on other securities (matches Python)
                security.SetBuyingPowerModel(BuyingPowerModel.Null);
            }
            
            // Apply dynamic slippage model that adapts to volume and price
            // VolumeShareSlippageModel automatically handles different security types
            // Default parameters: volumeLimit=0.025 (2.5%), priceImpact=0.1
            security.SetSlippageModel(new VolumeShareSlippageModel());
            
            // Skip backtesting-specific configurations in live mode (matches Python pattern)
            if (LiveMode)
                return;
            
            // Backtesting configurations - set RAW normalization via subscription configs
            security.Subscriptions.SetDataNormalizationMode(DataNormalizationMode.Raw);
            var mode = security.Subscriptions.FirstOrDefault()?.DataNormalizationMode;
            Logger?.Debug($"Initializer RAW set: {security.Symbol} => {mode}");
            
            var lastPrices = GetLastKnownPrices(security);
            if (lastPrices != null && lastPrices.Any())
            {
                security.SetMarketPrice(lastPrices.First());
            }
        }

    }
}