Overall Statistics
Total Orders
216
Average Win
0.80%
Average Loss
-0.19%
Compounding Annual Return
1.084%
Drawdown
9.400%
Expectancy
0.221
Start Equity
100000
End Equity
106269.18
Net Profit
6.269%
Sharpe Ratio
-0.846
Sortino Ratio
-0.465
Probabilistic Sharpe Ratio
1.406%
Loss Rate
77%
Win Rate
23%
Profit-Loss Ratio
4.23
Alpha
-0.024
Beta
0.012
Annual Standard Deviation
0.027
Annual Variance
0.001
Information Ratio
-0.612
Tracking Error
0.176
Treynor Ratio
-1.98
Total Fees
$585.41
Estimated Strategy Capacity
$34000.00
Lowest Capacity Asset
CLBT XRGF2SRY5D9H
Portfolio Turnover
0.50%
Drawdown Recovery
9
#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;

            // 1. Trading Hours Check
            if (!IsWithinTradingHours(slice.Time))
            {
                reason = $"Outside trading hours ({_config.TradingStartTime}-{_config.TradingEndTime})";
                return false;
            }

            // 2. Max Positions Check
            if (!HasCapacityForNewPosition())
            {
                reason = $"Max positions reached ({_config.MaxPositions})";
                return false;
            }

            // 3. Existing Position Check
            if (HasExistingPosition(symbol))
            {
                reason = "Already have position in this symbol";
                return false;
            }

            // 4. Available Capital Check
            if (!HasSufficientCapital())
            {
                reason = "Insufficient capital for new position";
                return false;
            }

            // 5. Volatility Check (if configured)
            if (_config.MinImpliedVolatility > 0 && !MeetsVolatilityRequirement(symbol, slice))
            {
                reason = $"Implied volatility below minimum ({_config.MinImpliedVolatility:P0})";
                return false;
            }

            // 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.
        /// </summary>
        private bool IsWithinTradingHours(DateTime currentTime)
        {
            var timeOfDay = currentTime.TimeOfDay;
            return timeOfDay >= _config.TradingStartTime && timeOfDay <= _config.TradingEndTime;
        }

        /// <summary>
        /// Check if we have capacity for a new position based on MaxPositions.
        /// </summary>
        private bool HasCapacityForNewPosition()
        {
            var currentPositions = _algorithm.Portfolio
                .Where(kvp => kvp.Value.Invested)
                .Count();
            
            return currentPositions < _config.MaxPositions;
        }

        /// <summary>
        /// Check if we already have a position in this symbol.
        /// </summary>
        private bool HasExistingPosition(Symbol symbol)
        {
            // Check direct position
            if (_algorithm.Portfolio[symbol].Invested)
                return true;

            // For options, also check if we have positions in the underlying
            if (symbol.SecurityType == SecurityType.Option)
            {
                var underlying = symbol.Underlying;
                return _algorithm.Portfolio.Any(kvp => 
                    kvp.Key.Underlying == underlying && kvp.Value.Invested);
            }

            return false;
        }

        /// <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 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 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
    {
        /// <summary>
        /// Handle option assignment by immediately liquidating assigned underlying shares.
        /// Uses QC's Portfolio system for position tracking and MarketOrder for liquidation.
        /// </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}");
                
                // Get the assigned position from QC's Portfolio system
                var assignedSymbol = assignmentEvent.Symbol;
                
                // For option assignments, we need to check the underlying symbol
                var underlyingSymbol = GetUnderlyingSymbol(assignedSymbol);
                
                if (underlyingSymbol == null)
                {
                    algorithm.Error($"Could not determine underlying symbol for assignment: {assignedSymbol}");
                    return;
                }
                
                // Check if we have an equity position from the assignment using QC's Portfolio
                var holding = algorithm.Portfolio[underlyingSymbol];
                
                if (holding.Invested && holding.Quantity != 0)
                {
                    algorithm.Log($"Assignment Details: {underlyingSymbol.Value} - Quantity: {holding.Quantity}, " +
                                 $"Value: ${holding.HoldingsValue:F0}");
                    
                    // Execute immediate liquidation (simplified approach)
                    ExecuteLiquidation(algorithm, underlyingSymbol, holding);
                }
                else
                {
                    algorithm.Log($"WARNING: No equity position found for assignment: {underlyingSymbol.Value}");
                }
                
                // Log assignment impact for tracking
                LogAssignmentImpact(algorithm, assignmentEvent, underlyingSymbol);
            }
            catch (Exception ex)
            {
                algorithm.Error($"Error handling assignment: {ex.Message}");
                algorithm.Error($"Stack trace: {ex.StackTrace}");
            }
        }
        
        /// <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>
        /// Execute immediate liquidation of assigned shares.
        /// </summary>
        private static void ExecuteLiquidation(QCAlgorithm algorithm, Symbol underlyingSymbol, SecurityHolding holding)
        {
            // Use QC's MarketOrder for immediate liquidation
            var liquidationQuantity = -(int)holding.Quantity; // Opposite sign to close position, convert to int
            var orderTicket = algorithm.MarketOrder(underlyingSymbol, liquidationQuantity, tag: "Assignment Auto-Liquidation");
            
            algorithm.Log($"LIQUIDATING ASSIGNMENT: {underlyingSymbol.Value} - " +
                         $"Quantity: {liquidationQuantity}, Order ID: {orderTicket.OrderId}");
        }
        
        /// <summary>
        /// Log assignment impact for performance tracking and analysis.
        /// </summary>
        private static void LogAssignmentImpact(QCAlgorithm algorithm, OrderEvent assignmentEvent, Symbol underlyingSymbol)
        {
            try
            {
                var holding = algorithm.Portfolio[underlyingSymbol];
                var assignmentValue = Math.Abs(holding.HoldingsValue);
                var portfolioImpact = assignmentValue / algorithm.Portfolio.TotalPortfolioValue * 100;
                
                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>
        /// 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;
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>
    /// 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();

        // 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>
        /// Process and output accumulated daily logs with smart summarization
        /// </summary>
        public static void ProcessDailyLogs(QCAlgorithm algorithm)
        {   
            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} ===");
                
                algorithm.Log("---------------------------------");
                algorithm.Log($"Daily Log Summary - {currentDay:yyyy-MM-dd}");
                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 to prevent memory leaks
                CleanOldThrottleEntries(currentDay);
                
                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);
            StoreSmartMessage("INFO", formattedMessage);
        }

        public void LogDebug(string message, params object[] args)
        {
            var formattedMessage = FormatMessage(message, args);
            StoreSmartMessage("DEBUG", formattedMessage);
        }

        public void LogWarning(string message, params object[] args)
        {
            var formattedMessage = FormatMessage(message, args);
            StoreSmartMessage("WARN", formattedMessage);
        }

        public void LogError(string message, params object[] args)
        {
            var formattedMessage = FormatMessage(message, args);
            StoreSmartMessage("ERROR", formattedMessage);
        }

        public void LogError(Exception exception, string message, params object[] args)
        {
            var formattedMessage = FormatMessage(message, args);
            var fullMessage = $"{formattedMessage} - Exception: {exception.Message}";
            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;

            // 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>
        /// 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;
                }
            }

            // 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)
        {
            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)
        {
            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.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;

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 class SimpleBaseStrategy : IStrategy, IAlgorithmContext
    {
        protected 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)
        protected SimpleTradeTracker TradeTracker { get; private set; } = new SimpleTradeTracker();

        /// <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);
                
                // 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();

        // 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);
        
        // 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 = "") => Algorithm.LimitOrder(symbol, quantity, limitPrice, 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)");
            }
        }
        protected void SetStartDate(DateTime date) => Algorithm.SetStartDate(date);
        protected void SetEndDate(DateTime date) => Algorithm.SetEndDate(date);
        protected void SetCash(decimal cash) => Algorithm.SetCash(cash);
        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
        }

        /// <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;
        }

        /// <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;
            }
        }

        /// <summary>
        /// Helper method for debug logging using context pattern
        /// </summary>
        protected void Debug(string message) => ((dynamic)Logger).Debug(message);
        
        /// <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 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;
            }
        }

        // ============================================================================
        // TRADE TRACKING HELPERS (Simple trade data collection like Python Position.py)
        // ============================================================================

        /// <summary>
        /// Track a new working order (order submitted but not filled)
        /// </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;
                
                TradeTracker.AddWorkingTrade(orderId, symbol, strategyName, ticket.Tag);
                Debug($"Tracking working order: {orderId} for {symbol}");
            }
        }
        
        /// <summary>
        /// Mark an order as filled (moved from working to open)
        /// </summary>
        public void TrackOrderFilled(OrderEvent orderEvent)
        {
            if (orderEvent?.OrderId != null)
            {
                var orderId = orderEvent.OrderId.ToString();
                TradeTracker.MarkTradeAsOpen(orderId, orderEvent.FillPrice, (int)orderEvent.FillQuantity);
                Debug($"Order filled: {orderId} at {orderEvent.FillPrice}");
            }
        }
        
        /// <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}");
        }
        
        /// <summary>
        /// Cancel a working order
        /// </summary>
        public void TrackOrderCancelled(string orderId)
        {
            TradeTracker.CancelWorkingTrade(orderId);
            Debug($"Order cancelled: {orderId}");
        }

        /// <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}");
        }

        // ============================================================================
        // 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)}");
            }
            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.
        }
    }
}
using System;
using System.Collections.Generic;
using System.Linq;

namespace CoreAlgo.Architecture.Core.Implementations
{
    /// <summary>
    /// Simple trade tracking system that maintains lists of trades in different states
    /// Similar to the Python setupbasestructure.py arrays for position management
    /// </summary>
    public class SimpleTradeTracker
    {
        /// <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
        /// </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);
            }
        }
        
        /// <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();
        }
    }
}
#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;

namespace CoreAlgo.Architecture.Core.Implementations
{
    /// <summary>
    /// Simple trade record for tracking individual trades through their lifecycle
    /// </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
        public decimal PnL { get; set; }
        public string OrderTag { get; set; } = string.Empty;
        
        /// <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;
        }
        
        /// <summary>
        /// Marks trade as open with fill details
        /// </summary>
        public void MarkAsOpen(decimal fillPrice, int fillQuantity)
        {
            Status = "Open";
            OpenPrice = fillPrice;
            Quantity = fillQuantity;
        }
        
        /// <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;
        }
        
        /// <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;
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 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.ComponentModel;
using System.Linq;
using CoreAlgo.Architecture.Core.Attributes;

namespace CoreAlgo.Architecture.Core.Models
{
    /// <summary>
    /// Configuration for Cash Secured Put options strategy
    /// QC-First approach - minimal wrapper for maximum flexibility
    /// </summary>
    public class CashSecuredPutConfig : StrategyConfig
    {
        [StrategyParameter("PutStrikeOffset", 0.02)]
        [Description("Offset below current price for put strike (e.g., 0.02 = 2% OTM)")]
        public decimal PutStrikeOffset { get; set; } = 0.02m; // 2% OTM for more opportunities

        [StrategyParameter("MinDaysToExpiration", 1)]
        [Description("Minimum days to expiration for put options")]
        public int MinDaysToExpiration { get; set; } = 1; // More aggressive - allow shorter expiry

        [StrategyParameter("MaxDaysToExpiration", 60)]
        [Description("Maximum days to expiration for put options")]
        public int MaxDaysToExpiration { get; set; } = 60; // Wider range for more options

        [StrategyParameter("MinimumPremium", 0.25)]
        [Description("Minimum premium to collect per contract")]
        public decimal MinimumPremium { get; set; } = 0.25m; // $25 per contract minimum - much more aggressive

        [StrategyParameter("CashPerContract", 0)]
        [Description("Cash required per contract (0 = auto-calculate from strike)")]
        public decimal CashPerContract { get; set; } = 0m; // Auto-calculate: strike * 100

        [StrategyParameter("MaxActivePositions", 2)]
        [Description("Maximum number of cash secured put positions")]
        public int MaxActivePositions { get; set; } = 2; // Conservative position limit

        [StrategyParameter("UseMarginReduction", false)]
        [Description("Use margin reduction strategies if available")]
        public bool UseMarginReduction { get; set; } = false; // Cash secured only by default

        [StrategyParameter("AcceptAssignment", true)]
        [Description("Accept assignment and buy shares if ITM at expiration")]
        public bool AcceptAssignment { get; set; } = true; // Willing to own the stock

        /// <summary>
        /// Key parameters for Cash Secured Put strategy optimization
        /// Focuses on strike selection, premium requirements, and timing
        /// </summary>
        public override string[] OptimizationParameters => base.OptimizationParameters.Concat(new[]
        {
            "PutStrikeOffset",      // Strike positioning - how far below current price
            "MinDaysToExpiration",  // Timing - minimum time to expiration
            "MaxDaysToExpiration",  // Timing - maximum time to expiration
            "MinimumPremium",       // Entry filter - minimum premium to collect
            "MaxActivePositions",   // Risk management - position count limit
            "AcceptAssignment"      // Strategy behavior - willing to own shares
        }).ToArray();

        public override string ToString()
        {
            return $"CashSecuredPut[{UnderlyingSymbol}] Strike:{PutStrikeOffset:P1} DTE:{MinDaysToExpiration}-{MaxDaysToExpiration} MinPrem:${MinimumPremium}";
        }

        public override string[] Validate()
        {
            var errors = new System.Collections.Generic.List<string>();

            if (PutStrikeOffset <= 0 || PutStrikeOffset > 0.20m)
                errors.Add("PutStrikeOffset must be between 0% and 20%");

            if (MinDaysToExpiration < 1 || MinDaysToExpiration > MaxDaysToExpiration)
                errors.Add("Invalid DTE range");

            if (MinimumPremium < 0)
                errors.Add("MinimumPremium cannot be negative");

            if (CashPerContract < 0)
                errors.Add("CashPerContract cannot be negative");

            if (MaxActivePositions < 1)
                errors.Add("MaxActivePositions must be at least 1");

            return errors.ToArray();
        }
    }
}
using System;
using System.ComponentModel;
using System.Linq;
using CoreAlgo.Architecture.Core.Attributes;

namespace CoreAlgo.Architecture.Core.Models
{
    /// <summary>
    /// Configuration for Covered Call options strategy
    /// QC-First approach - minimal wrapper for maximum flexibility
    /// </summary>
    public class CoveredCallConfig : StrategyConfig
    {
        [StrategyParameter("CallStrikeOffset", 0.02)]
        [Description("Offset above current stock price for call strike (e.g., 0.02 = 2% OTM)")]
        public decimal CallStrikeOffset { get; set; } = 0.02m; // 2% OTM for reasonable premium

        [StrategyParameter("MinDaysToExpiration", 7)]
        [Description("Minimum days to expiration for call options")]
        public int MinDaysToExpiration { get; set; } = 7; // Weekly options minimum

        [StrategyParameter("MaxDaysToExpiration", 45)]
        [Description("Maximum days to expiration for call options")]
        public int MaxDaysToExpiration { get; set; } = 45; // Monthly options maximum

        [StrategyParameter("MinimumPremium", 0.50)]
        [Description("Minimum premium to collect per call contract")]
        public decimal MinimumPremium { get; set; } = 0.50m; // $50 per contract minimum

        [StrategyParameter("SharesPerContract", 100)]
        [Description("Number of shares per covered call (typically 100)")]
        public int SharesPerContract { get; set; } = 100; // Standard option contract size

        [StrategyParameter("MaxActivePositions", 3)]
        [Description("Maximum number of covered call positions")]
        public int MaxActivePositions { get; set; } = 3; // Multiple positions allowed

        [StrategyParameter("BuySharesIfNeeded", true)]
        [Description("Automatically buy shares if not owned")]
        public bool BuySharesIfNeeded { get; set; } = true; // Auto-buy underlying

        /// <summary>
        /// Key parameters for Covered Call strategy optimization
        /// Focuses on call strike selection, premium requirements, and timing
        /// </summary>
        public override string[] OptimizationParameters => base.OptimizationParameters.Concat(new[]
        {
            "CallStrikeOffset",     // Strike positioning - how far above current price
            "MinDaysToExpiration",  // Timing - minimum time to expiration
            "MaxDaysToExpiration",  // Timing - maximum time to expiration
            "MinimumPremium",       // Entry filter - minimum premium to collect
            "MaxActivePositions",   // Risk management - position count limit
            "BuySharesIfNeeded"     // Strategy behavior - auto-buy underlying
        }).ToArray();

        public override string ToString()
        {
            return $"CoveredCall[{UnderlyingSymbol}] Strike:{CallStrikeOffset:P1} DTE:{MinDaysToExpiration}-{MaxDaysToExpiration} MinPrem:${MinimumPremium}";
        }

        public override string[] Validate()
        {
            var errors = new System.Collections.Generic.List<string>();

            if (CallStrikeOffset <= 0 || CallStrikeOffset > 0.20m)
                errors.Add("CallStrikeOffset must be between 0% and 20%");

            if (MinDaysToExpiration < 1 || MinDaysToExpiration > MaxDaysToExpiration)
                errors.Add("Invalid DTE range");

            if (MinimumPremium < 0)
                errors.Add("MinimumPremium cannot be negative");

            if (SharesPerContract != 100)
                errors.Add("SharesPerContract must be 100 for standard options");

            return errors.ToArray();
        }
    }
}
using System;
using System.Linq;
using CoreAlgo.Architecture.Core.Attributes;

namespace CoreAlgo.Architecture.Core.Models
{
    /// <summary>
    /// Configuration for ES Futures Opening Range Breakout strategy.
    /// Trades ES (E-mini S&P 500) futures based on 30-minute opening range breakouts
    /// with multi-contract position management and partial profit targets.
    /// </summary>
    public class ESFuturesORBConfig : FuturesConfig
    {
        /// <summary>
        /// Opening range period in minutes (30 min for 10:00-10:30 ET range)
        /// </summary>
        [StrategyParameter("Range period (minutes)", 30)]
        public int RangePeriodMinutes { get; set; } = 30;

        /// <summary>
        /// Breakout threshold in points above/below range (0.25 points for ES)
        /// </summary>
        [StrategyParameter("Breakout threshold (points)", 0.25)]
        public decimal BreakoutThreshold { get; set; } = 0.25m;

        /// <summary>
        /// Stop loss in points (10 points for ES)
        /// </summary>
        [StrategyParameter("Stop loss (points)", 10.0)]
        public decimal StopLossPoints { get; set; } = 15.0m;

        /// <summary>
        /// First profit target in points (10 points for ES)
        /// </summary>
        [StrategyParameter("First profit target (points)", 10.0)]
        public decimal FirstProfitTargetPoints { get; set; } = 15.0m;

        /// <summary>
        /// Second profit target in points (16 points for ES)
        /// </summary>
        [StrategyParameter("Second profit target (points)", 16.0)]
        public decimal SecondProfitTargetPoints { get; set; } = 20.0m;

        /// <summary>
        /// Total number of contracts to trade (2 contracts per the specification)
        /// </summary>
        [StrategyParameter("Total contracts", 2)]
        public int TotalContracts { get; set; } = 2;

        /// <summary>
        /// Number of contracts to exit at first profit target
        /// </summary>
        [StrategyParameter("Contracts at first target", 1)]
        public int ContractsAtFirstTarget { get; set; } = 1;


        /// <summary>
        /// Whether to allow both long and short trades on the same day
        /// </summary>
        [StrategyParameter("Allow both directions", false)]
        public bool AllowBothDirections { get; set; } = true;

        /// <summary>
        /// Maximum number of trades allowed per day (prevents multiple entries)
        /// </summary>
        [StrategyParameter("Max trades per day", 1)]
        public int MaxTradesPerDay { get; set; } = 5;

        /// <summary>
        /// Maximum daily loss limit in dollars
        /// </summary>
        [StrategyParameter("Daily loss limit ($)", 5000.0)]
        public decimal DailyLossLimit { get; set; } = 5000.0m;

        /// <summary>
        /// Minimum range width in points to consider valid for trading.
        /// Set to -1 to disable the minimum-width filter.
        /// </summary>
        [StrategyParameter("Minimum range width (points)", -1.0)]
        public decimal MinimumRangeWidthPoints { get; set; } = -1;

        /// <summary>
        /// Minutes before TradingEndTime to force end-of-day flatten. Set to -1 to disable.
        /// Example: TradingEndTime=16:15 and FlattenMinutesBeforeClose=15 => flatten from 16:00.
        /// </summary>
        [StrategyParameter("EOD flatten minutes before close", -1)]
        public int FlattenMinutesBeforeClose { get; set; } = -1;

        /// <summary>
        /// Override the default futures symbol to ES
        /// </summary>
        public ESFuturesORBConfig()
        {
            FutureSymbol = "ES";
            MaxContractsPerTrade = 2; // Override default to match strategy requirements
            DataResolution = "Minute"; // 30-minute bars require minute data
            MaxPositions = 5; // Prevent multiple positions - use framework capability
            
            // Set ES-specific trading hours (10:00 AM to 4:15 PM ET)
            TradingStartTime = new TimeSpan(10, 0, 0);   // 10:00 AM ET (was 9:00 AM CST)
            TradingEndTime = new TimeSpan(16, 15, 0);    // 4:15 PM ET (was 3:15 PM CST)

            StartDate = new DateTime(2020, 1, 1);
            EndDate = new DateTime(2025, 7, 31);

            UseEntryTimeWindow = false;
            EntryWindowStart = new TimeSpan(10, 30, 0);
            EntryWindowEnd = new TimeSpan(11, 30, 0);
        }

        /// <summary>
        /// Optimization parameters for ES Futures ORB strategy.
        /// Focuses on breakout thresholds, profit targets, and risk management.
        /// </summary>
        public override string[] OptimizationParameters => base.OptimizationParameters.Concat(new[]
        {
            "RangePeriodMinutes",           // Opening range definition
            "BreakoutThreshold",            // Entry signal sensitivity
            "StopLossPoints",               // Risk management
            "FirstProfitTargetPoints",      // Partial profit taking
            "SecondProfitTargetPoints",     // Final profit target
            "TotalContracts",               // Position sizing
            "ContractsAtFirstTarget",       // Partial exit strategy
            "AllowBothDirections",          // Trading flexibility
            "MaxTradesPerDay",              // Daily trade limit
            "DailyLossLimit",               // Risk management
            "MinimumRangeWidthPoints"       // Range validity filter
        }).ToArray();
    }
}
using System;
using System.Linq;
using CoreAlgo.Architecture.Core.Attributes;

namespace CoreAlgo.Architecture.Core.Models
{
    /// <summary>
    /// Configuration for Kullamägi Episodic Pivot strategy.
    /// Targets stocks with earnings catalysts that gap up 10%+ in pre-market,
    /// filtered by low recent momentum (30-day ROC) to find "neglected" stocks.
    /// Uses 20-day SMA trailing stop for exits.
    /// </summary>
    public class EpisodicPivotConfig : StrategyConfig
    {
        /// <summary>
        /// Minimum pre-market gap percentage required for entry consideration
        /// </summary>
        [StrategyParameter("Minimum gap percentage (%)", 10.0)]
        public decimal MinGapPercent { get; set; } = 10.0m;

        /// <summary>
        /// Minimum quarterly EPS growth percentage to qualify
        /// </summary>
        [StrategyParameter("Minimum EPS growth (%)", 20.0)]
        public decimal MinEPSGrowth { get; set; } = 20.0m;

        /// <summary>
        /// Maximum 30-day Rate of Change to filter "neglected" stocks
        /// </summary>
        [StrategyParameter("Max 30-day ROC (%)", 15.0)]
        public decimal MaxROC30Days { get; set; } = 15.0m;

        /// <summary>
        /// SMA period for trailing stop exit
        /// </summary>
        [StrategyParameter("SMA trailing stop period", 20)]
        public int SMAStopPeriod { get; set; } = 20;

        /// <summary>
        /// Minimum market capitalization to avoid micro-cap stocks
        /// </summary>
        [StrategyParameter("Minimum market cap ($)", 100000000.0)]
        public decimal MinMarketCap { get; set; } = 100_000_000m;

        /// <summary>
        /// Minimum daily dollar volume for liquidity filter
        /// </summary>
        [StrategyParameter("Minimum dollar volume ($)", 1000000.0)]
        public decimal MinDollarVolume { get; set; } = 1_000_000m;

        /// <summary>
        /// Days before/after earnings date to consider for catalyst
        /// </summary>
        [StrategyParameter("Earnings window days", 5)]
        public int EarningsWindowDays { get; set; } = 5;

        /// <summary>
        /// Maximum number of trades allowed per day
        /// </summary>
        [StrategyParameter("Max daily trades", 5)]
        public int MaxDailyTrades { get; set; } = 5;

        /// <summary>
        /// Universe size limit for performance optimization
        /// </summary>
        [StrategyParameter("Universe size limit", 50)]
        public int UniverseSizeLimit { get; set; } = 50;

        /// <summary>
        /// Use extended market hours for pre-market gap detection
        /// </summary>
        [StrategyParameter("Use extended hours", true)]
        public bool UseExtendedHours { get; set; } = true;

        // Note: upcoming-earnings universe is always used; no config toggles required

        /// <summary>
        /// Override the default configuration for Episodic Pivot strategy
        /// </summary>
        public EpisodicPivotConfig()
        {
            // Essential for earnings-based universe selection
            UseUniverseSelection = true;
            
            // Set up for pre-market gap trading
            TradingStartTime = new TimeSpan(4, 0, 0);   // 4:00 AM ET for pre-market
            TradingEndTime = new TimeSpan(16, 0, 0);    // 4:00 PM ET market close
            
            // Entry window limited to pre-market hours
            UseEntryTimeWindow = true;
            EntryWindowStart = new TimeSpan(4, 0, 0);   // 4:00 AM ET
            EntryWindowEnd = new TimeSpan(10, 0, 0);    // 10:00 AM ET (30 min after market open)
            
            // Position sizing for gap trading (smaller positions, more diversified)
            MaxPositions = 10;                          // Allow multiple gap plays
            AllocationPerPosition = 0.05m;              // 5% per position (conservative)
            
            // Risk management - 0 disables fixed targets (uses SMA trailing stop instead)
            ProfitTarget = 0m;                          // Disabled - using 20-day SMA trailing stop
            StopLoss = 0m;                              // Disabled - using 20-day SMA trailing stop
            MaxDaysInTrade = 90;                        // 3 months max holding period
            
            // Portfolio management
            
            // Extended market hours for gap detection
            
            // Date range for backtesting (includes COVID volatility and recovery)
            StartDate = new DateTime(2020, 1, 1);
            EndDate = new DateTime(2025, 8, 31);
            
            // Data requirements
        }

        /// <summary>
        /// Optimization parameters for Episodic Pivot strategy.
        /// Focuses on gap thresholds, momentum filters, and risk management.
        /// </summary>
        public override string[] OptimizationParameters => base.OptimizationParameters.Concat(new[]
        {
            "MinGapPercent",                            // Gap sensitivity
            "MinEPSGrowth",                            // Earnings catalyst strength
            "MaxROC30Days",                            // "Neglected" stock filter
            "SMAStopPeriod",                           // Trailing stop sensitivity
            "MinMarketCap",                            // Stock size filter
            "MinDollarVolume",                         // Liquidity filter
            "EarningsWindowDays",                      // Catalyst timing
            "MaxDailyTrades",                          // Risk control
            "AllocationPerPosition",                   // Position sizing
            "MaxPositions"                             // Diversification
        }).ToArray();

        /// <summary>
        /// Validate episodic pivot configuration parameters
        /// </summary>
        /// <returns>Array of validation error messages</returns>
        public override string[] Validate()
        {
            var errors = base.Validate().ToList();

            // Gap percentage validation
            if (MinGapPercent < 1.0m || MinGapPercent > 50.0m)
                errors.Add("MinGapPercent must be between 1% and 50%");

            // EPS growth validation
            if (MinEPSGrowth < 0m || MinEPSGrowth > 200.0m)
                errors.Add("MinEPSGrowth must be between 0% and 200%");

            // ROC validation
            if (MaxROC30Days < 0m || MaxROC30Days > 100.0m)
                errors.Add("MaxROC30Days must be between 0% and 100%");

            // SMA period validation
            if (SMAStopPeriod < 5 || SMAStopPeriod > 100)
                errors.Add("SMAStopPeriod must be between 5 and 100 days");

            // Market cap validation
            if (MinMarketCap < 1_000_000m)
                errors.Add("MinMarketCap must be at least $1M");

            // Volume validation
            if (MinDollarVolume < 100_000m)
                errors.Add("MinDollarVolume must be at least $100K");

            // Earnings window validation
            if (EarningsWindowDays < 1 || EarningsWindowDays > 30)
                errors.Add("EarningsWindowDays must be between 1 and 30 days");

            // Daily trades validation
            if (MaxDailyTrades < 1 || MaxDailyTrades > 20)
                errors.Add("MaxDailyTrades must be between 1 and 20");

            // Universe size validation
            if (UniverseSizeLimit < 10 || UniverseSizeLimit > 200)
                errors.Add("UniverseSizeLimit must be between 10 and 200");

            // Extended hours requirement
            if (!UseExtendedHours)
                errors.Add("UseExtendedHours must be true for pre-market gap detection");

            // Universe selection requirement
            if (!UseUniverseSelection)
                errors.Add("UseUniverseSelection must be true for earnings filtering");

            // Entry time window validation
            if (!UseEntryTimeWindow)
                errors.Add("UseEntryTimeWindow must be true for pre-market entries");

            // Additional validations for new parameters
            // (none)

            // Allow zero profit target and stop loss for SMA trailing stop strategy
            var profitTargetErrors = errors.Where(e => e.Contains("ProfitTarget")).ToArray();
            var stopLossErrors = errors.Where(e => e.Contains("StopLoss")).ToArray();
            foreach(var error in profitTargetErrors.Concat(stopLossErrors))
            {
                errors.Remove(error);
            }
            return errors.ToArray();
        }

        /// <summary>
        /// Get configuration summary for logging
        /// </summary>
        /// <returns>Configuration summary string</returns>
        public string GetConfigurationSummary()
        {
            return $"Episodic Pivot Strategy Configuration:\n" +
                   $"  Gap Threshold: {MinGapPercent}%\n" +
                   $"  EPS Growth Min: {MinEPSGrowth}%\n" +
                   $"  ROC30 Max: {MaxROC30Days}%\n" +
                   $"  SMA Stop: {SMAStopPeriod} days\n" +
                   $"  Market Cap Min: ${MinMarketCap:N0}\n" +
                   $"  Volume Min: ${MinDollarVolume:N0}\n" +
                   $"  Earnings Window: ±{EarningsWindowDays} days\n" +
                   $"  Max Daily Trades: {MaxDailyTrades}\n" +
                   $"  Position Size: {AllocationPerPosition:P1}\n" +
                   $"  Max Positions: {MaxPositions}\n" +
                   $"  Entry Window: {EntryWindowStart:hh\\:mm} - {EntryWindowEnd:hh\\:mm}\n" +
                   $"  Universe Size: {UniverseSizeLimit}";
        }
    }
}
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.Linq;
using CoreAlgo.Architecture.Core.Attributes;

namespace CoreAlgo.Architecture.Core.Models
{
    /// <summary>
    /// Configuration for futures options iron condor strategy.
    /// Trades iron condors on futures options, collecting premium from range-bound markets.
    /// Demonstrates futures options trading with QC's native AddFutureOption functionality.
    /// </summary>
    public class FuturesIronCondorConfig : FuturesConfig
    {
        /// <summary>
        /// Strike width between the short and long options (in strike price points)
        /// For example, if ES is at 4500, a width of 25 means 25 points difference
        /// </summary>
        [StrategyParameter("Strike width", 25)]
        public decimal StrikeWidth { get; set; } = 25m;

        /// <summary>
        /// Delta target for short options (absolute value, e.g., 0.15 for 15 delta)
        /// Lower delta = further OTM = lower premium but higher win rate
        /// </summary>
        [StrategyParameter("Short option delta target", 0.15)]
        public decimal ShortOptionDelta { get; set; } = 0.15m;

        /// <summary>
        /// Days to expiration range - minimum days
        /// </summary>
        [StrategyParameter("Minimum days to expiration", 14)]
        public int MinDaysToExpiration { get; set; } = 14;

        /// <summary>
        /// Days to expiration range - maximum days
        /// </summary>
        [StrategyParameter("Maximum days to expiration", 45)]
        public int MaxDaysToExpiration { get; set; } = 45;

        /// <summary>
        /// Profit target as percentage of premium collected (e.g., 0.5 for 50%)
        /// Close the position when 50% of max profit is achieved
        /// </summary>
        [StrategyParameter("Profit target percentage", 0.5)]
        public decimal ProfitTargetPercent { get; set; } = 0.5m;

        /// <summary>
        /// Stop loss as percentage of premium collected (e.g., 2.0 for 200%)
        /// Close position if loss exceeds 200% of premium received
        /// </summary>
        [StrategyParameter("Stop loss percentage", 2.0)]
        public decimal StopLossPercent { get; set; } = 2.0m;

        /// <summary>
        /// Days to expiration when to close position regardless of P&L
        /// </summary>
        [StrategyParameter("Days to expiry close", 5)]
        public int DaysToExpiryClose { get; set; } = 5;

        /// <summary>
        /// Maximum number of iron condors to have open at once
        /// </summary>
        [StrategyParameter("Max open positions", 3)]
        public int MaxOpenPositions { get; set; } = 3;

        /// <summary>
        /// Minimum credit received to enter trade (as percentage of strike width)
        /// E.g., 0.3 means minimum 30% of strike width as credit
        /// </summary>
        [StrategyParameter("Minimum credit percentage", 0.3)]
        public decimal MinimumCreditPercent { get; set; } = 0.3m;

        /// <summary>
        /// How often to scan for new trades (in hours)
        /// </summary>
        [StrategyParameter("Trade scan frequency (hours)", 24)]
        public int TradeScanFrequencyHours { get; set; } = 24;

        /// <summary>
        /// Whether to use weekly or monthly options
        /// Weekly options provide more trading opportunities but require more monitoring
        /// </summary>
        [StrategyParameter("Use weekly options", false)]
        public bool UseWeeklyOptions { get; set; } = false;

        /// <summary>
        /// Volatility filter - only trade if IV is above this percentile
        /// Set to 0 to disable volatility filter
        /// </summary>
        [StrategyParameter("Minimum IV percentile", 0)]
        public decimal MinimumIVPercentile { get; set; } = 0m;

        /// <summary>
        /// Optimization parameters for futures iron condor strategy.
        /// Focuses on futures options specific parameters and risk management
        /// </summary>
        public override string[] OptimizationParameters => base.OptimizationParameters.Concat(new[]
        {
            "StrikeWidth",            // Strike positioning - wing width
            "ShortOptionDelta",       // Delta targeting for short options
            "MinDaysToExpiration",    // Timing - minimum DTE
            "MaxDaysToExpiration",    // Timing - maximum DTE
            "ProfitTargetPercent",    // Exit target as percentage of premium
            "StopLossPercent",        // Risk management threshold
            "DaysToExpiryClose",      // Time-based exit rule
            "MaxOpenPositions",       // Position limit management
            "MinimumCreditPercent",   // Entry filter - minimum credit
            "TradeScanFrequencyHours" // Trade frequency control
        }).ToArray();
    }
}
using System.Linq;
using CoreAlgo.Architecture.Core.Attributes;

namespace CoreAlgo.Architecture.Core.Models
{
    /// <summary>
    /// Configuration for futures momentum trading strategy.
    /// Uses moving average crossover to trade futures contracts with trend following approach.
    /// Ideal for testing futures functionality with a simple, proven strategy.
    /// </summary>
    public class FuturesMomentumConfig : FuturesConfig
    {
        /// <summary>
        /// Fast moving average period (shorter-term trend)
        /// </summary>
        [StrategyParameter("Fast MA period", 10)]
        public int FastMAPeriod { get; set; } = 10;

        /// <summary>
        /// Slow moving average period (longer-term trend)
        /// </summary>
        [StrategyParameter("Slow MA period", 30)]
        public int SlowMAPeriod { get; set; } = 30;

        /// <summary>
        /// Stop loss percentage (as decimal, e.g., 0.02 for 2%)
        /// </summary>
        [StrategyParameter("Stop loss percentage", 0.02)]
        public decimal StopLossPercent { get; set; } = 0.02m;

        /// <summary>
        /// Take profit percentage (as decimal, e.g., 0.04 for 4%)
        /// </summary>
        [StrategyParameter("Take profit percentage", 0.04)]
        public decimal TakeProfitPercent { get; set; } = 0.04m;

        /// <summary>
        /// Minimum time between trades in minutes to avoid overtrading
        /// </summary>
        [StrategyParameter("Minimum trade interval (minutes)", 60)]
        public int MinimumTradeIntervalMinutes { get; set; } = 60;

        /// <summary>
        /// Whether to trade both long and short directions
        /// </summary>
        [StrategyParameter("Allow short trades", true)]
        public bool AllowShortTrades { get; set; } = true;

        /// <summary>
        /// Minimum volume filter - only trade if average volume > this value
        /// Set to 0 to disable volume filter
        /// </summary>
        [StrategyParameter("Minimum average volume", 0)]
        public int MinimumAverageVolume { get; set; } = 0;

        /// <summary>
        /// Optimization parameters for futures momentum strategy.
        /// Focuses on moving average parameters and risk management
        /// </summary>
        public override string[] OptimizationParameters => base.OptimizationParameters.Concat(new[]
        {
            "FastMAPeriod",                // Core signal - fast moving average
            "SlowMAPeriod",                // Core signal - slow moving average
            "StopLossPercent",             // Risk management - stop loss
            "TakeProfitPercent",           // Risk management - take profit
            "MinimumTradeIntervalMinutes", // Trade frequency control
            "AllowShortTrades",            // Direction flexibility
            "PositionSizeMultiplier",      // Position sizing control
            "MaxContractsPerTrade",        // Contract limit management
            "MinimumAverageVolume"         // Volume filter for liquidity
        }).ToArray();
    }
}
using System;
using System.ComponentModel;
using System.Linq;
using CoreAlgo.Architecture.Core.Attributes;

namespace CoreAlgo.Architecture.Core.Models
{
    /// <summary>
    /// Configuration for Iron Condor options strategy
    /// </summary>
    public class IronCondorConfig : StrategyConfig
    {
        [StrategyParameter("PutShortStrikeOffset", 0.03)]
        [Description("Offset below current price for short put strike (e.g., 0.03 = 3% below)")]
        public decimal PutShortStrikeOffset { get; set; } = 0.03m; // More aggressive - closer to current price

        [StrategyParameter("CallShortStrikeOffset", 0.03)]
        [Description("Offset above current price for short call strike (e.g., 0.03 = 3% above)")]
        public decimal CallShortStrikeOffset { get; set; } = 0.03m; // More aggressive - closer to current price

        [StrategyParameter("PutStrikeWidth", 5)]
        [Description("Distance between short and long put strikes")]
        public decimal PutStrikeWidth { get; set; } = 5m; // Smaller spreads for easier fills

        [StrategyParameter("CallStrikeWidth", 5)]
        [Description("Distance between short and long call strikes")]
        public decimal CallStrikeWidth { get; set; } = 5m; // Smaller spreads for easier fills

        [StrategyParameter("MinDaysToExpiration", 3)]
        [Description("Minimum days to expiration for options")]
        public int MinDaysToExpiration { get; set; } = 3; // Much shorter for more active trading

        [StrategyParameter("MaxDaysToExpiration", 21)]
        [Description("Maximum days to expiration for options")]
        public int MaxDaysToExpiration { get; set; } = 21; // Shorter expiration for more options available

        [StrategyParameter("MinimumPremium", 0.25)]
        [Description("Minimum premium to collect per contract")]
        public decimal MinimumPremium { get; set; } = 0.25m; // Much lower minimum for easier entry

        [StrategyParameter("MaxActivePositions", 5)]
        [Description("Maximum number of Iron Condor positions")]
        public int MaxActivePositions { get; set; } = 5; // Allow more positions

        /// <summary>
        /// Initialize inherited parameters for centralized hooks without hiding base members
        /// </summary>
        public IronCondorConfig()
        {
            // Enable overlap prevention and combo structure hints via base properties
            EnableOverlapPrevention = true;
            MaxPositionsPerCombo = 1;
            ComboOrderLegCount = 4;

            // Shorter test period (optional defaults)
            StartDate = new DateTime(2023, 1, 1);
            EndDate = new DateTime(2023, 1, 31);
        }

        /// <summary>
        /// Key parameters for Iron Condor strategy optimization
        /// Focuses on strike positioning, spread width, and timing parameters
        /// </summary>
        public override string[] OptimizationParameters => base.OptimizationParameters.Concat(new[]
        {
            "PutShortStrikeOffset",   // Strike positioning - put spread placement
            "CallShortStrikeOffset",  // Strike positioning - call spread placement
            "PutStrikeWidth",         // Spread structure - put spread width
            "CallStrikeWidth",        // Spread structure - call spread width
            "MinDaysToExpiration",    // Timing - minimum time to expiration
            "MaxDaysToExpiration",    // Timing - maximum time to expiration
            "MinimumPremium",         // Entry filter - minimum premium collection
            "MaxActivePositions"      // Risk management - position count limit
        }).ToArray();

        public override string ToString()
        {
            return $"IronCondor[{UnderlyingSymbol}] Put:{PutShortStrikeOffset:P1}±{PutStrikeWidth} Call:{CallShortStrikeOffset:P1}±{CallStrikeWidth} DTE:{MinDaysToExpiration}-{MaxDaysToExpiration}";
        }
    }
}
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 QuantConnect.Algorithm;
using CoreAlgo.Architecture.Core.Attributes;
using CoreAlgo.Architecture.QC.Helpers;
using CoreAlgo.Architecture.Core.Interfaces;

namespace CoreAlgo.Architecture.Core.Models
{
    /// <summary>
    /// Multi-Asset Iron Condor strategy configuration with ComboOrder support.
    /// Extends MultiAssetConfig with Iron Condor specific parameters and multi-asset adaptations.
    /// </summary>
    public class MultiAssetIronCondorConfig : MultiAssetConfig
    {
        /// <summary>
        /// Use ComboOrders for atomic execution of Iron Condor legs
        /// When true, uses QuantConnect's ComboMarketOrder for simultaneous execution
        /// When false, places individual orders for each leg
        /// </summary>
        [StrategyParameter("UseComboOrders", true)]
        public bool UseComboOrders { get; set; } = true;

        /// <summary>
        /// Minimum wing width for Iron Condor spreads (in strikes)
        /// Prevents creating spreads that are too tight
        /// </summary>
        [StrategyParameter("MinWingWidth", 5)]
        public int MinWingWidth { get; set; } = 5;

        /// <summary>
        /// Maximum wing width for Iron Condor spreads (in strikes)
        /// Prevents creating spreads that are too wide
        /// </summary>
        [StrategyParameter("MaxWingWidth", 20)]
        public int MaxWingWidth { get; set; } = 20;

        /// <summary>
        /// Target delta for short put leg (closest to money)
        /// </summary>
        [StrategyParameter("ShortPutDelta", -0.15)]
        public decimal ShortPutDelta { get; set; } = -0.15m;

        /// <summary>
        /// Target delta for short call leg (closest to money)
        /// </summary>
        [StrategyParameter("ShortCallDelta", 0.15)]
        public decimal ShortCallDelta { get; set; } = 0.15m;

        /// <summary>
        /// Enable iron condor specific asset adaptations
        /// Adjusts strike selection and wing widths based on asset volatility
        /// </summary>
        [StrategyParameter("EnableIronCondorAdaptation", true)]
        public bool EnableIronCondorAdaptation { get; set; } = true;

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

            // Apply Iron Condor specific adaptations
            if (EnableIronCondorAdaptation && Symbols != null && Symbols.Length > 0)
            {
                ApplyIronCondorAdaptations(context);
            }

            // Validate Iron Condor specific constraints
            ValidateIronCondorConstraints(context);
        }

        /// <summary>
        /// Apply Iron Condor specific parameter adaptations based on the selected assets
        /// </summary>
        private void ApplyIronCondorAdaptations(IAlgorithmContext context)
        {
            ((dynamic)context.Logger).Info("MultiAssetIronCondorConfig: Applying Iron Condor asset-specific adaptations");

            // Adapt wing widths based on asset characteristics
            foreach (var symbol in Symbols)
            {
                var profile = MultiAssetHelper.GetAssetProfile(symbol);
                if (profile != null)
                {
                    // Adjust wing widths based on asset type and volatility
                    var adaptedWingWidth = GetAdaptedWingWidth(symbol, profile);
                    
                    ((dynamic)context.Logger).Info($"MultiAssetIronCondorConfig: {symbol} adapted wing width: {adaptedWingWidth}");
                }
            }

            // Ensure ComboOrders are used for better multi-asset coordination
            if (UseComboOrders)
            {
                ((dynamic)context.Logger).Info("MultiAssetIronCondorConfig: ComboOrders enabled for atomic Iron Condor execution");
            }
            else
            {
                ((dynamic)context.Logger).Warning("MultiAssetIronCondorConfig: ComboOrders disabled - using individual leg orders");
            }
        }

        /// <summary>
        /// Get adapted wing width for a specific asset
        /// </summary>
        private int GetAdaptedWingWidth(string symbol, dynamic profile)
        {
            // Base wing width from configuration
            var baseWidth = (MinWingWidth + MaxWingWidth) / 2;

            // Adjust based on asset characteristics
            if (AssetManager.IsIndex(symbol))
            {
                // Index options typically have tighter spreads, can use narrower wings
                return Math.Max(MinWingWidth, baseWidth - 2);
            }
            else if (AssetManager.IsEquity(symbol))
            {
                // ETFs generally have good liquidity, use standard wing width
                return baseWidth;
            }
            else
            {
                // Individual stocks may need wider wings for liquidity
                return Math.Min(MaxWingWidth, baseWidth + 3);
            }
        }

        /// <summary>
        /// Validate Iron Condor specific constraints
        /// </summary>
        private void ValidateIronCondorConstraints(IAlgorithmContext context)
        {
            // Check wing width constraints
            if (MinWingWidth <= 0)
            {
                ((dynamic)context.Logger).Error("MultiAssetIronCondorConfig: MinWingWidth must be positive");
            }

            if (MaxWingWidth <= MinWingWidth)
            {
                ((dynamic)context.Logger).Error("MultiAssetIronCondorConfig: MaxWingWidth must be greater than MinWingWidth");
            }

            // Check delta targets
            if (ShortPutDelta >= 0)
            {
                ((dynamic)context.Logger).Error("MultiAssetIronCondorConfig: ShortPutDelta must be negative");
            }

            if (ShortCallDelta <= 0)
            {
                ((dynamic)context.Logger).Error("MultiAssetIronCondorConfig: ShortCallDelta must be positive");
            }

            // Log Iron Condor specific configuration
            var summaryMessages = new[]
            {
                $"MultiAssetIronCondorConfig: Wing width range: {MinWingWidth}-{MaxWingWidth} strikes",
                $"MultiAssetIronCondorConfig: Delta targets - Put: {ShortPutDelta:F2}, Call: {ShortCallDelta:F2}",
                $"MultiAssetIronCondorConfig: ComboOrders: {(UseComboOrders ? "Enabled" : "Disabled")}",
                $"MultiAssetIronCondorConfig: Iron Condor adaptation: {(EnableIronCondorAdaptation ? "Enabled" : "Disabled")}"
            };

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

        /// <summary>
        /// Get wing width for a specific asset with adaptations
        /// </summary>
        public int GetWingWidthForAsset(string symbol)
        {
            var profile = MultiAssetHelper.GetAssetProfile(symbol);
            return profile != null ? GetAdaptedWingWidth(symbol, profile) : (MinWingWidth + MaxWingWidth) / 2;
        }

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

            // Iron Condor specific validations
            if (MinWingWidth <= 0)
            {
                errors.Add("MinWingWidth must be positive");
            }

            if (MaxWingWidth <= MinWingWidth)
            {
                errors.Add("MaxWingWidth must be greater than MinWingWidth");
            }

            if (ShortPutDelta >= 0)
            {
                errors.Add("ShortPutDelta must be negative for Iron Condor");
            }

            if (ShortCallDelta <= 0)
            {
                errors.Add("ShortCallDelta must be positive for Iron Condor");
            }

            if (Math.Abs(ShortPutDelta) > 0.5m || Math.Abs(ShortCallDelta) > 0.5m)
            {
                errors.Add("Delta targets should typically be between -0.5 and 0.5 for Iron Condor");
            }

            // Multi-asset Iron Condor specific checks
            if (Symbols != null && Symbols.Length > 0)
            {
                foreach (var symbol in Symbols)
                {
                    if (!MultiAssetHelper.HasLiquidOptions(symbol))
                    {
                        errors.Add($"Iron Condor requires liquid options for {symbol}");
                    }
                }
            }

            return errors.ToArray();
        }

        /// <summary>
        /// Optimization parameters for multi-asset iron condor strategy.
        /// Focuses on iron condor specific parameters and multi-asset adaptations
        /// </summary>
        public override string[] OptimizationParameters => base.OptimizationParameters.Concat(new[]
        {
            "MinWingWidth",           // Iron condor structure - minimum wing width
            "MaxWingWidth",           // Iron condor structure - maximum wing width
            "ShortPutDelta",          // Strike positioning - put side
            "ShortCallDelta",         // Strike positioning - call side
            "UseComboOrders",         // Execution method for atomic trades
            "EnableIronCondorAdaptation", // Asset-specific adaptations
            "MaxAssets",              // Multi-asset diversification limit
            "AllocationPerPosition",  // Position sizing per condor
            "ProfitTarget",           // Exit target for iron condors
            "StopLoss"                // Risk management threshold
        }).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.ComponentModel;
using System.Linq;
using QuantConnect;
using CoreAlgo.Architecture.Core.Attributes;

namespace CoreAlgo.Architecture.Core.Models
{
	/// <summary>
	/// Configuration for Opening Range Breakout (ORB) strategy
	/// Monitors opening range and opens 0DTE credit spreads at 12:00 PM on full trading days
	/// </summary>
	public class ORBConfig : StrategyConfig
	{
		/// <summary>
		/// Default constructor - sets SPX as default underlying symbol for ORB
		/// </summary>
		public ORBConfig()
		{
			UnderlyingSymbol = "SPX";
			Symbols = new[] { "SPX" };
			StartDate = new DateTime(2025, 7, 29);
			EndDate = new DateTime(2025, 8, 13);

			// Enable central entry window by default for ORB
			UseEntryTimeWindow = true;
			EntryWindowStart = new TimeSpan(12, 0, 0);
			EntryWindowEnd = new TimeSpan(15, 30, 0);
		}
		[StrategyParameter("RangePeriodMinutes", 60)]
		[Description("Duration in minutes to establish the opening range (default: 60)")]
		public int RangePeriodMinutes { get; set; } = 60;

		[StrategyParameter("MinRangeWidthPercent", 0.2)]
		[Description("Minimum required range width as percentage of opening price (default: 0.2%)")]
		public decimal MinRangeWidthPercent { get; set; } = 0.2m;

		[StrategyParameter("SpreadWidth", 15)]
		[Description("Width between short and long strikes in dollars (default: $15)")]
		public decimal SpreadWidth { get; set; } = 15m;

		[StrategyParameter("EntryTime", "12:00")]
		[Description("Exact time to evaluate and enter trades in HH:mm (default: 12:00)")]
		public string EntryTime { get; set; } = "12:00";

		[StrategyParameter("MaxPositionsPerDay", 1)]
		[Description("Maximum number of positions to open per day (default: 1)")]
		public int MaxPositionsPerDay { get; set; } = 1;

		[StrategyParameter("ContractSize", 10)]
		[Description("Number of contracts per trade (default: 10)")]
		public int ContractSize { get; set; } = 10;

		[StrategyParameter("MinStrikeOffset", 0.01)]
		[Description("Minimum offset from price/boundary for short strikes (default: $0.01)")]
		public decimal MinStrikeOffset { get; set; } = 0.01m;

		[StrategyParameter("UseSmaTwentyFilter", true)]
		[Description("Use SMA(20) filter - only enter call spreads when price below SMA (default: true)")]
		public bool UseSmaTwentyFilter { get; set; } = true;

		[StrategyParameter("CapitalAllocation", 100000)]
		[Description("Maximum capital allocation for ORB strategy (default: $100,000)")]
		public decimal CapitalAllocation { get; set; } = 100000m;

		[StrategyParameter("SkipFomcDays", true)]
		[Description("Skip trading on FOMC meeting days (default: true)")]
		public bool SkipFomcDays { get; set; } = true;

		[StrategyParameter("DataResolution", "Minute")]
		[Description("Data resolution for underlying and options (Minute for intraday, Daily for EOD) (default: Minute)")]
		public string DataResolution { get; set; } = "Minute";

		[StrategyParameter("WarmupPeriodDays", 30)]
		[Description("Number of days to warm up indicators before trading starts (default: 30)")]
		public int WarmupPeriodDays { get; set; } = 30;

		[StrategyParameter("EntryEvaluationMode", "ExactTime")]
		[Description("Entry evaluation mode: ExactTime (default), Window, or AllDay")]
		public string EntryEvaluationMode { get; set; } = "ExactTime";

		[StrategyParameter("ShortStrikeBasis", "Underlying")]
		[Description("Basis for selecting short strikes: Underlying (default) or ORB")]
		public string ShortStrikeBasis { get; set; } = "Underlying";

		[StrategyParameter("StrictZeroDteOnly", true)]
		[Description("Require 0DTE options only; if none available, skip trading (default: true)")]
		public bool StrictZeroDteOnly { get; set; } = true;

		[StrategyParameter("FirstBreakout", true)]
		[Description("When both ORB high and low are breached before entry time, prefer the first breakout side (default: true)")]
		public bool FirstBreakout { get; set; } = true;

		/// <summary>
		/// FOMC meeting dates for 2024-2025
		/// </summary>
		public static readonly HashSet<DateTime> FomcDates2024_2025 = new HashSet<DateTime>
		{
			// 2024 FOMC dates
			new DateTime(2024, 1, 31),
			new DateTime(2024, 3, 20),
			new DateTime(2024, 5, 1),
			new DateTime(2024, 6, 12),
			new DateTime(2024, 7, 31),
			new DateTime(2024, 9, 18),
			new DateTime(2024, 11, 7),
			new DateTime(2024, 12, 18),
			
			// 2025 FOMC dates (projected)
			new DateTime(2025, 1, 29),
			new DateTime(2025, 3, 19),
			new DateTime(2025, 4, 30),
			new DateTime(2025, 6, 11),
			new DateTime(2025, 7, 30),
			new DateTime(2025, 9, 17),
			new DateTime(2025, 11, 6),
			new DateTime(2025, 12, 17)
		};

		/// <summary>
		/// Get the Resolution enum value from the DataResolution string parameter
		/// </summary>
		public Resolution GetDataResolution()
		{
			return DataResolution?.ToUpperInvariant() switch
			{
				"MINUTE" => Resolution.Minute,
				"SECOND" => Resolution.Second,
				"HOUR" => Resolution.Hour,
				"DAILY" => Resolution.Daily,
				_ => Resolution.Minute // Default to Minute for intraday strategies
			};
		}

		/// <summary>
		/// Key parameters for ORB strategy optimization
		/// Focuses on the most impactful parameters for backtesting performance
		/// </summary>
		public override string[] OptimizationParameters => base.OptimizationParameters.Concat(new[]
		{
			"RangePeriodMinutes",    // Core ORB logic - range establishment period
			"MinRangeWidthPercent",  // Entry filter - minimum range requirement
			"SpreadWidth",           // Position structure - spread width
			"EntryTime",             // Timing - when to evaluate/enter trades
			"ContractSize",          // Position sizing - number of contracts
			"UseSmaTwentyFilter",    // Market filter - SMA trend filter
			"CapitalAllocation",     // Risk management - max capital
			"StrictZeroDteOnly",     // Options selection - 0DTE requirement
			"FirstBreakout"          // Entry logic - breakout preference
		}).ToArray();

		public override string ToString()
		{
			return $"ORB[{UnderlyingSymbol}] Range:{RangePeriodMinutes}min MinWidth:{MinRangeWidthPercent:P1} SpreadWidth:${SpreadWidth} Entry:{EntryTime} Contracts:{ContractSize} SMA:{UseSmaTwentyFilter} Capital:${CapitalAllocation:N0} Resolution:{DataResolution} Warmup:{WarmupPeriodDays}d";
		}
	}
}
using System;
using System.Collections.Generic;
using System.Linq;
using QuantConnect;
using CoreAlgo.Architecture.Core.Attributes;

namespace CoreAlgo.Architecture.Core.Models
{
    /// <summary>
    /// Configuration for the Restricted Trading strategy demonstrating Entry/Exit Restriction Framework.
    /// Shows how to configure trading restrictions for controlled position management.
    /// </summary>
    public class RestrictedTradingConfig : StrategyConfig
    {
        // === Underlying Configuration ===
        [StrategyParameter("Trade Options", "false")]
        public bool TradeOptions { get; set; } = false;

        // === Entry/Exit/Position Defaults (set base properties via constructor) ===
        public RestrictedTradingConfig()
        {
            // Entry restrictions
            TradingStartTime = new TimeSpan(9, 30, 0);
            TradingEndTime = new TimeSpan(15, 30, 0);
            MaxPositions = 3;
            AllocationPerPosition = 0.30m; // 30% per position
            MinImpliedVolatility = 0.15m;   // 15% minimum IV

            // Exit restrictions
            ProfitTarget = 0.10m; // 10% profit target
            StopLoss = -0.05m;    // 5% stop loss
            MaxDaysInTrade = 30;  // Exit after 30 days

            // Options-specific
            EntryDeltaMin = 0.25m;
            EntryDeltaMax = 0.40m;
            ExitDelta = 0.15m;
        }

        /// <summary>
        /// Validate configuration parameters.
        /// </summary>
        public override string[] Validate()
        {
            var errors = new List<string>();

            if (string.IsNullOrWhiteSpace(UnderlyingSymbol))
                errors.Add("UnderlyingSymbol cannot be empty");

            if (TradingStartTime >= TradingEndTime)
                errors.Add("TradingStartTime must be before TradingEndTime");

            if (MaxPositions <= 0 || MaxPositions > 10)
                errors.Add("MaxPositions must be between 1 and 10");

            if (AllocationPerPosition <= 0 || AllocationPerPosition > 1)
                errors.Add("AllocationPerPosition must be between 0 and 1");

            if (ProfitTarget <= 0)
                errors.Add("ProfitTarget must be positive");

            if (StopLoss >= 0)
                errors.Add("StopLoss must be negative");

            if (MaxDaysInTrade <= 0)
                errors.Add("MaxDaysInTrade must be positive");

            if (TradeOptions)
            {
                if (EntryDeltaMin <= 0 || EntryDeltaMin >= 1)
                    errors.Add("EntryDeltaMin must be between 0 and 1");

                if (EntryDeltaMax <= EntryDeltaMin || EntryDeltaMax >= 1)
                    errors.Add("EntryDeltaMax must be greater than EntryDeltaMin and less than 1");

                if (ExitDelta <= 0 || ExitDelta >= EntryDeltaMin)
                    errors.Add("ExitDelta must be positive and less than EntryDeltaMin");

                if (MinImpliedVolatility < 0 || MinImpliedVolatility > 2)
                    errors.Add("MinImpliedVolatility must be between 0 and 2");
            }

            return errors.ToArray();
        }

        /// <summary>
        /// Get a friendly description of the configuration.
        /// </summary>
        public override string ToString()
        {
            return $"RestrictedTradingConfig: {UnderlyingSymbol}, " +
                   $"Trading Hours: {TradingStartTime:hh\\:mm}-{TradingEndTime:hh\\:mm}, " +
                   $"Max Positions: {MaxPositions}, " +
                   $"Allocation: {AllocationPerPosition:P0}, " +
                   $"Targets: +{ProfitTarget:P0}/{StopLoss:P0}, " +
                   $"Max Days: {MaxDaysInTrade}" +
                   (TradeOptions ? $", Options Delta: {EntryDeltaMin:F2}-{EntryDeltaMax:F2}" : "");
        }

        /// <summary>
        /// Optimization parameters for restricted trading strategy.
        /// Focuses on restriction framework parameters and position management
        /// </summary>
        public override string[] OptimizationParameters => base.OptimizationParameters.Concat(new[]
        {
            "TradingStartTime",       // Entry restriction - trading window start
            "TradingEndTime",         // Entry restriction - trading window end
            "MaxPositions",           // Entry restriction - position limit
            "AllocationPerPosition",  // Position sizing control
            "ProfitTarget",           // Exit restriction - profit target
            "StopLoss",               // Exit restriction - stop loss
            "MaxDaysInTrade",         // Exit restriction - time-based exit
            "EntryDeltaMin",          // Options - delta targeting minimum
            "EntryDeltaMax",          // Options - delta targeting maximum
            "MinImpliedVolatility"    // Entry filter - volatility threshold
        }).ToArray();
    }
}
using System;
using System.Collections.Generic;
using System.Linq;
using System.Reflection;
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>
        /// 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>
        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();
            
            foreach (var property in properties)
            {
                var attribute = property.GetCustomAttribute<StrategyParameterAttribute>();
                if (attribute != null)
                {
                    try
                    {
                        var parameterName = attribute.Name;
                        var defaultValue = property.GetValue(this)?.ToString() ?? attribute.DefaultValue?.ToString() ?? "";
                        
                        var parameterValue = context.Algorithm.GetParameter(parameterName, defaultValue);
                        
                        // Use context logger for debugging
                        ((dynamic)context.Logger).Debug($"Raw parameter {parameterName} = '{parameterValue}' (Type: {parameterValue?.GetType()?.Name ?? "null"})");
                        
                        // Convert the parameter value to the property type
                        var convertedValue = ConvertParameterValue(parameterValue, property.PropertyType);
                        property.SetValue(this, convertedValue);
                        
                        // Enhanced logging for arrays
                        if (convertedValue is string[] arrayValue)
                        {
                            ((dynamic)context.Logger).Debug($"Loaded parameter {parameterName} = [{string.Join(", ", arrayValue)}] (Array Length: {arrayValue.Length})");
                        }
                        else
                        {
                            ((dynamic)context.Logger).Debug($"Loaded parameter {parameterName} = {convertedValue} (Converted Type: {convertedValue?.GetType()?.Name ?? "null"})");
                        }
                    }
                    catch (Exception ex)
                    {
                        ((dynamic)context.Logger).Error($"Failed to load parameter {attribute.Name}: {ex.Message}");
                    }
                }
            }
        }

        /// <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.ComponentModel;
using System.Linq;
using CoreAlgo.Architecture.Core.Attributes;

namespace CoreAlgo.Architecture.Core.Models
{
    /// <summary>
    /// Configuration for Universe Selection strategy that demonstrates enhanced StrategyConfig capabilities.
    /// Shows how to use multi-asset support, fundamental filters, and all new parameters.
    /// </summary>
    public class UniverseSelectionConfig : StrategyConfig
    {
        // ============================================================================
        // UNIVERSE SELECTION PARAMETERS
        // ============================================================================

        [StrategyParameter("MinMarketCap", 1000000000)]
        [Description("Minimum market capitalization for universe selection (default: $1B)")]
        public decimal MinMarketCap { get; set; } = 1_000_000_000m;

        [StrategyParameter("MinDollarVolume", 10000000)]
        [Description("Minimum daily dollar volume for universe selection (default: $10M)")]
        public decimal MinDollarVolume { get; set; } = 10_000_000m;

        [StrategyParameter("MinPERatio", 5)]
        [Description("Minimum P/E ratio for fundamental filter")]
        public decimal MinPERatio { get; set; } = 5m;

        [StrategyParameter("MaxPERatio", 30)]
        [Description("Maximum P/E ratio for fundamental filter")]
        public decimal MaxPERatio { get; set; } = 30m;

        [StrategyParameter("TradeOptions", false)]
        [Description("Enable options trading for selected securities")]
        public bool TradeOptions { get; set; } = false;

        [StrategyParameter("MinutesBetwenTrades", 60)]
        [Description("Minimum minutes between trades for the same symbol")]
        public int MinutesBetwenTrades { get; set; } = 60;

        // ============================================================================
        // CONSTRUCTOR WITH STRATEGY-SPECIFIC DEFAULTS
        // ============================================================================

        public UniverseSelectionConfig()
        {
            // Override base class defaults for this strategy
            UseUniverseSelection = true; // This strategy is all about universe selection
            
            // Multi-asset capability - manual symbols for when universe selection is disabled
            Symbols = new[] { "SPY", "QQQ", "TLT", "GLD", "SPX" }; // Mix of equity and index
            
            // Position sizing optimized for universe selection
            AllocationPerPosition = 0.15m; // 15% per position
            MaxPositions = 6; // Allow more positions for diversification
            
            // More conservative risk management for dynamic universe
            ProfitTarget = 0.20m; // 20% profit target
            StopLoss = -0.10m; // 10% stop loss
            MaxDaysInTrade = 21; // 3 weeks max
            
            // Options parameters for when TradeOptions is enabled
            EntryDeltaMin = 0.20m;
            EntryDeltaMax = 0.40m;
            ExitDelta = 0.05m;
            MinImpliedVolatility = 0.20m; // Higher IV threshold for universe selection
            
            // Trading hours - avoid first and last 30 minutes
            TradingStartTime = new TimeSpan(10, 0, 0); // 10:00 AM
            TradingEndTime = new TimeSpan(15, 0, 0);   // 3:00 PM
        }

        public override string[] Validate()
        {
            var errors = new List<string>();
            
            // Add base validation
            var baseErrors = base.Validate();
            errors.AddRange(baseErrors);
            
            // Strategy-specific validation
            if (MinMarketCap <= 0)
                errors.Add("MinMarketCap must be greater than 0");
                
            if (MinDollarVolume <= 0)
                errors.Add("MinDollarVolume must be greater than 0");
                
            if (MinPERatio <= 0 || MinPERatio >= MaxPERatio)
                errors.Add("MinPERatio must be positive and less than MaxPERatio");
                
            if (MaxPERatio <= MinPERatio)
                errors.Add("MaxPERatio must be greater than MinPERatio");
                
            if (MinutesBetwenTrades < 0)
                errors.Add("MinutesBetwenTrades cannot be negative");
                
            // Validate allocation doesn't exceed 100%
            var totalAllocation = AllocationPerPosition * MaxPositions;
            if (totalAllocation > 1.0m)
                errors.Add($"Total allocation ({totalAllocation:P0}) exceeds 100% (MaxPositions * AllocationPerPosition)");
            
            return errors.ToArray();
        }

        public override string ToString()
        {
            return $"UniverseSelection[{(UseUniverseSelection ? "Dynamic" : "Manual")}] " +
                   $"Cap>=${MinMarketCap/1000000:N0}M Vol>=${MinDollarVolume/1000000:N0}M " +
                   $"PE:{MinPERatio}-{MaxPERatio} Pos:{MaxPositions}x{AllocationPerPosition:P0} " +
                   $"Options:{TradeOptions}";
        }

        /// <summary>
        /// Optimization parameters for universe selection strategy.
        /// Focuses on universe filtering, fundamental analysis, and dynamic selection
        /// </summary>
        public override string[] OptimizationParameters => base.OptimizationParameters.Concat(new[]
        {
            "MinMarketCap",           // Universe filter - market cap threshold
            "MinDollarVolume",        // Universe filter - liquidity requirement
            "MinPERatio",             // Fundamental filter - minimum P/E
            "MaxPERatio",             // Fundamental filter - maximum P/E
            "MaxPositions",           // Portfolio diversification limit
            "AllocationPerPosition",  // Position sizing per selected stock
            "ProfitTarget",           // Exit target for dynamic universe
            "StopLoss",               // Risk management threshold
            "MaxDaysInTrade",         // Holding period control
            "MinutesBetwenTrades"     // Trade frequency control
        }).ToArray();
    }
}
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.Algorithm;
using QuantConnect.Data;
using QuantConnect.Data.Market;
using CoreAlgo.Architecture.Core.Interfaces;

namespace CoreAlgo.Architecture.Core.Services
{
    /// <summary>
    /// TODO: INTEGRATE LATER - Infrastructure component for asset correlation analysis.
    /// 
    /// CURRENT STATUS: Built but not integrated with templates yet.
    /// REASON: Keeping main system simple while building foundation for future enhancements.
    /// 
    /// FUTURE INTEGRATION PLAN:
    /// 1. Add to CoreAlgoRiskManager when main system is stable
    /// 2. Integrate with MultiAssetIronCondorTemplate for correlation-based position sizing
    /// 3. Add correlation-based asset selection to strategy templates
    /// 
    /// This component leverages QuantConnect's historical data APIs to calculate asset correlations
    /// and provide correlation-based risk management capabilities.
    /// </summary>
    public class CorrelationCalculator
    {
        private readonly IAlgorithmContext _context;
        private readonly Dictionary<string, List<decimal>> _priceHistory;
        private readonly Dictionary<(string, string), decimal> _correlationCache;
        private readonly int _defaultLookbackDays;

        public CorrelationCalculator(IAlgorithmContext context, int defaultLookbackDays = 30)
        {
            _context = context ?? throw new ArgumentNullException(nameof(context));
            _priceHistory = new Dictionary<string, List<decimal>>();
            _correlationCache = new Dictionary<(string, string), decimal>();
            _defaultLookbackDays = defaultLookbackDays;
        }

        /// <summary>
        /// TODO: FUTURE USE - Calculates correlation between two assets using historical price data.
        /// 
        /// Uses QuantConnect's historical data API to fetch price series and calculate Pearson correlation.
        /// Caches results to avoid repeated calculations for performance.
        /// </summary>
        /// <param name="symbol1">First asset symbol</param>
        /// <param name="symbol2">Second asset symbol</param>
        /// <param name="lookbackDays">Number of days for correlation calculation</param>
        /// <returns>Correlation coefficient (-1 to 1)</returns>
        public decimal CalculateCorrelation(string symbol1, string symbol2, int? lookbackDays = null)
        {
            try
            {
                var days = lookbackDays ?? _defaultLookbackDays;
                var cacheKey = (symbol1, symbol2);
                
                // Check cache first (TODO: Add cache expiration)
                if (_correlationCache.ContainsKey(cacheKey))
                {
                    return _correlationCache[cacheKey];
                }

                // Get historical price data using QC's API
                var prices1 = GetHistoricalPrices(symbol1, days);
                var prices2 = GetHistoricalPrices(symbol2, days);

                if (prices1.Count < 10 || prices2.Count < 10)
                {
                    ((dynamic)_context.Logger).Warning($"Insufficient price data for correlation: {symbol1} ({prices1.Count}), {symbol2} ({prices2.Count})");
                    return 0; // No correlation if insufficient data
                }

                // Calculate Pearson correlation coefficient
                var correlation = CalculatePearsonCorrelation(prices1, prices2);
                
                // Cache result
                _correlationCache[cacheKey] = correlation;
                _correlationCache[(symbol2, symbol1)] = correlation; // Symmetric
                
                ((dynamic)_context.Logger).Debug($"Correlation calculated: {symbol1} vs {symbol2} = {correlation:F3} ({days} days)");
                
                return correlation;
            }
            catch (Exception ex)
            {
                ((dynamic)_context.Logger).Error($"Error calculating correlation between {symbol1} and {symbol2}: {ex.Message}");
                return 0; // Default to no correlation on error
            }
        }

        /// <summary>
        /// TODO: FUTURE USE - Gets correlation matrix for a set of symbols.
        /// Useful for portfolio construction and risk analysis.
        /// </summary>
        public Dictionary<(string, string), decimal> GetCorrelationMatrix(string[] symbols, int? lookbackDays = null)
        {
            var matrix = new Dictionary<(string, string), decimal>();
            
            for (int i = 0; i < symbols.Length; i++)
            {
                for (int j = i + 1; j < symbols.Length; j++)
                {
                    var correlation = CalculateCorrelation(symbols[i], symbols[j], lookbackDays);
                    matrix[(symbols[i], symbols[j])] = correlation;
                }
            }

            return matrix;
        }

        /// <summary>
        /// TODO: FUTURE USE - Checks if a new asset would create high correlation with existing positions.
        /// Prevents adding highly correlated assets to reduce concentration risk.
        /// </summary>
        public bool ShouldPreventTrade(string newSymbol, string[] existingSymbols, decimal maxCorrelation = 0.7m)
        {
            foreach (var existingSymbol in existingSymbols)
            {
                var correlation = Math.Abs(CalculateCorrelation(newSymbol, existingSymbol));
                if (correlation > maxCorrelation)
                {
                    ((dynamic)_context.Logger).Warning($"High correlation detected: {newSymbol} vs {existingSymbol} = {correlation:F3} > {maxCorrelation:F3}");
                    return true; // Prevent trade
                }
            }
            
            return false; // Allow trade
        }

        /// <summary>
        /// TODO: FUTURE USE - Gets highly correlated assets for a given symbol.
        /// Useful for risk monitoring and position sizing adjustments.
        /// </summary>
        public List<CorrelatedAsset> GetHighlyCorrelatedAssets(string symbol, string[] candidateSymbols, decimal threshold = 0.6m)
        {
            var correlatedAssets = new List<CorrelatedAsset>();
            
            foreach (var candidate in candidateSymbols)
            {
                if (candidate == symbol) continue;
                
                var correlation = CalculateCorrelation(symbol, candidate);
                if (Math.Abs(correlation) > threshold)
                {
                    correlatedAssets.Add(new CorrelatedAsset
                    {
                        Symbol = candidate,
                        Correlation = correlation,
                        IsPositivelyCorrelated = correlation > 0
                    });
                }
            }

            return correlatedAssets.OrderByDescending(x => Math.Abs(x.Correlation)).ToList();
        }

        /// <summary>
        /// Gets historical daily closing prices using QuantConnect's History API.
        /// TODO: Consider using returns instead of prices for better correlation calculation.
        /// </summary>
        private List<decimal> GetHistoricalPrices(string symbol, int days)
        {
            try
            {
                // TODO: Implement actual QC History API call
                // var history = _context.Algorithm.History<TradeBar>(symbol, days, Resolution.Daily);
                // return history.Select(x => x.Close).ToList();
                
                // PLACEHOLDER: Return mock data for now
                // This will be replaced with actual QC History API call during integration
                var random = new Random(symbol.GetHashCode());
                var prices = new List<decimal>();
                var basePrice = 100m;
                
                for (int i = 0; i < days; i++)
                {
                    basePrice *= (decimal)(1 + (random.NextDouble() - 0.5) * 0.02); // ±1% daily moves
                    prices.Add(basePrice);
                }
                
                return prices;
            }
            catch (Exception ex)
            {
                ((dynamic)_context.Logger).Error($"Error fetching historical prices for {symbol}: {ex.Message}");
                return new List<decimal>();
            }
        }

        /// <summary>
        /// Calculates Pearson correlation coefficient between two price series.
        /// </summary>
        private decimal CalculatePearsonCorrelation(List<decimal> prices1, List<decimal> prices2)
        {
            var count = Math.Min(prices1.Count, prices2.Count);
            if (count < 2) return 0;

            // Calculate returns instead of using raw prices
            var returns1 = new List<decimal>();
            var returns2 = new List<decimal>();

            for (int i = 1; i < count; i++)
            {
                if (prices1[i - 1] != 0 && prices2[i - 1] != 0)
                {
                    returns1.Add((prices1[i] - prices1[i - 1]) / prices1[i - 1]);
                    returns2.Add((prices2[i] - prices2[i - 1]) / prices2[i - 1]);
                }
            }

            if (returns1.Count < 2) return 0;

            var mean1 = returns1.Average();
            var mean2 = returns2.Average();

            var numerator = returns1.Zip(returns2, (x, y) => (x - mean1) * (y - mean2)).Sum();
            var denominator1 = Math.Sqrt((double)returns1.Sum(x => (x - mean1) * (x - mean1)));
            var denominator2 = Math.Sqrt((double)returns2.Sum(x => (x - mean2) * (x - mean2)));

            if (denominator1 == 0 || denominator2 == 0) return 0;

            return (decimal)(numerator / (decimal)(denominator1 * denominator2));
        }
    }

    /// <summary>
    /// Represents an asset that is correlated with another asset.
    /// TODO: FUTURE USE - Used for correlation-based risk analysis.
    /// </summary>
    public class CorrelatedAsset
    {
        public string Symbol { get; set; }
        public decimal Correlation { get; set; }
        public bool IsPositivelyCorrelated { get; set; }
    }
}
using System;
using System.Collections.Generic;
using System.Linq;
using QuantConnect;
using QuantConnect.Securities;
using CoreAlgo.Architecture.Core.Interfaces;

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;
        }
    }

    #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;

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);
                
                // Place initial limit order
                var ticket = _algorithm.LimitOrder(symbol, quantity, initialPrice, 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, initialPrice);

                _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 comboTracker = new ComboOrderTracker(comboTickets, legs, comboQuote, 
                    comboDirection, _pricingEngine.Mode, initialNetPrice.Value);

                // Track using primary order ID
                _activeComboOrders[comboTracker.PrimaryOrderId] = comboTracker;

                ((dynamic)_context.Logger).Info($"SmartCombo: Placed {legs.Count}-leg combo limit order " +
                                              $"at net price ${initialNetPrice.Value: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)
                {
                    // Update order price
                    var updateFields = new UpdateOrderFields { LimitPrice = nextPrice.Value };
                    var response = tracker.OrderTicket.Update(updateFields);
                    
                    if (response.IsSuccess)
                    {
                        tracker.CurrentPrice = nextPrice.Value;
                        ((dynamic)_context.Logger).Debug($"SmartOrder: Updated order {tracker.OrderTicket.OrderId} " +
                                                       $"price to ${nextPrice.Value: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)
                    {
                        tracker.UpdateNetPrice(nextNetPrice.Value, 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;
        }
    }
}
using System;
using System.Linq;
using QuantConnect;
using QuantConnect.Data;
using QuantConnect.Orders;
using QuantConnect.Securities;
using QuantConnect.Securities.Option;
using CoreAlgo.Architecture.Core.Implementations;
using CoreAlgo.Architecture.Core.Models;

namespace CoreAlgo.Architecture.Core.Templates
{
    /// <summary>
    /// Cash Secured Put options strategy template
    /// QC-First approach - leverages QuantConnect's native capabilities
    /// </summary>
    public class CashSecuredPutTemplate : SimpleBaseStrategy
    {
        private CashSecuredPutConfig _config;
        private Symbol _underlying;
        private Symbol _optionSymbol;

        public override string Name => "Cash Secured Put";
        
        public override string Description => 
            "Income strategy that generates premium by selling put options backed by cash";

        public override void OnInitialize()
        {
            SmartLog("CashSecuredPutTemplate.OnInitialize() starting...");
            
            // Configure with CashSecuredPut-specific settings
            try
            {
                Configure<CashSecuredPutConfig>();
                _config = (CashSecuredPutConfig)Config;
                SmartLog("Configuration loaded successfully");
            }
            catch (Exception ex)
            {
                SmartError($"Failed to load configuration: {ex.Message}");
                throw;
            }
            
            // Configuration already loaded automatically by Configure<CashSecuredPutConfig>()
            
            // Setup underlying and put options using common helper
            (_underlying, _optionSymbol) = SetupOptionsForSymbol(_config.UnderlyingSymbol, 
                strikeRange: 5, _config.MinDaysToExpiration, _config.MaxDaysToExpiration, putsOnly: true);
            
            SmartLog($"Cash Secured Put initialized for {_config.UnderlyingSymbol}");
            SmartLog($"Configuration:");
            SmartLog($"   Put offset: {_config.PutStrikeOffset:P1} below current price");
            SmartLog($"   DTE range: {_config.MinDaysToExpiration}-{_config.MaxDaysToExpiration} days");
            SmartLog($"   Min premium: ${_config.MinimumPremium:F2}");
            SmartLog($"   Max positions: {_config.MaxActivePositions}");
            SmartLog($"   Accept assignment: {_config.AcceptAssignment}");
            SmartLog($"Ready for cash secured put trading!");
        }

        protected override void OnExecute(Slice slice)
        {
            Debug($"CashSecuredPut OnExecute called at {slice.Time}");
            
            // Check if we have option data
            if (!slice.OptionChains.TryGetValue(_optionSymbol, out var chain))
            {
                Debug("No option chain data available");
                return;
            }

            // Get current underlying price
            var underlyingPrice = Securities[_underlying].Price;
            if (underlyingPrice <= 0)
            {
                Debug("Invalid underlying price");
                return;
            }

            // Check our existing put positions
            var putPositions = Portfolio.Securities
                .Where(kvp => kvp.Value.Type == SecurityType.Option && 
                             kvp.Value.Symbol.Underlying == _underlying &&
                             kvp.Value.Symbol.ID.OptionRight == OptionRight.Put &&
                             kvp.Value.Holdings.Quantity < 0)  // Short puts
                .Count();

            if (putPositions >= _config.MaxActivePositions)
            {
                Debug($"Max positions reached: {putPositions}/{_config.MaxActivePositions}");
                return;
            }

            // Find suitable put options to sell
            var targetStrike = underlyingPrice * (1 - _config.PutStrikeOffset);
            
            var candidatePuts = chain
                .Where(contract => contract.Right == OptionRight.Put)
                .Where(contract => contract.Strike <= targetStrike)
                .Where(contract => contract.BidPrice >= _config.MinimumPremium)
                .OrderByDescending(contract => contract.Strike)  // Prefer higher strikes (less OTM)
                .ThenBy(contract => contract.Expiry)
                .Take(3); // Top 3 candidates

            var selectedPut = candidatePuts.FirstOrDefault();
            if (selectedPut == null)
            {
                Debug($"No suitable puts found with premium >= ${_config.MinimumPremium}");
                return;
            }

            // Calculate cash required
            var cashRequired = selectedPut.Strike * 100;  // 100 shares per contract
            var availableCash = Portfolio.Cash;

            if (availableCash < cashRequired)
            {
                Debug($"Insufficient cash: ${availableCash:F2} < ${cashRequired:F2} required");
                return;
            }

            // Sell the put option
            var quantity = -1; // Negative for selling
            var putOrder = MarketOrder(selectedPut.Symbol, quantity);
            
            SmartLog($"CASH SECURED PUT EXECUTED:");
            SmartLog($"   Underlying: {_underlying.Value} @ ${underlyingPrice:F2}");
            SmartLog($"   Sold Put: Strike ${selectedPut.Strike:F2}, Expiry {selectedPut.Expiry:yyyy-MM-dd}");
            SmartLog($"   Premium: ${selectedPut.BidPrice:F2} x 100 = ${selectedPut.BidPrice * 100:F2}");
            SmartLog($"   Cash Secured: ${cashRequired:F2}");
            SmartLog($"   DTE: {(selectedPut.Expiry - Time).TotalDays:F0} days");
            SmartLog($"   Break-even: ${selectedPut.Strike - selectedPut.BidPrice:F2}");
        }

        protected void LogDailySummary()
        {
            // Log daily summary - called from OnExecute when needed
            var underlyingPrice = Algorithm.Securities[_underlying].Price;
            var putPositions = Algorithm.Portfolio.Securities
                .Where(kvp => kvp.Value.Type == SecurityType.Option && 
                             kvp.Value.Symbol.Underlying == _underlying &&
                             kvp.Value.Symbol.ID.OptionRight == OptionRight.Put)
                .Select(kvp => kvp.Value);

            SmartLog($"=== Daily Summary for {_underlying.Value} ===");
            SmartLog($"Underlying Price: ${underlyingPrice:F2}");
            SmartLog($"Available Cash: ${Algorithm.Portfolio.Cash:N2}");
            
            foreach (var option in putPositions)
            {
                if (option.Holdings.Quantity < 0) // Short puts
                {
                    var strike = option.Symbol.ID.StrikePrice;
                    var expiry = option.Symbol.ID.Date;
                    var dte = (expiry - Algorithm.Time).TotalDays;
                    var moneyness = underlyingPrice > strike ? "OTM" : "ITM";
                    
                    SmartLog($"Short Put: {strike} {expiry:yyyy-MM-dd} x{Math.Abs(option.Holdings.Quantity)} ({moneyness}, {dte:F0} DTE)");
                }
            }
            
            SmartLog($"Portfolio Value: ${Algorithm.Portfolio.TotalPortfolioValue:N2}");
        }
    }
}
using System;
using System.Linq;
using QuantConnect;
using QuantConnect.Data;
using QuantConnect.Orders;
using QuantConnect.Securities;
using QuantConnect.Securities.Option;
using CoreAlgo.Architecture.Core.Implementations;
using CoreAlgo.Architecture.Core.Models;

namespace CoreAlgo.Architecture.Core.Templates
{
    /// <summary>
    /// Covered Call options strategy template
    /// QC-First approach - leverages QuantConnect's native capabilities
    /// </summary>
    public class CoveredCallTemplate : SimpleBaseStrategy
    {
        private CoveredCallConfig _config;
        private Symbol _underlying;
        private Symbol _optionSymbol;

        public override string Name => "Covered Call";
        
        public override string Description => 
            "Income strategy that generates premium by selling call options against owned stock";

        public override void OnInitialize()
        {
            SmartLog("CoveredCallTemplate.OnInitialize() starting...");
            
            // Configure with CoveredCall-specific settings
            try
            {
                Configure<CoveredCallConfig>();
                _config = (CoveredCallConfig)Config;
                SmartLog("Configuration loaded successfully");
            }
            catch (Exception ex)
            {
                SmartError($"Failed to load configuration: {ex.Message}");
                throw;
            }
            
            // Configuration already loaded automatically by Configure<CoveredCallConfig>()
            
            // Setup underlying and call options using common helper
            (_underlying, _optionSymbol) = SetupOptionsForSymbol(_config.UnderlyingSymbol, 
                strikeRange: 5, _config.MinDaysToExpiration, _config.MaxDaysToExpiration, callsOnly: true);
            
            SmartLog($"Covered Call initialized for {_config.UnderlyingSymbol}");
            SmartLog($"Configuration:");
            SmartLog($"   Call offset: {_config.CallStrikeOffset:P1} above current price");
            SmartLog($"   DTE range: {_config.MinDaysToExpiration}-{_config.MaxDaysToExpiration} days");
            SmartLog($"   Min premium: ${_config.MinimumPremium:F2}");
            SmartLog($"   Max positions: {_config.MaxActivePositions}");
            SmartLog($"   Auto-buy shares: {_config.BuySharesIfNeeded}");
            SmartLog($"Ready for covered call trading!");
        }

        protected override void OnExecute(Slice slice)
        {
            Debug($"CoveredCall OnExecute called at {slice.Time}");
            
            // Check if we have option data
            if (!slice.OptionChains.TryGetValue(_optionSymbol, out var chain))
            {
                Debug("No option chain data available");
                return;
            }

            // Get current underlying price
            var underlyingPrice = Securities[_underlying].Price;
            if (underlyingPrice <= 0)
            {
                Debug("Invalid underlying price");
                return;
            }

            // Check our stock position
            var stockHolding = Portfolio[_underlying].Quantity;
            var sharesNeeded = _config.SharesPerContract - stockHolding;
            
            // If we need shares and auto-buy is enabled
            if (sharesNeeded > 0 && _config.BuySharesIfNeeded)
            {
                SmartLog($"Need {sharesNeeded} shares for covered call position");
                var buyOrder = MarketOrder(_underlying, sharesNeeded);
                SmartLog($"Bought {sharesNeeded} shares of {_underlying.Value} at market");
                return; // Wait for next bar to sell calls
            }

            // Check if we have enough shares to sell a covered call
            if (stockHolding < _config.SharesPerContract)
            {
                Debug($"Insufficient shares: {stockHolding} < {_config.SharesPerContract}");
                return;
            }

            // Check if we already have call positions
            var callPositions = Portfolio.Securities
                .Where(kvp => kvp.Value.Type == SecurityType.Option && 
                             kvp.Value.Symbol.Underlying == _underlying &&
                             kvp.Value.Holdings.Quantity < 0)  // Short calls
                .Count();

            if (callPositions >= _config.MaxActivePositions)
            {
                Debug($"Max positions reached: {callPositions}/{_config.MaxActivePositions}");
                return;
            }

            // Find suitable call options to sell
            var targetStrike = underlyingPrice * (1 + _config.CallStrikeOffset);
            
            var candidateCalls = chain
                .Where(contract => contract.Right == OptionRight.Call)
                .Where(contract => contract.Strike >= targetStrike)
                .Where(contract => contract.BidPrice >= _config.MinimumPremium)
                .OrderBy(contract => Math.Abs(contract.Strike - targetStrike))
                .ThenBy(contract => contract.Expiry)
                .Take(3); // Top 3 candidates

            var selectedCall = candidateCalls.FirstOrDefault();
            if (selectedCall == null)
            {
                Debug($"No suitable calls found with premium >= ${_config.MinimumPremium}");
                return;
            }

            // Sell the call option
            var quantity = -1; // Negative for selling
            var callOrder = MarketOrder(selectedCall.Symbol, quantity);
            
            SmartLog($"COVERED CALL EXECUTED:");
            SmartLog($"   Underlying: {_underlying.Value} @ ${underlyingPrice:F2}");
            SmartLog($"   Sold Call: Strike ${selectedCall.Strike:F2}, Expiry {selectedCall.Expiry:yyyy-MM-dd}");
            SmartLog($"   Premium: ${selectedCall.BidPrice:F2} x 100 = ${selectedCall.BidPrice * 100:F2}");
            SmartLog($"   DTE: {(selectedCall.Expiry - Time).TotalDays:F0} days");
        }

        protected void LogDailySummary()
        {
            // Log daily summary - called from OnExecute when needed
            var stockPosition = Algorithm.Portfolio[_underlying];
            var optionPositions = Algorithm.Portfolio.Securities
                .Where(kvp => kvp.Value.Type == SecurityType.Option && 
                             kvp.Value.Symbol.Underlying == _underlying)
                .Select(kvp => kvp.Value);

            SmartLog($"=== Daily Summary for {_underlying.Value} ===");
            SmartLog($"Stock Position: {stockPosition.Quantity} shares @ ${stockPosition.AveragePrice:F2}");
            
            foreach (var option in optionPositions)
            {
                if (option.Holdings.Quantity < 0) // Short calls
                {
                    SmartLog($"Short Call: {option.Symbol.ID.StrikePrice} {option.Symbol.ID.Date:yyyy-MM-dd} x{Math.Abs(option.Holdings.Quantity)}");
                }
            }
            
            SmartLog($"Portfolio Value: ${Algorithm.Portfolio.TotalPortfolioValue:N2}");
        }
    }
}
using System;
using System.Collections.Generic;
using QuantConnect;
using QuantConnect.Data;
using QuantConnect.Securities;
using QuantConnect.Securities.Future;
using CoreAlgo.Architecture.Core.Implementations;
using CoreAlgo.Architecture.Core.Models;
using CoreAlgo.Architecture.QC.Helpers;

namespace CoreAlgo.Architecture.Core.Templates
{
    /// <summary>
    /// ES Futures Opening Range Breakout (ORB) strategy
    /// Trades ES (E-mini S&P 500) futures based on 30-minute opening range breakouts.
    /// 
    /// Strategy Rules:
    /// - Establish 30-minute range from 9:00-9:30 CST
    /// - Enter long when high is broken by 0.25 points
    /// - Enter short when low is broken by 0.25 points  
    /// - Use 2 contracts with 10-point stop loss
    /// - First profit target at 10 points (1 contract)
    /// - Second profit target at 16 points (1 contract)
    /// </summary>
    public class ESFuturesORBTemplate : SimpleBaseStrategy
    {
        private ESFuturesORBConfig _config;
        
        private Future _future;
        private Symbol _currentContract;
        
        // Opening range tracking
        private decimal _orbHigh = decimal.MinValue;
        private decimal _orbLow = decimal.MaxValue;
        private DateTime _rangeStartTime;
        private DateTime _rangeEndTime;
        private bool _rangeEstablished = false;
        private bool _validRange = false;
        
        // Position tracking
        private bool _positionOpenedToday = false;
        private DateTime _lastTradingDay = DateTime.MinValue;
        private decimal _entryPrice = 0;
        private int _currentDirection = 0; // 1 = long, -1 = short, 0 = no position
        private int _remainingContracts = 0;
        private bool _firstTargetHit = false;
        
        // Trade limit tracking
        private int _dailyTradeCount = 0;
        
        // Risk management
        private decimal _stopLossPrice = 0;
        private decimal _firstProfitPrice = 0;
        private decimal _secondProfitPrice = 0;
        private decimal _dailyPnL = 0;

        public override string Name => "ESFuturesORB";
        public override string Description => "ES Futures Opening Range Breakout with multi-contract partial profit targets";

        public override void OnInitialize()
        {
            SmartLog("ESFuturesORB: Initializing...");
            
            try
            {
                // Configure with ESFuturesORB-specific settings
                Configure<ESFuturesORBConfig>();
                _config = (ESFuturesORBConfig)Config;
                SmartLog("Configuration loaded successfully");
                
                // Add ES futures contract using AssetManager
                var addedSecurity = AssetManager.AddAsset(
                    this,
                    _config.FutureSymbol,
                    _config.GetDataResolution(),
                    _config.UseContinuousContract,
                    _config.ContractDepthOffset);

                _future = addedSecurity as Future;
                if (_future == null)
                {
                    throw new InvalidOperationException($"Failed to add future {_config.FutureSymbol} - returned security is not a Future type");
                }

                // Set futures contract filter for continuous contracts
                if (_config.UseContinuousContract)
                {
                    _future.SetFilter(0, 90); // Front month to 3 months out
                    SmartLog("ESFuturesORB: Set continuous contract filter (0-90 days)");
                }

                SmartLog($"ESFuturesORB: Successfully added {_config.FutureSymbol} futures");
                SmartLog($"ESFuturesORB: Contract details - Multiplier: {_future.SymbolProperties.ContractMultiplier}");
                SmartLog($"ESFuturesORB: Strategy configuration:");
                SmartLog($"  Range Period: {_config.RangePeriodMinutes} minutes");
                SmartLog($"  Breakout Threshold: {_config.BreakoutThreshold} points");
                SmartLog($"  Stop Loss: {_config.StopLossPoints} points");
                SmartLog($"  Profit Targets: {_config.FirstProfitTargetPoints} / {_config.SecondProfitTargetPoints} points");
                SmartLog($"  Total Contracts: {_config.TotalContracts}");
            }
            catch (Exception ex)
            {
                SmartError($"ESFuturesORB: Failed to initialize: {ex.Message}");
                throw;
            }
        }

        protected override void OnPreExecuteAlways(Slice slice)
        {
            // Ensure daily tracking and contract mapping are prepared before gating
            if (slice.Time.Date > _lastTradingDay)
            {
                ResetDailyTracking(slice);
                _lastTradingDay = slice.Time.Date;
            }
            UpdateCurrentContract();

            // Build opening range during establishment window before central gating
            if (_currentContract != null)
            {
                var security = Algorithm.Securities.ContainsKey(_currentContract) ? Algorithm.Securities[_currentContract] : null;
                var pricePre = security != null ? security.Price : 0m;
                var currentTime = slice.Time;
                if (pricePre > 0 && !_rangeEstablished && currentTime >= _rangeStartTime && currentTime <= _rangeEndTime)
                {
                    UpdateOpeningRange(pricePre, currentTime);
                }
            }
        }

        /// <summary>
        /// Force ES ORB exits to run even when new entries are blocked, and handle EOD flatten.
        /// </summary>
        protected override void CheckExitConditions(Slice slice)
        {
            UpdateCurrentContract();
            if (_currentContract == null)
                return;
            
            var holding = Algorithm.Portfolio[_currentContract];
            var price = Algorithm.Securities.ContainsKey(_currentContract) ? Algorithm.Securities[_currentContract].Price : 0m;
            if (price > 0 && holding.Invested)
            {
                // Ensure point-based stop/target exits always run
                CheckRiskManagementExits(price);
            }
            
            // EOD flatten window (configurable; -1 disables)
            if (_config.FlattenMinutesBeforeClose >= 0)
            {
                var flattenTime = _config.TradingEndTime - TimeSpan.FromMinutes(_config.FlattenMinutesBeforeClose);
                var nowTod = slice.Time.TimeOfDay;
                if (nowTod >= flattenTime && holding.Invested)
                {
                    SmartLog($"ESFuturesORB: EOD Flatten triggered at {slice.Time:HH:mm} (window starts {flattenTime:hh\\:mm})");
                    var qty = -(int)holding.Quantity;
                    if (qty != 0)
                    {
                        var ticket = MarketOrder(_currentContract, qty, tag: "EOD Flatten");
                        if (ticket != null)
                        {
                            _remainingContracts = 0;
                            _positionOpenedToday = false;
                            _currentDirection = 0;
                            _firstTargetHit = false;
                            ExitRestrictions.ClearPositionEntry(_currentContract);
                            SmartLog("ESFuturesORB: EOD flatten order submitted");
                        }
                    }
                }
            }
        }

        /// <summary>
        /// Strategy-specific validation for ES Futures ORB.
        /// Checks daily trade limits and daily loss limits before allowing new trades.
        /// Base class ShouldExecuteTrade() already handles trading hours, position limits, and margin checks.
        /// </summary>
        protected override bool OnShouldExecuteTrade(Slice slice, out string blockReason)
        {
            blockReason = "";
            
            // 1. Check daily trade limit
            if (_dailyTradeCount >= _config.MaxTradesPerDay)
            {
                blockReason = $"Daily trade limit reached ({_dailyTradeCount}/{_config.MaxTradesPerDay})";
                return false;
            }
            
            // 2. Check daily loss limit
            if (_dailyPnL <= -_config.DailyLossLimit)
            {
                blockReason = $"Daily loss limit reached: ${_dailyPnL:F2}";
                return false;
            }
            
            // 3. Allow execution during range establishment period so range can be collected
            var currentTime = slice.Time;
            var establishmentStart = currentTime.Date.Add(_config.TradingStartTime);
            var establishmentEnd = establishmentStart.AddMinutes(_config.RangePeriodMinutes);
            var isInEstablishmentWindow = currentTime >= establishmentStart && currentTime <= establishmentEnd;
            if (isInEstablishmentWindow)
            {
                return true;
            }
            
            // 4. After the window, block if opening range not established or invalid
            if (!_rangeEstablished || !_validRange)
            {
                blockReason = _rangeEstablished ? "Range too narrow to trade" : "Opening range not established yet";
                return false;
            }
            
            // 5. Ensure we have a valid contract
            if (_currentContract == null)
            {
                blockReason = "No current contract available";
                return false;
            }
            
            // All strategy-specific checks passed
            return true;
        }

        protected override void OnExecute(Slice slice)
        {
            // Reset daily tracking at market open
            if (slice.Time.Date > _lastTradingDay)
            {
                ResetDailyTracking(slice);
                _lastTradingDay = slice.Time.Date;
            }

            // Update current contract symbol
            UpdateCurrentContract();
            
            if (_currentContract == null)
                return;

            var currentPrice = Algorithm.Securities[_currentContract].Price;
            if (currentPrice <= 0) 
                return;

            var currentTime = slice.Time;

            // NOTE: Trading hours check is now handled centrally by SimpleBaseStrategy.ShouldExecuteTrade()

            // Check for risk management exits if we have a position
            if (_positionOpenedToday && _currentDirection != 0)
            {
                CheckRiskManagementExits(currentPrice);
            }

            // Check for entry signals if range is established and we don't have a position
            if (_rangeEstablished && _validRange && !_positionOpenedToday)
            {
                CheckForEntrySignals(currentPrice, slice);
            }
        }

        private void ResetDailyTracking(Slice slice)
        {
            SmartLog($"=== ESFuturesORB: Resetting daily tracking for {slice.Time.Date:yyyy-MM-dd} ===");
            
            // Always reset range tracking
            _orbHigh = decimal.MinValue;
            _orbLow = decimal.MaxValue;
            _rangeEstablished = false;
            _validRange = false;
            
            // Check if we have an actual position in Portfolio before resetting position tracking
            if (_currentContract != null)
            {
                var currentPosition = Algorithm.Portfolio[_currentContract];
                if (currentPosition != null && currentPosition.Quantity != 0)
                {
                    // Carry over position tracking if position exists from previous day
                    _positionOpenedToday = true;
                    _currentDirection = Math.Sign((int)currentPosition.Quantity);
                    _remainingContracts = Math.Abs((int)currentPosition.Quantity);
                    SmartLog($"ESFuturesORB: Carrying over position from previous day: {currentPosition.Quantity} contracts");
                    // Don't reset trade count - position was opened on previous day
                }
                else
                {
                    // Only reset position tracking if no position exists in Portfolio
                    _positionOpenedToday = false;
                    _currentDirection = 0;
                    _remainingContracts = 0;
                    _firstTargetHit = false;
                    _entryPrice = 0;
                    _stopLossPrice = 0;
                    _firstProfitPrice = 0;
                    _secondProfitPrice = 0;
                    // Reset trade count for new day
                    _dailyTradeCount = 0;
                    SmartLog("ESFuturesORB: No existing position - resetting all tracking");
                }
            }
            else
            {
                // If no contract yet, just reset everything
                _positionOpenedToday = false;
                _currentDirection = 0;
                _remainingContracts = 0;
                _firstTargetHit = false;
                _entryPrice = 0;
                _stopLossPrice = 0;
                _firstProfitPrice = 0;
                _secondProfitPrice = 0;
                _dailyTradeCount = 0;
                SmartLog("ESFuturesORB: No contract available yet - resetting all tracking");
            }
            _dailyPnL = 0;

            // Set range times (9:00-9:30 CST)
            _rangeStartTime = slice.Time.Date.Add(_config.TradingStartTime);
            _rangeEndTime = _rangeStartTime.AddMinutes(_config.RangePeriodMinutes);
            
            SmartLog($"ESFuturesORB: Range period set from {_rangeStartTime:HH:mm} to {_rangeEndTime:HH:mm} UTC");
        }

        private void UpdateCurrentContract()
        {
            if (_config.UseContinuousContract && _future != null)
            {
                _currentContract = _future.Mapped;
            }
            else
            {
                _currentContract = _future?.Symbol;
            }
        }

        private void UpdateOpeningRange(decimal price, DateTime time)
        {
            // Update range high/low
            if (price > _orbHigh || _orbHigh == decimal.MinValue)
            {
                _orbHigh = price;
            }
            if (price < _orbLow || _orbLow == decimal.MaxValue)
            {
                _orbLow = price;
            }

            // Check if range period is complete
            if (time >= _rangeEndTime && !_rangeEstablished)
            {
                _rangeEstablished = true;
                var rangeWidth = _orbHigh - _orbLow;
                
                if (_config.MinimumRangeWidthPoints < 0)
                {
                    _validRange = true;
                    SmartLog("ESFuturesORB: Opening Range Established (min width filter disabled)");
                }
                else
                {
                    _validRange = rangeWidth >= _config.MinimumRangeWidthPoints;
                    SmartLog("ESFuturesORB: Opening Range Established:");
                    SmartLog($"  High: {_orbHigh:F2}");
                    SmartLog($"  Low: {_orbLow:F2}");
                    SmartLog($"  Width: {rangeWidth:F2} points");
                    SmartLog($"  Valid: {(_validRange ? "YES" : "NO - Too narrow")}");
                    SmartLog($"  Required Width: {_config.MinimumRangeWidthPoints:F2} points");
                }
            }
        }

        private void CheckForEntrySignals(decimal currentPrice, Slice slice)
        {
            // NOTE: Basic entry restrictions (trading hours, max positions, margin) are now handled
            //       centrally by SimpleBaseStrategy.ShouldExecuteTrade() via OnShouldExecuteTrade() override
            //       Strategy-specific validations (daily limits) are in OnShouldExecuteTrade() below
            
            // Long entry: price breaks above range high + threshold
            var longTrigger = _orbHigh + _config.BreakoutThreshold;
            if (currentPrice >= longTrigger)
            {
                SmartLog($"ESFuturesORB: Long breakout detected - Price: {currentPrice:F2} >= Trigger: {longTrigger:F2}");
                ExecuteTrade(1, currentPrice); // 1 = long
                return;
            }
            
            // Short entry: price breaks below range low - threshold
            var shortTrigger = _orbLow - _config.BreakoutThreshold;
            if (currentPrice <= shortTrigger)
            {
                SmartLog($"ESFuturesORB: Short breakout detected - Price: {currentPrice:F2} <= Trigger: {shortTrigger:F2}");
                ExecuteTrade(-1, currentPrice); // -1 = short
                return;
            }
        }

        private void ExecuteTrade(int direction, decimal currentPrice)
        {
            if (_currentContract == null || _config.TotalContracts <= 0)
                return;

            try
            {
                // Check if we have an existing position and flatten it first
                var currentPosition = Algorithm.Portfolio[_currentContract];
                if (currentPosition != null && currentPosition.Quantity != 0)
                {
                    var existingQuantity = currentPosition.Quantity;
                    SmartLog($"ESFuturesORB: Flattening existing position of {existingQuantity} contracts at {currentPrice:F2}");
                    
                    var flattenOrder = MarketOrder(_currentContract, -existingQuantity, "Flatten Pre-Entry");
                    if (flattenOrder != null)
                    {
                        SmartLog($"ESFuturesORB: Position flattened successfully");
                    }
                }
                
                var quantity = _config.TotalContracts * direction;
                
                SmartLog($"ESFuturesORB: Executing {(direction > 0 ? "LONG" : "SHORT")} trade");
                SmartLog($"  Contracts: {Math.Abs(quantity)}");
                SmartLog($"  Entry Price: {currentPrice:F2}");
                
                var entryTag = direction > 0 ? "Entry Long" : "Entry Short";
                var orderTicket = MarketOrder(_currentContract, quantity, entryTag);
                
                if (orderTicket != null)
                {
                    // Increment daily trade count
                    _dailyTradeCount++;
                    
                    // Update position tracking
                    _positionOpenedToday = true;
                    _currentDirection = direction;
                    _entryPrice = currentPrice;
                    _remainingContracts = _config.TotalContracts;
                    _firstTargetHit = false;
                    
                    // Set risk management levels
                    SetRiskManagementLevels(currentPrice, direction);
                    
                    // Record entry time for any time-based processing
                    ExitRestrictions.RecordPositionEntry(_currentContract, Algorithm.Time);
                    
                    SmartLog($"ESFuturesORB: Trade executed successfully (Trade {_dailyTradeCount}/{_config.MaxTradesPerDay})");
                    SmartLog($"  Stop Loss: {_stopLossPrice:F2}");
                    SmartLog($"  First Target: {_firstProfitPrice:F2} ({_config.ContractsAtFirstTarget} contracts)");
                    SmartLog($"  Second Target: {_secondProfitPrice:F2} ({_config.TotalContracts - _config.ContractsAtFirstTarget} contracts)");
                }
            }
            catch (Exception ex)
            {
                SmartError($"ESFuturesORB: Error executing trade: {ex.Message}");
            }
        }

        private void SetRiskManagementLevels(decimal entryPrice, int direction)
        {
            if (direction > 0) // Long position
            {
                _stopLossPrice = entryPrice - _config.StopLossPoints;
                _firstProfitPrice = entryPrice + _config.FirstProfitTargetPoints;
                _secondProfitPrice = entryPrice + _config.SecondProfitTargetPoints;
            }
            else // Short position
            {
                _stopLossPrice = entryPrice + _config.StopLossPoints;
                _firstProfitPrice = entryPrice - _config.FirstProfitTargetPoints;
                _secondProfitPrice = entryPrice - _config.SecondProfitTargetPoints;
            }
        }

        private void CheckRiskManagementExits(decimal currentPrice)
        {
            if (_currentContract == null || _currentDirection == 0)
                return;

            var position = Algorithm.Portfolio[_currentContract];
            if (position.Quantity == 0)
            {
                _positionOpenedToday = false;
                _currentDirection = 0;
                return;
            }

            bool shouldExit = false;
            string exitReason = "";
            int contractsToExit = 0;

            // Check stop loss
            if ((_currentDirection > 0 && currentPrice <= _stopLossPrice) ||
                (_currentDirection < 0 && currentPrice >= _stopLossPrice))
            {
                shouldExit = true;
                exitReason = "Stop Loss";
                contractsToExit = _remainingContracts;
            }
            // Check first profit target
            else if (!_firstTargetHit && 
                     ((_currentDirection > 0 && currentPrice >= _firstProfitPrice) ||
                      (_currentDirection < 0 && currentPrice <= _firstProfitPrice)))
            {
                shouldExit = true;
                exitReason = "First Profit Target";
                contractsToExit = _config.ContractsAtFirstTarget;
                _firstTargetHit = true;
            }
            // Check second profit target
            else if (_firstTargetHit && 
                     ((_currentDirection > 0 && currentPrice >= _secondProfitPrice) ||
                      (_currentDirection < 0 && currentPrice <= _secondProfitPrice)))
            {
                shouldExit = true;
                exitReason = "Second Profit Target";
                contractsToExit = _remainingContracts;
            }

            if (shouldExit && contractsToExit > 0)
            {
                SmartLog($"ESFuturesORB: Exiting {contractsToExit} contracts due to {exitReason}");
                SmartLog($"  Current Price: {currentPrice:F2}");
                SmartLog($"  P&L per contract: {(_currentDirection * (currentPrice - _entryPrice)):F2} points");
                
                // Calculate exit quantity (negative of current direction to close)
                var exitQuantity = -_currentDirection * contractsToExit;
                
                var orderTicket = MarketOrder(_currentContract, exitQuantity, exitReason);
                
                if (orderTicket != null)
                {
                    _remainingContracts -= contractsToExit;
                    
                    // Update daily P&L
                    var pointsProfit = _currentDirection * (currentPrice - _entryPrice);
                    var dollarProfit = pointsProfit * contractsToExit * _future.SymbolProperties.ContractMultiplier;
                    _dailyPnL += dollarProfit;
                    
                    SmartLog($"ESFuturesORB: Exit executed - Remaining contracts: {_remainingContracts}");
                    SmartLog($"  Points P&L: {pointsProfit:F2}");
                    SmartLog($"  Dollar P&L: ${dollarProfit:F2}");
                    SmartLog($"  Daily P&L: ${_dailyPnL:F2}");
                    
                    // If all contracts are closed, reset position
                    if (_remainingContracts <= 0)
                    {
                        _positionOpenedToday = false;
                        _currentDirection = 0;
                        ExitRestrictions.ClearPositionEntry(_currentContract);
                        SmartLog("ESFuturesORB: All contracts closed - position reset");
                    }
                }
            }
        }
    }
}
using System;
using System.Collections.Generic;
using System.Linq;
using QuantConnect;
using QuantConnect.Data;
using QuantConnect.Data.Fundamental;
using QuantConnect.Data.UniverseSelection;
using QuantConnect.Securities;
using QuantConnect.Indicators;
using CoreAlgo.Architecture.Core.Implementations;
using CoreAlgo.Architecture.Core.Models;
using QuantConnect.Orders;
using QuantConnect.DataSource;

namespace CoreAlgo.Architecture.Core.Templates
{
    /// <summary>
    /// Kullamägi Episodic Pivot strategy implementation.
    /// Targets stocks with earnings catalysts that gap up 10%+ in pre-market,
    /// filtered by low recent momentum (30-day ROC) to find "neglected" stocks.
    /// Uses 20-day SMA trailing stop for exits.
    /// 
    /// Based on research from: https://stonkscapital.substack.com/p/modeling-kullamagi-part-3-episodic
    /// Strategy achieves 30% CAGR with -29% max drawdown by focusing on episodic events.
    /// </summary>
    public class EpisodicPivotTemplate : SimpleBaseStrategy
    {
        private EpisodicPivotConfig _config;
        private Dictionary<Symbol, SymbolData> _symbolData = new Dictionary<Symbol, SymbolData>();
        // Removed legacy recentEarnings set; we rely on upcoming earnings universe
        private HashSet<Symbol> _upcomingEarningsSymbols = new HashSet<Symbol>();
        
        // Daily tracking
        private int _dailyTradeCount = 0;
        private DateTime _lastTradingDay = DateTime.MinValue;
        
        // Performance tracking
        private decimal _totalGapTrades = 0;
        private decimal _successfulGapTrades = 0;

        // New: Prevent duplicate entries per symbol per day
        private HashSet<Symbol> _entryOrdersPlacedToday = new HashSet<Symbol>();

        // New: Coarse/Fine eligibility tracking (liquidity/size filters)
        private HashSet<Symbol> _coarseEligibleSymbols = new HashSet<Symbol>();
        private HashSet<Symbol> _fineEligibleSymbols = new HashSet<Symbol>();
        
        public override string Name => "Episodic Pivot";
        
        public override string Description => 
            "Kullamägi Episodic Pivot strategy: Targets earnings-catalyst stocks with 10%+ pre-market gaps, " +
            "filtered by low 30-day momentum to find 'neglected' opportunities. Uses 20-SMA trailing stop.";

        public override void OnInitialize()
        {
            SmartLog("EpisodicPivotTemplate.OnInitialize() starting...");
            
            try
            {
                // Load configuration
                Configure<EpisodicPivotConfig>();
                _config = (EpisodicPivotConfig)Config;
                SmartLog("Episodic Pivot configuration loaded successfully");
                
                // Log strategy parameters
                SmartLog(_config.GetConfigurationSummary());
                
                // Enable extended market hours for pre-market gap detection
                Algorithm.UniverseSettings.ExtendedMarketHours = _config.UseExtendedHours;
                SmartLog($"Extended market hours set to: {Algorithm.UniverseSettings.ExtendedMarketHours}");
                
                // Set up upcoming-earnings universe (announcement date)
                SmartLog("Setting up Upcoming Earnings universe (announcement day, pre-market only)...");
                Algorithm.AddUniverse<EODHDUpcomingEarnings>(data => EarningsSelectionFilter(data.OfType<EODHDUpcomingEarnings>()));

                // New: Add Coarse + Fine universe linked to earnings set, filtered by liquidity/size thresholds
                Algorithm.AddUniverse(
                    // Coarse selector
                    (IEnumerable<CoarseFundamental> coarse) =>
                    {
                        var minDollarVol = (double)_config.MinDollarVolume;
                        var filtered = coarse
                            .Where(c => c.HasFundamentalData
                                         && c.DollarVolume >= minDollarVol
                                         && _upcomingEarningsSymbols.Contains(c.Symbol))
                            .Select(c => c.Symbol)
                            .ToList();
                        _coarseEligibleSymbols = new HashSet<Symbol>(filtered);
                        return filtered;
                    },
                    // Fine selector
                    (IEnumerable<FineFundamental> fine) =>
                    {
                        var filtered = fine
                            .Where(f => f.MarketCap >= (double)_config.MinMarketCap
                                         && _coarseEligibleSymbols.Contains(f.Symbol))
                            .Select(f => f.Symbol)
                            .ToList();
                        _fineEligibleSymbols = new HashSet<Symbol>(filtered);
                        return filtered;
                    });
                
                // Initialize tracking collections
                _symbolData = new Dictionary<Symbol, SymbolData>();
                
                SmartLog($"Episodic Pivot initialized: Universe size limit = {_config.UniverseSizeLimit}");
                SmartLog($"Entry window: {_config.EntryWindowStart:hh\\:mm} - {_config.EntryWindowEnd:hh\\:mm}");
                SmartLog($"Gap threshold: {_config.MinGapPercent}%, Max ROC: {_config.MaxROC30Days}%");
            }
            catch (Exception ex)
            {
                SmartError($"EpisodicPivot: Failed to initialize: {ex.Message}");
                throw;
            }
        }

        /// <summary>
        /// Pre-execution logic that runs before any trade validation.
        /// Handles gap calculation and daily tracking reset.
        /// NO ORDER PLACEMENT ALLOWED HERE.
        /// </summary>
        protected override void OnPreExecuteAlways(Slice slice)
        {
            // Reset daily tracking at market open
            if (slice.Time.Date > _lastTradingDay)
            {
                _dailyTradeCount = 0;
                _lastTradingDay = slice.Time.Date;
                _entryOrdersPlacedToday.Clear();
                SmartLog($"New trading day: {slice.Time.Date:yyyy-MM-dd} - Daily trade count reset");
            }

            // Update previous close prices at market close for next day's gap calculation
            if (slice.Time.TimeOfDay == new TimeSpan(16, 0, 0)) // 4:00 PM ET
            {
                var closePrices = 0;
                foreach (var kvp in _symbolData)
                {
                    if (slice.Bars.ContainsKey(kvp.Key))
                    {
                        kvp.Value.PreviousClose = slice.Bars[kvp.Key].Close;
                        closePrices++;
                    }
                }
                if (closePrices > 0)
                {
                    SmartLog($"Updated {closePrices} previous close prices for gap calculation");
                }
            }

            // Calculate pre-market gaps if in extended hours
            if (IsPreMarketHours(slice.Time))
            {
                // Debug logging for pre-market activity
                if (slice.Time.TimeOfDay >= new TimeSpan(4, 0, 0) && slice.Time.TimeOfDay <= new TimeSpan(5, 0, 0) && slice.Time.Minute % 30 == 0)
                {
                    SmartLog($"[DEBUG] PreExecute at {slice.Time:HH:mm:ss}, calculating gaps...");
                    SmartLog($"[DEBUG] IsPreMarketHours={IsPreMarketHours(slice.Time)}");
                }
                
                CalculatePreMarketGaps(slice);
            }
        }

        /// <summary>
        /// Earnings selection filter using upcoming earnings dataset
        /// </summary>
        private IEnumerable<Symbol> EarningsSelectionFilter(IEnumerable<EODHDUpcomingEarnings> earnings)
        {
            var selected = new List<Symbol>();
            var total = 0;
            var bmo = 0;

            var today = Algorithm.Time.Date;

            foreach (var e in earnings)
            {
                total++;
                var reportDate = e.ReportDate.Date;
                if (reportDate != today) continue; // only announcement day

                // ReportTime can be enum/int; normalize via string and detect Before Market Open
                var rt = (e.ReportTime?.ToString() ?? string.Empty).Trim().ToUpperInvariant();
                var isBmo = rt.Contains("BEFORE") || rt.Contains("BMO") || rt.Contains("PRE");
                if (!isBmo) continue; // only pre-market announcements

                if (isBmo) bmo++;
                selected.Add(e.Symbol);
            }

            _upcomingEarningsSymbols = new HashSet<Symbol>(selected);
            SmartLog($"[EarningsUniverse] Total={total}, SelectedTodayBMO={selected.Count} (BMO={bmo}) on {today:yyyy-MM-dd}");
            return selected;
        }

        /// <summary>
        /// Strategy-specific validation for Episodic Pivot entries.
        /// Base class already handles trading hours, position limits, and margin checks.
        /// </summary>
        protected override bool OnShouldExecuteTrade(Slice slice, out string blockReason)
        {
            blockReason = "";
            
            // Base class already handles entry window validation (4:00-9:30 AM)
            // No need for redundant pre-market check here
            
            // 1. Check daily trade limit
            if (_dailyTradeCount >= _config.MaxDailyTrades)
            {
                blockReason = $"Daily trade limit reached ({_dailyTradeCount}/{_config.MaxDailyTrades})";
                return false;
            }
            
            // 2. Check if we have any qualifying gap-up candidates
            var gapCandidates = _symbolData.Count(kvp => 
                kvp.Value.CurrentGap >= _config.MinGapPercent / 100m &&
                kvp.Value.ROC30.IsReady &&
                kvp.Value.ROC30.Current.Value <= _config.MaxROC30Days / 100m &&
                _upcomingEarningsSymbols.Contains(kvp.Key) &&
                !Portfolio[kvp.Key].Invested);
            
            if (gapCandidates == 0)
            {
                blockReason = "No qualifying gap-up candidates with earnings catalyst";
                return false;
            }
            
            SmartLog($"Found {gapCandidates} qualifying episodic pivot candidates");
            return true; // All strategy-specific checks passed
        }

        /// <summary>
        /// Main entry logic - executes when all validations pass.
        /// Finds qualifying stocks and places market orders.
        /// </summary>
        protected override void OnExecute(Slice slice)
        {
            // Debug logging for entry execution
            SmartLog($"[DEBUG] OnExecute called at {slice.Time:HH:mm:ss}");
            SmartLog($"[DEBUG] Tracking {_symbolData.Count} symbols, {_upcomingEarningsSymbols.Count} with earnings");
            SmartLog($"[DEBUG] Daily trades: {_dailyTradeCount}/{_config.MaxDailyTrades}");
            
            // Find all qualifying candidates
            var candidates = new List<Symbol>();
            
            foreach (var kvp in _symbolData)
            {
                var symbol = kvp.Key;
                var data = kvp.Value;
                
                // Skip if already holding position
                if (Portfolio[symbol].Invested) continue;

                // Skip if we already placed an entry order for this symbol today, or there are open buy orders
                var hasOpenBuyOrder = Algorithm.Transactions.GetOpenOrders(symbol)
                    .Any(o => o.Direction == OrderDirection.Buy);
                if (_entryOrdersPlacedToday.Contains(symbol) || hasOpenBuyOrder) continue;
                
                // Check all entry criteria
                if (data.CurrentGap >= _config.MinGapPercent / 100m &&           // Minimum gap
                    data.ROC30.IsReady && data.ROC30.Current.Value <= _config.MaxROC30Days / 100m && // Low momentum (neglected)
                    _upcomingEarningsSymbols.Contains(symbol) &&                // Has earnings catalyst (announcement today, BMO)
                    Algorithm.Securities.ContainsKey(symbol))                  // Security exists
                {
                    candidates.Add(symbol);
                    SmartLog($"Candidate: {symbol} Gap:{data.CurrentGap:P2} ROC30:{data.ROC30.Current.Value:P2}");
                }
            }
            
            SmartLog($"[DEBUG] Found {candidates.Count} candidates meeting all criteria");
            
            if (candidates.Count == 0)
            {
                SmartLog("No candidates found despite passing ShouldExecuteTrade - data may not be ready");
                return;
            }
            
            // Sort by gap size (largest gaps first - strongest catalyst signal)
            candidates = candidates.OrderByDescending(s => _symbolData[s].CurrentGap).ToList();
            
            // Calculate how many positions we can add
            var currentPositions = Portfolio.Where(p => p.Value.Invested).Count();
            var maxNewPositions = _config.MaxPositions - currentPositions;
            var tradesRemaining = _config.MaxDailyTrades - _dailyTradeCount;
            var maxTradesToday = Math.Min(maxNewPositions, tradesRemaining);
            
            SmartLog($"Can place {maxTradesToday} trades today (Positions: {currentPositions}/{_config.MaxPositions}, Daily trades: {_dailyTradeCount}/{_config.MaxDailyTrades})");
            
            // Place orders for top candidates
            var ordersPlaced = 0;
            foreach (var symbol in candidates.Take(maxTradesToday))
            {
                try
                {
                    var security = Algorithm.Securities[symbol];
                    var price = security.Price;
                    
                    if (price <= 0)
                    {
                        SmartWarn($"Invalid price for {symbol}: ${price:F2}");
                        continue;
                    }
                    
                    // Calculate position size
                    var targetValue = Portfolio.TotalPortfolioValue * _config.AllocationPerPosition;
                    var quantity = (int)(targetValue / price);
                    
                    if (quantity <= 0)
                    {
                        SmartWarn($"Invalid quantity for {symbol}: {quantity}");
                        continue;
                    }
                    
                    // Place market order
                    var gapPercent = _symbolData[symbol].CurrentGap;
                    var rocPercent = _symbolData[symbol].ROC30.Current.Value;
                    var orderTag = $"EP Entry G:{gapPercent:P1} R:{rocPercent:P1}";
                    
                    var ticket = MarketOrder(symbol, quantity, tag: orderTag);
                    
                    if (ticket != null && ticket.Status != OrderStatus.Invalid)
                    {
                        ordersPlaced++;
                        _dailyTradeCount++;
                        _totalGapTrades++;

                        // Mark that we've placed an entry for this symbol today
                        _entryOrdersPlacedToday.Add(symbol);
                        
                        // Track entry details
                        _symbolData[symbol].EntryPrice = price;
                        _symbolData[symbol].EntryTime = slice.Time;
                        _symbolData[symbol].IsActive = true;
                        
                        SmartLog($"Entry order placed: {symbol} Qty:{quantity} @ ${price:F2} Gap:{gapPercent:P2} ROC:{rocPercent:P2}");
                        
                        // Register position with entry restrictions for tracking
                        if (EntryRestrictions != null)
                        {
// TODO: Add position tracking if needed
                        }
                    }
                    else
                    {
                        SmartError($"Failed to place entry order for {symbol}");
                    }
                }
                catch (Exception ex)
                {
                    SmartError($"Error placing order for {symbol}: {ex.Message}");
                }
            }
            
            if (ordersPlaced > 0)
            {
                SmartLog($"Placed {ordersPlaced} episodic pivot entry orders");
            }
        }

        /// <summary>
        /// Check exit conditions using 20-day SMA trailing stop.
        /// Always runs, even when new trades are blocked.
        /// </summary>
        protected override void CheckExitConditions(Slice slice)
        {
            var positionsToExit = new List<Symbol>();
            var exitCount = 0;
            
            foreach (var position in Portfolio.Where(p => p.Value.Invested))
            {
                var symbol = position.Key;
                
                // Ensure we have tracking data
                if (!_symbolData.ContainsKey(symbol) || !_symbolData[symbol].SMA20.IsReady)
                    continue;
                
                var data = _symbolData[symbol];
                var currentPrice = Algorithm.Securities[symbol].Price;
                var smaValue = data.SMA20.Current.Value;
                
                // Check SMA trailing stop
                if (currentPrice < smaValue)
                {
                    positionsToExit.Add(symbol);
                    var pnl = (currentPrice - data.EntryPrice) / data.EntryPrice;
                    SmartLog($"SMA stop triggered: {symbol} Price:${currentPrice:F2} < SMA:${smaValue:F2} PnL:{pnl:P2}");
                }
            }
            
            // Execute exits
            foreach (var symbol in positionsToExit)
            {
                try
                {
                    var quantity = Portfolio[symbol].Quantity;
                    var ticket = MarketOrder(symbol, -quantity, tag: "SMA Trailing Stop");
                    
                    if (ticket != null)
                    {
                        exitCount++;
                        
                        // Track success rate
                        var data = _symbolData[symbol];
                        var finalPrice = Algorithm.Securities[symbol].Price;
                        var pnl = (finalPrice - data.EntryPrice) / data.EntryPrice;
                        if (pnl > 0) _successfulGapTrades++;
                        
                        SmartLog($"Exit order placed: {symbol} Qty:{-quantity} PnL:{pnl:P2}");
                        
                        // Clear tracking data
                        data.EntryPrice = 0;
                        data.EntryTime = DateTime.MinValue;
                        data.IsActive = false;
                        
                        // Clear from restrictions tracking
                        if (ExitRestrictions != null)
                        {
                            ExitRestrictions.ClearPositionEntry(symbol);
                        }
                    }
                }
                catch (Exception ex)
                {
                    SmartError($"Error exiting position {symbol}: {ex.Message}");
                }
            }
            
            if (exitCount > 0)
            {
                var successRate = _totalGapTrades > 0 ? (_successfulGapTrades / _totalGapTrades) : 0;
                SmartLog($"Exited {exitCount} positions via SMA stop. Success rate: {successRate:P1} ({_successfulGapTrades}/{_totalGapTrades})");
            }
        }

        /// <summary>
        /// Handle security changes in the dynamic universe
        /// </summary>
        public override void OnSecuritiesChanged(SecurityChanges changes)
        {
            // Add tracking for new securities
            foreach (var security in changes.AddedSecurities)
            {
                var symbol = security.Symbol;
                // Ensure symbol is both in upcoming earnings selection and passes coarse/fine thresholds
                if (!_upcomingEarningsSymbols.Contains(symbol) || !_fineEligibleSymbols.Contains(symbol))
                {
                    SmartLog($"Skipping tracking for {symbol} (not in upcoming earnings set or not eligible after thresholds)");
                    continue;
                }

                if (!_symbolData.ContainsKey(symbol))
                {
                    _symbolData[symbol] = new SymbolData
                    {
                        Symbol = symbol,
                        SMA20 = Algorithm.SMA(symbol, _config.SMAStopPeriod, Resolution.Daily),
                        ROC30 = Algorithm.ROCP(symbol, 30, Resolution.Daily), // Rate of Change Percent
                        PreviousClose = 0,
                        CurrentGap = 0,
                        EntryPrice = 0,
                        EntryTime = DateTime.MinValue,
                        IsActive = false
                    };

                    // Seed indicators with a fixed history window for immediate readiness
                    try
                    {
                        var history = Algorithm.History(symbol, 40, Resolution.Daily)
                            .OrderBy(h => h.EndTime)
                            .ToList();
                        if (history.Count > 0)
                        {
                            foreach (var bar in history)
                            {
                                _symbolData[symbol].SMA20.Update(bar.EndTime, bar.Close);
                                _symbolData[symbol].ROC30.Update(bar.EndTime, bar.Close);
                            }
                            // Set previous close to last history close
                            _symbolData[symbol].PreviousClose = history.Last().Close;
                            SmartLog($"Seeded indicators for {symbol}: SMAReady={_symbolData[symbol].SMA20.IsReady}, ROCReady={_symbolData[symbol].ROC30.IsReady}, PrevClose=${_symbolData[symbol].PreviousClose:F2}");
                        }
                    }
                    catch (Exception ex)
                    {
                        SmartWarn($"Failed to seed indicators for {symbol}: {ex.Message}");
                    }

                    SmartLog($"Added tracking for {symbol}");
                }
            }
            
            // Clean up removed securities (but keep if we have active positions)
            foreach (var security in changes.RemovedSecurities)
            {
                var symbol = security.Symbol;
                if (!Portfolio[symbol].Invested && _symbolData.ContainsKey(symbol))
                {
                    _symbolData.Remove(symbol);
                    SmartLog($"Removed tracking for {symbol}");
                }
            }
            
            SmartLog($"Universe updated: {changes.AddedSecurities.Count()} added, {changes.RemovedSecurities.Count()} removed. Tracking {_symbolData.Count} symbols.");
        }

        /// <summary>
        /// Check if current time is in pre-market hours
        /// </summary>
        private bool IsPreMarketHours(DateTime time)
        {
            var tod = time.TimeOfDay;
            // Simple time check: 4:00 AM - 9:30 AM ET (aligns with entry window)
            return tod >= new TimeSpan(4, 0, 0) && tod < new TimeSpan(9, 30, 0);
        }

        /// <summary>
        /// Calculate pre-market gaps for all tracked symbols
        /// </summary>
        private void CalculatePreMarketGaps(Slice slice)
        {
            var gapsCalculated = 0;
            var significantGaps = 0;
            
            // Debug logging for gap calculation timing
            if (slice.Time.Minute % 30 == 0 || significantGaps > 0)
            {
                SmartLog($"[DEBUG] CalculatePreMarketGaps at {slice.Time:HH:mm:ss}, tracking {_symbolData.Count} symbols, {_upcomingEarningsSymbols.Count} with earnings");
            }
            
            foreach (var kvp in _symbolData)
            {
                var symbol = kvp.Key;
                var data = kvp.Value;
                
                if (slice.Bars.ContainsKey(symbol) && data.PreviousClose > 0)
                {
                    var currentPrice = slice.Bars[symbol].Open; // Pre-market opening price
                    data.CurrentGap = (currentPrice - data.PreviousClose) / data.PreviousClose;
                    gapsCalculated++;
                    
                    if (data.CurrentGap >= _config.MinGapPercent / 100m)
                    {
                        significantGaps++;
                        SmartLog($"Significant gap: {symbol} +{data.CurrentGap:P2} (${data.PreviousClose:F2} -> ${currentPrice:F2})");
                    }
                }
            }
            
            // Enhanced logging with detailed gap information
            if (gapsCalculated > 0 && (slice.Time.Minute % 30 == 0 || significantGaps > 0))
            {
                SmartLog($"Pre-market gaps calculated: {gapsCalculated} stocks, {significantGaps} significant gaps (>{_config.MinGapPercent}%)");
                
                // Log first 3 gaps to see actual values (avoid spam)
                var topGaps = _symbolData
                    .Where(kvp => kvp.Value.CurrentGap > 0)
                    .OrderByDescending(kvp => kvp.Value.CurrentGap)
                    .Take(3);
                
                foreach (var kvp in topGaps)
                {
                    var hasROC = kvp.Value.ROC30.IsReady;
                    var rocValue = hasROC ? kvp.Value.ROC30.Current.Value : 0;
                    var inEarnings = _upcomingEarningsSymbols.Contains(kvp.Key);
                    SmartLog($"[DEBUG] {kvp.Key}: Gap={kvp.Value.CurrentGap:P2}, ROC30={rocValue:P2} (Ready:{hasROC}), InEarnings={inEarnings}");
                }
            }
        }

        /// <summary>
        /// Override performance metrics to include strategy-specific tracking
        /// </summary>
        protected override void OnGetPerformanceMetrics(Dictionary<string, double> metrics)
        {
            base.OnGetPerformanceMetrics(metrics);
            
            // Add strategy-specific metrics
            metrics["TotalGapTrades"] = (double)_totalGapTrades;
            metrics["SuccessfulGapTrades"] = (double)_successfulGapTrades;
            metrics["GapTradeSuccessRate"] = _totalGapTrades > 0 ? (double)(_successfulGapTrades / _totalGapTrades) : 0;
            metrics["CurrentPositions"] = Portfolio.Where(p => p.Value.Invested).Count();
            metrics["TrackedSymbols"] = _symbolData.Count;
            metrics["UpcomingEarningsSymbols"] = _upcomingEarningsSymbols.Count;
            metrics["DailyTradeCount"] = _dailyTradeCount;
        }

        /// <summary>
        /// Data structure to track per-symbol indicators and state
        /// </summary>
        private class SymbolData
        {
            public Symbol Symbol { get; set; }
            public SimpleMovingAverage SMA20 { get; set; }
            public RateOfChangePercent ROC30 { get; set; }
            public decimal PreviousClose { get; set; }
            public decimal CurrentGap { get; set; }
            public decimal EntryPrice { get; set; }
            public DateTime EntryTime { get; set; }
            public bool IsActive { get; set; }
        }
    }
}
using System;
using System.Collections.Generic;
using System.Linq;
using QuantConnect;
using QuantConnect.Algorithm;
using QuantConnect.Data;
using QuantConnect.Data.Market;
using QuantConnect.Orders;
using QuantConnect.Securities;
using QuantConnect.Securities.Future;
using QuantConnect.Securities.Option;
using QuantConnect.Data.UniverseSelection;
using CoreAlgo.Architecture.Core.Models;
using CoreAlgo.Architecture.Core.Implementations;
using CoreAlgo.Architecture.QC.Helpers;

namespace CoreAlgo.Architecture.Core.Templates
{
    /// <summary>
    /// Futures options iron condor strategy.
    /// Demonstrates futures options trading using QuantConnect's native AddFutureOption.
    /// Key features:
    /// - Automatic futures options chain subscription
    /// - Iron condor construction and management
    /// - Greeks-based position selection
    /// - Risk management with profit targets and stop losses
    /// - Contract expiration handling
    /// </summary>
    public class FuturesIronCondorTemplate : SimpleBaseStrategy
    {
        private new FuturesIronCondorConfig Config => (FuturesIronCondorConfig)base.Config;
        
        private Future _future;
        private Symbol _futureOptionSymbol;
        private List<IronCondorPosition> _openPositions = new List<IronCondorPosition>();
        private DateTime _lastTradeScan = DateTime.MinValue;
        
        // Track futures options chain
        private Symbol _currentFutureContract;

        public override string Name => "FuturesIronCondor";
        public override string Description => "Futures options iron condor strategy using QuantConnect's native AddFutureOption";

        public override void OnInitialize()
        {
            // Base initialization is handled by SimpleBaseStrategy

            try
            {
                SmartLog($"FuturesIronCondor: Initializing with futures symbol {Config.FutureSymbol}");
                
                // Add the futures contract
                var addedSecurity = AssetManager.AddAsset(
                    this, 
                    Config.FutureSymbol,
                    Config.GetDataResolution(),
                    Config.UseContinuousContract,
                    Config.ContractDepthOffset);

                _future = addedSecurity as Future;
                if (_future == null)
                {
                    throw new InvalidOperationException($"Failed to add future {Config.FutureSymbol} - returned security is not a Future type");
                }

                // Set futures contract filter
                _future.SetFilter(0, Config.MaxDaysToExpiration + 30);
                
                // Add futures options using AssetManager
                _futureOptionSymbol = AssetManager.AddOptionsChain(this, _future, Config.GetDataResolution());
                
                if (_futureOptionSymbol == null)
                {
                    throw new InvalidOperationException("Failed to add futures options chain");
                }

                // Set up futures options filter
                var futureOption = Algorithm.Securities[_futureOptionSymbol] as Option;
                if (futureOption != null)
                {
                    futureOption.SetFilter(universe => universe
                        .Expiration(Config.MinDaysToExpiration, Config.MaxDaysToExpiration)
                        .Strikes(-10, 10) // Wide strike range for iron condor construction
                        .IncludeWeeklys());
                        
                    SmartLog($"FuturesIronCondor: Set futures options filter - DTE: {Config.MinDaysToExpiration}-{Config.MaxDaysToExpiration}, Weekly: {Config.UseWeeklyOptions}");
                }

                SmartLog($"FuturesIronCondor: Strategy initialized successfully");
                SmartLog($"FuturesIronCondor: Futures options chain symbol: {_futureOptionSymbol}");
            }
            catch (Exception ex)
            {
                SmartError($"FuturesIronCondor: Failed to initialize: {ex.Message}");
                throw;
            }
        }

        protected override void OnExecute(Slice data)
        {
            try
            {
                // Update current future contract
                UpdateCurrentFutureContract();
                
                // Manage existing positions
                ManageExistingPositions(data);
                
                // Look for new iron condor opportunities
                if (ShouldScanForNewTrades())
                {
                    ScanForIronCondorOpportunities(data);
                }
            }
            catch (Exception ex)
            {
                SmartError($"FuturesIronCondor: Error in Execute: {ex.Message}");
            }
        }

        // TODO: Add symbol changed events handling for futures rollover when needed

        private void UpdateCurrentFutureContract()
        {
            if (Config.UseContinuousContract && _future != null)
            {
                _currentFutureContract = _future.Mapped;
            }
            else
            {
                _currentFutureContract = _future?.Symbol;
            }
        }

        private bool ShouldScanForNewTrades()
        {
            // Check if we have room for more positions
            if (_openPositions.Count >= Config.MaxOpenPositions)
                return false;

            // Check time since last trade scan
            var timeSinceLastScan = Algorithm.Time - _lastTradeScan;
            return timeSinceLastScan.TotalHours >= Config.TradeScanFrequencyHours;
        }

        private void ScanForIronCondorOpportunities(Slice data)
        {
            if (!data.OptionChains.ContainsKey(_futureOptionSymbol))
                return;

            try
            {
                var optionChain = data.OptionChains[_futureOptionSymbol];
                if (!optionChain.Any())
                    return;

                SmartLog($"FuturesIronCondor: Scanning {optionChain.Count()} options for iron condor opportunities");

                // Get the underlying futures price
                var underlyingPrice = GetUnderlyingPrice(optionChain);
                if (underlyingPrice == 0)
                    return;

                // Find suitable iron condor strikes
                var ironCondor = FindBestIronCondor(optionChain, underlyingPrice);
                if (ironCondor != null)
                {
                    ExecuteIronCondor(ironCondor);
                }

                _lastTradeScan = Algorithm.Time;
            }
            catch (Exception ex)
            {
                SmartError($"FuturesIronCondor: Error scanning for opportunities: {ex.Message}");
            }
        }

        private decimal GetUnderlyingPrice(OptionChain optionChain)
        {
            if (_currentFutureContract != null && Algorithm.Securities.ContainsKey(_currentFutureContract))
            {
                return Algorithm.Securities[_currentFutureContract].Price;
            }

            // Fallback: use option chain underlying price
            return optionChain.Underlying.Price;
        }

        private IronCondorSetup FindBestIronCondor(OptionChain optionChain, decimal underlyingPrice)
        {
            try
            {
                // Group options by expiration
                var expirationGroups = optionChain
                    .Where(contract => contract.Expiry >= Algorithm.Time.AddDays(Config.MinDaysToExpiration) &&
                                      contract.Expiry <= Algorithm.Time.AddDays(Config.MaxDaysToExpiration))
                    .GroupBy(contract => contract.Expiry.Date)
                    .OrderBy(g => g.Key);

                foreach (var expirationGroup in expirationGroups)
                {
                    var expiration = expirationGroup.Key;
                    var daysToExpiry = (expiration - Algorithm.Time).Days;
                    
                    SmartLog($"FuturesIronCondor: Analyzing expiration {expiration:yyyy-MM-dd} ({daysToExpiry} DTE)");

                    var calls = expirationGroup.Where(contract => contract.Right == OptionRight.Call)
                                              .OrderBy(c => c.Strike)
                                              .ToList();
                    
                    var puts = expirationGroup.Where(contract => contract.Right == OptionRight.Put)
                                             .OrderByDescending(p => p.Strike)
                                             .ToList();

                    if (calls.Count < 2 || puts.Count < 2)
                        continue;

                    // Find iron condor strikes
                    var ironCondor = ConstructIronCondor(calls, puts, underlyingPrice, daysToExpiry);
                    if (ironCondor != null)
                    {
                        SmartLog($"FuturesIronCondor: Found suitable iron condor for {expiration:yyyy-MM-dd}");
                        return ironCondor;
                    }
                }

                return null;
            }
            catch (Exception ex)
            {
                SmartError($"FuturesIronCondor: Error finding iron condor: {ex.Message}");
                return null;
            }
        }

        private IronCondorSetup ConstructIronCondor(List<OptionContract> calls, List<OptionContract> puts, 
                                                   decimal underlyingPrice, int daysToExpiry)
        {
            // Find short call (OTM, target delta)
            var shortCall = calls.Where(c => c.Strike > underlyingPrice && 
                                           Math.Abs(c.Greeks.Delta) <= Config.ShortOptionDelta)
                                 .OrderBy(c => Math.Abs(c.Greeks.Delta - Config.ShortOptionDelta))
                                 .FirstOrDefault();

            if (shortCall == null) return null;

            // Find long call (further OTM)
            var longCallStrike = shortCall.Strike + Config.StrikeWidth;
            var longCall = calls.Where(c => Math.Abs(c.Strike - longCallStrike) < Config.StrikeWidth * 0.1m)
                               .OrderBy(c => Math.Abs(c.Strike - longCallStrike))
                               .FirstOrDefault();

            if (longCall == null) return null;

            // Find short put (OTM, target delta)
            var shortPut = puts.Where(p => p.Strike < underlyingPrice && 
                                     Math.Abs(p.Greeks.Delta) <= Config.ShortOptionDelta)
                              .OrderBy(p => Math.Abs(p.Greeks.Delta - Config.ShortOptionDelta))
                              .FirstOrDefault();

            if (shortPut == null) return null;

            // Find long put (further OTM)
            var longPutStrike = shortPut.Strike - Config.StrikeWidth;
            var longPut = puts.Where(p => Math.Abs(p.Strike - longPutStrike) < Config.StrikeWidth * 0.1m)
                             .OrderBy(p => Math.Abs(p.Strike - longPutStrike))
                             .FirstOrDefault();

            if (longPut == null) return null;

            // Calculate net credit
            var netCredit = shortCall.BidPrice + shortPut.BidPrice - longCall.AskPrice - longPut.AskPrice;
            var minCredit = Config.StrikeWidth * Config.MinimumCreditPercent;

            if (netCredit < minCredit)
            {
                SmartLog($"FuturesIronCondor: Insufficient credit - Got: ${netCredit:F2}, Required: ${minCredit:F2}");
                return null;
            }

            // Check IV percentile if filter is enabled
            if (Config.MinimumIVPercentile > 0)
            {
                var avgIV = (shortCall.ImpliedVolatility + shortPut.ImpliedVolatility) / 2;
                // Simplified IV percentile check (would need historical IV data for accurate percentile)
                if (avgIV < 0.2m) // Basic threshold instead of percentile
                {
                    SmartLog($"FuturesIronCondor: IV too low - Current: {avgIV:P1}");
                    return null;
                }
            }

            return new IronCondorSetup
            {
                ShortCall = shortCall,
                LongCall = longCall,
                ShortPut = shortPut,
                LongPut = longPut,
                NetCredit = netCredit,
                MaxProfit = netCredit,
                MaxLoss = Config.StrikeWidth - netCredit,
                UnderlyingPrice = underlyingPrice,
                DaysToExpiry = daysToExpiry,
                Expiration = shortCall.Expiry
            };
        }

        private void ExecuteIronCondor(IronCondorSetup setup)
        {
            try
            {
                SmartLog($"FuturesIronCondor: Executing iron condor - Credit: ${setup.NetCredit:F2}, Max Risk: ${setup.MaxLoss:F2}");

                var orders = new List<OrderTicket>();

                // Sell short call and short put, buy long call and long put
                orders.Add(Algorithm.MarketOrder(setup.ShortCall.Symbol, -1)); // Sell
                orders.Add(Algorithm.MarketOrder(setup.LongCall.Symbol, 1));   // Buy
                orders.Add(Algorithm.MarketOrder(setup.ShortPut.Symbol, -1));  // Sell
                orders.Add(Algorithm.MarketOrder(setup.LongPut.Symbol, 1));    // Buy

                if (orders.All(o => o != null))
                {
                    var position = new IronCondorPosition
                    {
                        Setup = setup,
                        EntryTime = Algorithm.Time,
                        Orders = orders,
                        MaxCredit = setup.NetCredit,
                        ProfitTarget = setup.NetCredit * Config.ProfitTargetPercent,
                        StopLoss = setup.NetCredit * Config.StopLossPercent
                    };

                    _openPositions.Add(position);
                    SmartLog($"FuturesIronCondor: Iron condor opened successfully - Position #{_openPositions.Count}");
                }
            }
            catch (Exception ex)
            {
                SmartError($"FuturesIronCondor: Error executing iron condor: {ex.Message}");
            }
        }

        private void ManageExistingPositions(Slice data)
        {
            var positionsToClose = new List<IronCondorPosition>();

            foreach (var position in _openPositions)
            {
                try
                {
                    var daysToExpiry = (position.Setup.Expiration - Algorithm.Time).Days;
                    
                    // Close position if approaching expiry
                    if (daysToExpiry <= Config.DaysToExpiryClose)
                    {
                        SmartLog($"FuturesIronCondor: Closing position due to expiry (DTE: {daysToExpiry})");
                        CloseIronCondor(position, "Expiry");
                        positionsToClose.Add(position);
                        continue;
                    }

                    // Check profit/loss
                    var currentPnL = CalculatePositionPnL(position);
                    
                    if (currentPnL >= position.ProfitTarget)
                    {
                        SmartLog($"FuturesIronCondor: Closing position for profit - P&L: ${currentPnL:F2}");
                        CloseIronCondor(position, "Profit Target");
                        positionsToClose.Add(position);
                    }
                    else if (currentPnL <= -position.StopLoss)
                    {
                        SmartLog($"FuturesIronCondor: Closing position for loss - P&L: ${currentPnL:F2}");
                        CloseIronCondor(position, "Stop Loss");
                        positionsToClose.Add(position);
                    }
                }
                catch (Exception ex)
                {
                    SmartError($"FuturesIronCondor: Error managing position: {ex.Message}");
                }
            }

            // Remove closed positions
            foreach (var position in positionsToClose)
            {
                _openPositions.Remove(position);
            }
        }

        private decimal CalculatePositionPnL(IronCondorPosition position)
        {
            try
            {
                decimal totalPnL = 0;

                // Calculate P&L for each leg
                totalPnL += Algorithm.Portfolio[position.Setup.ShortCall.Symbol].UnrealizedProfit;
                totalPnL += Algorithm.Portfolio[position.Setup.LongCall.Symbol].UnrealizedProfit;
                totalPnL += Algorithm.Portfolio[position.Setup.ShortPut.Symbol].UnrealizedProfit;
                totalPnL += Algorithm.Portfolio[position.Setup.LongPut.Symbol].UnrealizedProfit;

                return totalPnL;
            }
            catch
            {
                return 0;
            }
        }

        private void CloseIronCondor(IronCondorPosition position, string reason)
        {
            try
            {
                // Close all legs
                Algorithm.Liquidate(position.Setup.ShortCall.Symbol);
                Algorithm.Liquidate(position.Setup.LongCall.Symbol);
                Algorithm.Liquidate(position.Setup.ShortPut.Symbol);
                Algorithm.Liquidate(position.Setup.LongPut.Symbol);

                var finalPnL = CalculatePositionPnL(position);
                SmartLog($"FuturesIronCondor: Position closed - Reason: {reason}, Final P&L: ${finalPnL:F2}");
            }
            catch (Exception ex)
            {
                SmartError($"FuturesIronCondor: Error closing iron condor: {ex.Message}");
            }
        }

        // Helper classes
        private class IronCondorSetup
        {
            public OptionContract ShortCall { get; set; }
            public OptionContract LongCall { get; set; }
            public OptionContract ShortPut { get; set; }
            public OptionContract LongPut { get; set; }
            public decimal NetCredit { get; set; }
            public decimal MaxProfit { get; set; }
            public decimal MaxLoss { get; set; }
            public decimal UnderlyingPrice { get; set; }
            public int DaysToExpiry { get; set; }
            public DateTime Expiration { get; set; }
        }

        private class IronCondorPosition
        {
            public IronCondorSetup Setup { get; set; }
            public DateTime EntryTime { get; set; }
            public List<OrderTicket> Orders { get; set; }
            public decimal MaxCredit { get; set; }
            public decimal ProfitTarget { get; set; }
            public decimal StopLoss { get; set; }
        }
    }
}
using System;
using System.Linq;
using QuantConnect;
using QuantConnect.Algorithm;
using QuantConnect.Data;
using QuantConnect.Indicators;
using QuantConnect.Securities;
using QuantConnect.Securities.Future;
using QuantConnect.Data.UniverseSelection;
using CoreAlgo.Architecture.Core.Models;
using CoreAlgo.Architecture.Core.Implementations;
using CoreAlgo.Architecture.QC.Helpers;

namespace CoreAlgo.Architecture.Core.Templates
{
    /// <summary>
    /// Futures momentum trading strategy using moving average crossover.
    /// Demonstrates futures trading with:
    /// - Continuous contract support
    /// - Automatic rollover handling
    /// - Proper futures position sizing
    /// - Risk management (stop loss/take profit)
    /// - Contract rollover event handling
    /// </summary>
    public class FuturesMomentumTemplate : SimpleBaseStrategy
    {
        private new FuturesMomentumConfig Config => (FuturesMomentumConfig)base.Config;
        
        private Future _future;
        private SimpleMovingAverage _fastMA;
        private SimpleMovingAverage _slowMA;
        private DateTime _lastTradeTime = DateTime.MinValue;
        private Symbol _currentContract;
        
        // Risk management
        private decimal _entryPrice;
        private bool _hasPosition;
        private decimal _stopLossPrice;
        private decimal _takeProfitPrice;

        public override string Name => "FuturesMomentum";
        public override string Description => "Futures momentum trading strategy using moving average crossover";

        public override void OnInitialize()
        {
            // Configure and load parameters
            Configure<FuturesMomentumConfig>();

            try
            {
                SmartLog($"FuturesMomentum: Initializing with symbol {Config.FutureSymbol}");
                
                // Add the futures contract using enhanced AssetManager
                var addedSecurity = AssetManager.AddAsset(
                    this, 
                    Config.FutureSymbol,
                    Config.GetDataResolution(),
                    Config.UseContinuousContract,
                    Config.ContractDepthOffset);

                _future = addedSecurity as Future;
                if (_future == null)
                {
                    throw new InvalidOperationException($"Failed to add future {Config.FutureSymbol} - returned security is not a Future type");
                }

                // Set up futures contract filter
                if (Config.UseContinuousContract)
                {
                    // For continuous contracts, set a reasonable filter
                    _future.SetFilter(0, 180); // Front month to 6 months out
                    SmartLog($"FuturesMomentum: Set continuous contract filter (0-180 days)");
                }

                // Initialize moving averages
                _fastMA = Algorithm.SMA(_future.Symbol, Config.FastMAPeriod, Config.GetDataResolution());
                _slowMA = Algorithm.SMA(_future.Symbol, Config.SlowMAPeriod, Config.GetDataResolution());
                
                // Map strategy-specific risk parameters to centralized exit system
                base.Config.ProfitTarget = Config.TakeProfitPercent;
                base.Config.StopLoss = -Config.StopLossPercent;
                
                SmartLog($"FuturesMomentum: Created MAs - Fast: {Config.FastMAPeriod}, Slow: {Config.SlowMAPeriod}");
                SmartLog($"FuturesMomentum: Strategy initialized successfully");
                SmartLog($"FuturesMomentum: Contract details - Multiplier: {_future.SymbolProperties.ContractMultiplier}, MinPrice: {_future.SymbolProperties.MinimumPriceVariation}");
            }
            catch (Exception ex)
            {
                SmartError($"FuturesMomentum: Failed to initialize: {ex.Message}");
                throw;
            }
        }

        protected override bool OnShouldExecuteTrade(Slice slice, out string blockReason)
        {
            blockReason = "";
            return true; // Base handles hours/margin/positions
        }

        protected override void OnPreExecuteAlways(Slice slice)
        {
            UpdateCurrentContract();
        }

        protected override void OnExecute(Slice data)
        {
            if (!IsReady())
                return;

            try
            {
                // Check for new entry signals
                if (ShouldTrade() && HasCrossoverSignal())
                {
                    var signal = GetTradingSignal();
                    if (signal != 0)
                    {
                        ExecuteTrade(signal);
                    }
                }
            }
            catch (Exception ex)
            {
                SmartError($"FuturesMomentum: Error in Execute: {ex.Message}");
            }
        }

        // TODO: Add symbol changed events handling for futures rollover when needed

        private bool IsReady()
        {
            return _fastMA != null && _slowMA != null && _fastMA.IsReady && _slowMA.IsReady && _future != null;
        }

        private void UpdateCurrentContract()
        {
            if (Config.UseContinuousContract && _future != null)
            {
                _currentContract = _future.Mapped;
            }
            else
            {
                _currentContract = _future?.Symbol;
            }
        }

        private bool ShouldTrade()
        {
            // Check minimum time interval between trades
            var timeSinceLastTrade = Algorithm.Time - _lastTradeTime;
            if (timeSinceLastTrade.TotalMinutes < Config.MinimumTradeIntervalMinutes)
            {
                return false;
            }

            // Check if we already have a position
            if (_hasPosition)
            {
                return false;
            }

            // Volume filter (if enabled)
            if (Config.MinimumAverageVolume > 0 && _currentContract != null)
            {
                var security = Algorithm.Securities[_currentContract];
                if (security.Volume < Config.MinimumAverageVolume)
                {
                    return false;
                }
            }

            return true;
        }

        private bool HasCrossoverSignal()
        {
            if (!_fastMA.IsReady || !_slowMA.IsReady)
                return false;

            // Check for crossover signal
            var currentFast = _fastMA.Current.Value;
            var currentSlow = _slowMA.Current.Value;
            var previousFast = _fastMA[1];
            var previousSlow = _slowMA[1];

            // Bullish crossover: fast MA crosses above slow MA
            bool bullishCross = previousFast <= previousSlow && currentFast > currentSlow;
            
            // Bearish crossover: fast MA crosses below slow MA
            bool bearishCross = previousFast >= previousSlow && currentFast < currentSlow;

            return bullishCross || (bearishCross && Config.AllowShortTrades);
        }

        private int GetTradingSignal()
        {
            if (!_fastMA.IsReady || !_slowMA.IsReady)
                return 0;

            var currentFast = _fastMA.Current.Value;
            var currentSlow = _slowMA.Current.Value;
            var previousFast = _fastMA[1];
            var previousSlow = _slowMA[1];

            // Bullish crossover
            if (previousFast <= previousSlow && currentFast > currentSlow)
            {
                return 1; // Buy signal
            }

            // Bearish crossover
            if (Config.AllowShortTrades && previousFast >= previousSlow && currentFast < currentSlow)
            {
                return -1; // Sell signal
            }

            return 0; // No signal
        }

        private void ExecuteTrade(int signal)
        {
            if (_currentContract == null || signal == 0)
                return;

            try
            {
                var security = Algorithm.Securities[_currentContract];
                var currentPrice = security.Price;

                // Calculate position size
                var contractsToTrade = CalculatePositionSize(currentPrice);
                
                if (contractsToTrade == 0)
                {
                    SmartLog($"FuturesMomentum: Position size calculated as 0, skipping trade");
                    return;
                }

                // Adjust for signal direction
                var quantity = contractsToTrade * signal;

                SmartLog($"FuturesMomentum: Executing {(signal > 0 ? "LONG" : "SHORT")} trade - {Math.Abs(quantity)} contracts at ${currentPrice:F2}");
                
                // Execute the trade through wrapper
                var ticket = MarketOrder(_currentContract, quantity, tag: signal > 0 ? "Entry Long" : "Entry Short");
                
                if (ticket != null)
                {
                    // Update position tracking
                    _hasPosition = true;
                    _entryPrice = currentPrice;
                    _lastTradeTime = Algorithm.Time;
                    
                    // Set risk management levels (for logging only; centralized exits use base Config)
                    UpdateRiskManagementPrices(currentPrice, signal);

                    ExitRestrictions.RecordPositionEntry(_currentContract, Algorithm.Time);
                    
                    SmartLog($"FuturesMomentum: Trade executed - Entry: ${_entryPrice:F2}, Stop: ${_stopLossPrice:F2}, Target: ${_takeProfitPrice:F2}");
                }
                else
                {
                    SmartError("FuturesMomentum: MarketOrder returned null (blocked or failed)");
                }
            }
            catch (Exception ex)
            {
                SmartError($"FuturesMomentum: Error executing trade: {ex.Message}");
            }
        }

        private int CalculatePositionSize(decimal currentPrice)
        {
            try
            {
                var equity = Algorithm.Portfolio.TotalPortfolioValue;
                var contractMultiplier = _future.SymbolProperties.ContractMultiplier;
                var contractValue = currentPrice * contractMultiplier;
                
                // Calculate base position size
                var rawPosition = (equity * Config.PositionSizeMultiplier) / contractValue;
                var contractsToTrade = Math.Max(1, (int)Math.Floor(rawPosition));
                
                // Apply maximum contracts limit
                contractsToTrade = Math.Min(contractsToTrade, Config.MaxContractsPerTrade);
                
                SmartLog($"FuturesMomentum: Position sizing - Equity: ${equity:F0}, Contract Value: ${contractValue:F0}, Contracts: {contractsToTrade}");
                
                return contractsToTrade;
            }
            catch (Exception ex)
            {
                SmartError($"FuturesMomentum: Error calculating position size: {ex.Message}");
                return 1; // Default to 1 contract
            }
        }

        private void UpdateRiskManagementPrices(decimal entryPrice, int direction = 0)
        {
            if (direction == 0)
            {
                // Determine direction from current position
                var position = Algorithm.Portfolio[_currentContract];
                direction = position.Quantity > 0 ? 1 : -1;
            }

            if (direction > 0) // Long position
            {
                _stopLossPrice = entryPrice * (1 - Config.StopLossPercent);
                _takeProfitPrice = entryPrice * (1 + Config.TakeProfitPercent);
            }
            else // Short position
            {
                _stopLossPrice = entryPrice * (1 + Config.StopLossPercent);
                _takeProfitPrice = entryPrice * (1 - Config.TakeProfitPercent);
            }
        }

        // Centralized exits are handled by base CheckExitConditions
    }
}
using System;
using System.Collections.Generic;
using System.Linq;
using QuantConnect;
using QuantConnect.Data;
using QuantConnect.Orders;
using QuantConnect.Securities;
using QuantConnect.Securities.Option;
using CoreAlgo.Architecture.Core.Implementations;
using CoreAlgo.Architecture.Core.Models;

namespace CoreAlgo.Architecture.Core.Templates
{
    /// <summary>
    /// Iron Condor options strategy template
    /// QC-First approach - leverages QuantConnect's native capabilities
    /// </summary>
    public class IronCondorTemplate : SimpleBaseStrategy
    {
        private IronCondorConfig _config;
        private Symbol _underlying;
        private Symbol _optionSymbol;

        public override string Name => "Iron Condor";
        
        public override string Description => 
            "Neutral options strategy that profits from low volatility by selling both a call spread and put spread";

        public override void OnInitialize()
        {
            SmartLog("IronCondorTemplate.OnInitialize() starting...");
            
            // Configure with IronCondor-specific settings
            try
            {
                Configure<IronCondorConfig>();
                _config = (IronCondorConfig)Config;
                SmartLog("Configuration loaded successfully");
            }
            catch (Exception ex)
            {
                SmartError($"Failed to load configuration: {ex.Message}");
                throw;
            }
            
            // Configuration already loaded automatically by Configure<IronCondorConfig>()
            
            // Don't apply algorithm settings from config - Main.cs handles this
            // SetStartDate(_config.StartDate);
            // SetEndDate(_config.EndDate);
            // SetCash(_config.StartingCash);
            
            // Add underlying and options using AssetManager  
            SmartLog($"Setting up {_config.UnderlyingSymbol} for Iron Condor trading");
            SmartLog($"  Asset type - Index: {CoreAlgo.Architecture.QC.Helpers.AssetManager.IsIndex(_config.UnderlyingSymbol)}, " +
                     $"Future: {CoreAlgo.Architecture.QC.Helpers.AssetManager.IsFuture(_config.UnderlyingSymbol)}, " + 
                     $"Equity: {CoreAlgo.Architecture.QC.Helpers.AssetManager.IsEquity(_config.UnderlyingSymbol)}");
            
            // Setup underlying and options with VERY WIDE strike range for testing (Phase 5)
            SmartLog($"[PHASE5] Using VERY WIDE option filters to rule out restrictive filtering");
            SmartLog($"   Original DTE range: {_config.MinDaysToExpiration}-{_config.MaxDaysToExpiration}");
            SmartLog($"   Test DTE range: 1-60 (very wide)");
            SmartLog($"   Original strike range: ±10");
            SmartLog($"   Test strike range: ±50 (very wide)");
            
            (_underlying, _optionSymbol) = SetupOptionsForSymbol(_config.UnderlyingSymbol, 
                strikeRange: 10, _config.MinDaysToExpiration, _config.MaxDaysToExpiration, resolution: Resolution.Minute);
            
            SmartLog($"  Successfully added {_underlying} with options chain {_optionSymbol}");
            
            SmartLog($"Iron Condor initialized for {_config.UnderlyingSymbol}");
            SmartLog($"Configuration:");
            SmartLog($"   Put offset: {_config.PutShortStrikeOffset:P1} below, Call offset: {_config.CallShortStrikeOffset:P1} above");
            SmartLog($"   Strike widths: Put ${_config.PutStrikeWidth}, Call ${_config.CallStrikeWidth}");
            SmartLog($"   DTE range: {_config.MinDaysToExpiration}-{_config.MaxDaysToExpiration} days");
            SmartLog($"   Min premium: ${_config.MinimumPremium:F2}");
            SmartLog($"   Max positions: {_config.MaxActivePositions}");
            SmartLog($"Ready for aggressive Iron Condor trading!");
        }

        protected override void OnExecute(Slice slice)
        {
            // [DEBUG] COMPREHENSIVE SYMBOL ACCESS DEBUGGING
            SmartLog($"[START] IronCondor OnExecute ENTRY at {slice.Time}");
            SmartLog($"[DATA] Initial Slice Analysis:");
            SmartLog($"   slice.OptionChains.Count: {slice.OptionChains.Count}");
            SmartLog($"   slice.Bars.Count: {slice.Bars.Count}");
            SmartLog($"   slice.QuoteBars.Count: {slice.QuoteBars.Count}");
            SmartLog($"   slice.Ticks.Count: {slice.Ticks.Count}");
            SmartLog($"   slice.Keys.Count: {slice.Keys.Count}");
            
            // [DEBUG] DETAILED SYMBOL DEBUGGING
            SmartLog($"[TARGET] Symbol Access Analysis:");
            SmartLog($"   _underlying symbol: {_underlying}");
            SmartLog($"   _optionSymbol we're looking for: {_optionSymbol}");
            SmartLog($"   _optionSymbol.Value: {_optionSymbol.Value}");
            SmartLog($"   _optionSymbol.SecurityType: {_optionSymbol.SecurityType}");
            
            // [DEBUG] LOG ALL AVAILABLE SLICE KEYS
            if (slice.Keys.Any())
            {
                SmartLog($"[LIST] ALL Available Slice Keys ({slice.Keys.Count} total):");
                foreach (var key in slice.Keys.Take(20)) // Limit to first 20 to avoid spam
                {
                    SmartLog($"   Key: {key} (Type: {key.SecurityType})");
                }
                if (slice.Keys.Count > 20)
                {
                    SmartLog($"   ... and {slice.Keys.Count - 20} more keys");
                }
            }
            else
            {
                SmartLog($"[ERROR] NO Keys found in slice at all!");
            }
            
            // [DEBUG] LOG ALL AVAILABLE OPTION CHAIN KEYS
            if (slice.OptionChains.Keys.Any())
            {
                SmartLog($"[LIGHTNING] Available OptionChains Keys ({slice.OptionChains.Keys.Count} total):");
                foreach (var optKey in slice.OptionChains.Keys)
                {
                    SmartLog($"   OptionChain Key: {optKey} (Type: {optKey.SecurityType}, Value: {optKey.Value})");
                }
            }
            else
            {
                SmartLog($"[ERROR] NO OptionChains keys found in slice!");
            }
            
            // [DEBUG] COMPREHENSIVE DATA INSPECTION (PHASE 1)
            SmartLog($"=== COMPREHENSIVE DATA SLICE INSPECTION at {slice.Time} ===");
            SmartLog($"Algorithm time: {Time}");
            SmartLog($"Slice time: {slice.Time}");
            SmartLog($"Day of week: {slice.Time.DayOfWeek}");
            SmartLog($"Market open: {Securities[_underlying].Exchange.DateTimeIsOpen(slice.Time)}");
            
            // Securities collection inspection
            SmartLog($"=== SECURITIES COLLECTION INSPECTION ===");
            SmartLog($"Securities collection has {Securities.Keys.Count} securities");
            foreach (var sec in Securities.Keys.Take(10))
            {
                var security = Securities[sec];
                SmartLog($"  Security: {sec} Type: {security.Type} Resolution: {security.Subscriptions.GetHighestResolution()} HasData: {security.HasData}");
            }
            
            // Available symbols in slice
            SmartLog($"=== SLICE SYMBOLS INSPECTION ===");
            SmartLog($"Available symbols in slice: {string.Join(", ", slice.Keys.Take(15))}");
            if (slice.Keys.Count > 15)
            {
                SmartLog($"... and {slice.Keys.Count - 15} more symbols");
            }
            
            // [DEBUG] TEST DIFFERENT SYMBOL ACCESS PATTERNS (PHASE 2)
            SmartLog($"=== SYMBOL ACCESS PATTERN TESTING ===");
            
            // Test 1: Our current approach
            var foundWithOptionSymbol = slice.OptionChains.TryGetValue(_optionSymbol, out var chain1);
            SmartLog($"   Test 1 - _optionSymbol ({_optionSymbol}): {foundWithOptionSymbol}");
            if (foundWithOptionSymbol) SmartLog($"     Chain1 has {chain1.Count} contracts");
            
            // Test 2: Try underlying symbol
            var foundWithUnderlying = slice.OptionChains.TryGetValue(_underlying, out var chain2);
            SmartLog($"   Test 2 - _underlying ({_underlying}): {foundWithUnderlying}");
            if (foundWithUnderlying) SmartLog($"     Chain2 has {chain2.Count} contracts");
            
            // Test 3: Try canonical symbol
            try
            {
                var canonical = Symbol.CreateCanonicalOption(_underlying);
                var foundWithCanonical = slice.OptionChains.TryGetValue(canonical, out var chain3);
                SmartLog($"   Test 3 - Canonical ({canonical}): {foundWithCanonical}");
                if (foundWithCanonical) SmartLog($"     Chain3 has {chain3.Count} contracts");
            }
            catch (Exception ex)
            {
                SmartLog($"   Test 3 - Canonical creation failed: {ex.Message}");
            }
            
            // Test 4: Try exact string match with available keys
            Symbol matchingKey = null;
            foreach (var key in slice.OptionChains.Keys)
            {
                if (key.Value == _optionSymbol.Value || key.Value == _underlying.Value)
                {
                    matchingKey = key;
                    break;
                }
            }
            if (matchingKey != null)
            {
                var foundWithMatching = slice.OptionChains.TryGetValue(matchingKey, out var chain4);
                SmartLog($"   Test 4 - Matching key ({matchingKey}): {foundWithMatching}");
                if (foundWithMatching) SmartLog($"     Chain4 has {chain4.Count} contracts");
            }
            else
            {
                SmartLog($"   Test 4 - No matching keys found");
            }
            
            // Test 5: Try all available option chain keys
            if (slice.OptionChains.Any())
            {
                SmartLog($"   Test 5 - Trying all available option chain keys:");
                foreach (var kvp in slice.OptionChains.Take(3))
                {
                    SmartLog($"     Available key: {kvp.Key} has {kvp.Value.Count} contracts");
                    if (kvp.Value.Any())
                    {
                        var sample = kvp.Value.First();
                        SmartLog($"       Sample contract: {sample.Strike} {sample.Right} {sample.Expiry:MM/dd/yyyy}");
                    }
                }
            }
            
            // DEBUG: Log execution entry (keep existing for compatibility)
            Debug($"IronCondor OnExecute called at {slice.Time} with {slice.OptionChains.Count} option chains");
            
            // More aggressive Iron Condor logic to ensure trades happen
            if (slice.OptionChains.Count == 0)
            {
                SmartLog($"[ERROR] EARLY EXIT: No option chains available at {slice.Time}");
                SmartLog($"   This means our symbol access pattern is not finding any option data");
                SmartLog($"   Either: 1) Wrong symbol used, 2) No data available, 3) Filter too restrictive");
                Debug("No option chains available, returning");
                return;
            }
            
            SmartLog($"[SUCCESS] PROCEEDING: Found {slice.OptionChains.Count} option chains");

            // Check if we've reached position limit
            var activePositions = Portfolio.Count(x => x.Value.Invested);
            if (activePositions >= _config.MaxActivePositions)
                return;

            // Trade on any weekday to maximize trading opportunities
            if (slice.Time.DayOfWeek == DayOfWeek.Saturday || slice.Time.DayOfWeek == DayOfWeek.Sunday)
                return;

            // Get option chain for underlying
            if (slice.OptionChains.TryGetValue(_optionSymbol, out var chain))
            {
                var underlyingPrice = chain.Underlying.Price;
                SmartLog($"[DEBUG] PRE-FILTER OPTION CHAIN ANALYSIS for {_optionSymbol.Value}");
                SmartLog($"[DATA] RAW Option Universe: {chain.Count} total contracts at underlying price ${underlyingPrice:F2}");
                
                // Show complete raw option universe BEFORE any filtering
                SmartLog($"[CALENDAR] ALL Available Expiration Dates ({chain.Select(x => x.Expiry.Date).Distinct().Count()} unique):");
                var allExpirations = chain.Select(x => x.Expiry.Date).Distinct().OrderBy(x => x).ToList();
                foreach (var exp in allExpirations)
                {
                    var contractsForExp = chain.Where(x => x.Expiry.Date == exp).Count();
                    var daysToExp = (exp - slice.Time.Date).Days;
                    SmartLog($"  {exp:yyyy-MM-dd} ({daysToExp} DTE): {contractsForExp} contracts");
                }
                
                SmartLog($"[LIGHTNING] ALL Available Strikes ({chain.Select(x => x.Strike).Distinct().Count()} unique):");
                var allStrikes = chain.Select(x => x.Strike).Distinct().OrderBy(x => x).ToList();
                SmartLog($"  Range: ${allStrikes.FirstOrDefault():F2} to ${allStrikes.LastOrDefault():F2}");
                SmartLog($"  Strikes around current price (+/-10): {string.Join(", ", allStrikes.Where(s => Math.Abs(s - underlyingPrice) <= 10).Select(s => $"${s:F0}"))}");
                
                SmartLog($"[TARGET] Target Strike Analysis:");
                SmartLog($"  Current Price: ${underlyingPrice:F2}");
                SmartLog($"  Target Put Strike: ${underlyingPrice * (1 - _config.PutShortStrikeOffset):F2} ({_config.PutShortStrikeOffset:P1} below)");
                SmartLog($"  Target Call Strike: ${underlyingPrice * (1 + _config.CallShortStrikeOffset):F2} ({_config.CallShortStrikeOffset:P1} above)");
                
                // Show sample of raw option data with bid/ask prices
                SmartLog($"[LIST] Sample Raw Options (first 10 contracts):");
                var sampleOptions = chain.Take(10).ToList();
                foreach (var opt in sampleOptions)
                {
                    var dte = (opt.Expiry.Date - slice.Time.Date).Days;
                    SmartLog($"  {opt.Symbol.ID.StrikePrice:F0} {opt.Right} {opt.Expiry:MM/dd} ({dte}DTE) - Bid:${opt.BidPrice:F2} Ask:${opt.AskPrice:F2} Vol:{opt.Volume}");
                }
                
                // Show options by type before filtering
                var rawPuts = chain.Where(x => x.Right == OptionRight.Put).ToList();
                var rawCalls = chain.Where(x => x.Right == OptionRight.Call).ToList();
                SmartLog($"[DATA] Raw Option Types: {rawPuts.Count} Puts, {rawCalls.Count} Calls");
                
                Debug($"Processing {chain.Count} options contracts at underlying price ${underlyingPrice:F2}");
                
                // DEBUG: Show available expirations and strikes (keep existing debug for compatibility)
                var expirations = chain.Select(x => x.Expiry.Date).Distinct().OrderBy(x => x).ToList();
                Debug($"Available expiration dates: {string.Join(", ", expirations.Select(x => x.ToString("yyyy-MM-dd")))}");
                
                var strikes = chain.Select(x => x.Strike).Distinct().OrderBy(x => x).ToList();
                Debug($"Available strikes: Min=${strikes.FirstOrDefault():F2}, Max=${strikes.LastOrDefault():F2}, Count={strikes.Count}");
                Debug($"Strike range needed: Put=${underlyingPrice * (1 - _config.PutShortStrikeOffset):F2}, Call=${underlyingPrice * (1 + _config.CallShortStrikeOffset):F2}");
                
                // STEP-BY-STEP FILTERING WITH DETAILED DIAGNOSTICS
                SmartLog($"[DEBUG] FILTERING PROCESS - Step by Step Analysis:");
                
                // Step 1: DTE Filter
                var dteFiltered = chain.Where(x => 
                    (x.Expiry.Date - slice.Time.Date).Days >= _config.MinDaysToExpiration &&
                    (x.Expiry.Date - slice.Time.Date).Days <= _config.MaxDaysToExpiration).ToList();
                SmartLog($"[CALENDAR] Step 1 - DTE Filter ({_config.MinDaysToExpiration}-{_config.MaxDaysToExpiration} days): {chain.Count} -> {dteFiltered.Count} options ({chain.Count - dteFiltered.Count} removed)");
                
                if (dteFiltered.Count == 0)
                {
                    SmartLog($"[ERROR] No options match DTE criteria - showing why:");
                    var expirationsOutOfRange = chain.GroupBy(x => x.Expiry.Date)
                        .Select(g => new { Date = g.Key, Count = g.Count(), DTE = (g.Key - slice.Time.Date).Days })
                        .OrderBy(x => x.DTE).Take(5).ToList();
                    foreach (var exp in expirationsOutOfRange)
                    {
                        var status = exp.DTE < _config.MinDaysToExpiration ? "too soon" : "too far";
                        SmartLog($"  {exp.Date:yyyy-MM-dd} ({exp.DTE} DTE): {exp.Count} contracts - {status}");
                    }
                }
                
                // Step 2: Liquidity Filter (Bid/Ask > 0)
                var liquidOptions = dteFiltered.Where(x => x.BidPrice > 0 && x.AskPrice > 0).ToList();
                SmartLog($"[MONEY] Step 2 - Liquidity Filter (bid/ask > 0): {dteFiltered.Count} -> {liquidOptions.Count} options ({dteFiltered.Count - liquidOptions.Count} removed)");
                
                if (liquidOptions.Count < dteFiltered.Count)
                {
                    var illiquidCount = dteFiltered.Count - liquidOptions.Count;
                    var illiquidSample = dteFiltered.Where(x => x.BidPrice <= 0 || x.AskPrice <= 0).Take(3).ToList();
                    SmartLog($"[WARNING] {illiquidCount} options removed for liquidity issues. Sample:");
                    foreach (var opt in illiquidSample)
                    {
                        SmartLog($"  {opt.Strike:F0} {opt.Right} {opt.Expiry:MM/dd} - Bid:${opt.BidPrice:F2} Ask:${opt.AskPrice:F2}");
                    }
                }
                
                // Final result
                var allOptions = liquidOptions;
                SmartLog($"[SUCCESS] FINAL FILTERED RESULT: {allOptions.Count} options available for Iron Condor");
                
                // Show data quality metrics
                if (allOptions.Count > 0)
                {
                    var avgBidAskSpread = allOptions.Average(x => x.AskPrice - x.BidPrice);
                    var avgVolume = allOptions.Average(x => x.Volume);
                    SmartLog($"[DATA] Data Quality Metrics:");
                    SmartLog($"  Average Bid-Ask Spread: ${avgBidAskSpread:F3}");
                    SmartLog($"  Average Volume: {avgVolume:F0}");
                    SmartLog($"  Price Range: ${allOptions.Min(x => x.BidPrice):F2} - ${allOptions.Max(x => x.AskPrice):F2}");
                }

                // DEBUG: Show filtering results (keep existing debug for compatibility)
                Debug($"After DTE filter ({_config.MinDaysToExpiration}-{_config.MaxDaysToExpiration} days): {dteFiltered.Count} options");
                Debug($"After liquidity filter (bid/ask > 0): {liquidOptions.Count} options");

                if (allOptions.Count < 4)
                {
                    Debug($"Not enough liquid options found: {allOptions.Count}, need at least 4");
                    
                    // DEBUG: Show why options are being filtered out
                    var sample = chain.Take(5).Select(x => 
                        $"  {x.Symbol.ID.StrikePrice:F0} {x.Right} exp:{x.Expiry:MM/dd} DTE:{(x.Expiry.Date - slice.Time.Date).Days} Bid:{x.BidPrice:F2} Ask:{x.AskPrice:F2}");
                    Debug($"Sample options:\n{string.Join("\n", sample)}");
                    return;
                }

                // Separate puts and calls
                var puts = allOptions.Where(x => x.Right == OptionRight.Put).OrderByDescending(x => x.Strike).ToList();
                var calls = allOptions.Where(x => x.Right == OptionRight.Call).OrderBy(x => x.Strike).ToList();
                
                Debug($"Found {puts.Count} puts and {calls.Count} calls with good liquidity");

                if (puts.Count < 2 || calls.Count < 2)
                {
                    Debug("Need at least 2 puts and 2 calls for Iron Condor");
                    return;
                }

                // More flexible strike selection - find any reasonable strikes
                var shortPutStrike = underlyingPrice * (1 - _config.PutShortStrikeOffset);
                var shortCallStrike = underlyingPrice * (1 + _config.CallShortStrikeOffset);
                
                // Find closest available strikes to our targets
                var shortPut = puts.Where(p => p.Strike <= shortPutStrike).OrderByDescending(p => p.Strike).FirstOrDefault();
                var shortCall = calls.Where(c => c.Strike >= shortCallStrike).OrderBy(c => c.Strike).FirstOrDefault();
                
                // If we can't find exact strikes, try any reasonable ones
                if (shortPut == null)
                    shortPut = puts.Where(p => p.Strike < underlyingPrice).OrderByDescending(p => p.Strike).FirstOrDefault();
                if (shortCall == null)
                    shortCall = calls.Where(c => c.Strike > underlyingPrice).OrderBy(c => c.Strike).FirstOrDefault();
                
                if (shortPut == null || shortCall == null)
                {
                    Debug("Could not find suitable short strikes");
                    return;
                }

                // Find long strikes (wider spreads for better liquidity)
                var longPut = puts.Where(p => p.Strike <= shortPut.Strike - _config.PutStrikeWidth).OrderByDescending(p => p.Strike).FirstOrDefault();
                var longCall = calls.Where(c => c.Strike >= shortCall.Strike + _config.CallStrikeWidth).OrderBy(c => c.Strike).FirstOrDefault();
                
                if (longPut == null || longCall == null)
                {
                    Debug("Could not find suitable long strikes for protection");
                    return;
                }

                // Calculate total premium (be more lenient)
                var totalPremium = shortPut.BidPrice + shortCall.BidPrice - longPut.AskPrice - longCall.AskPrice;
                
                SmartLog($"Iron Condor Analysis:");
                SmartLog($"  Short Put: {shortPut.Strike} @ ${shortPut.BidPrice:F2}");
                SmartLog($"  Long Put: {longPut.Strike} @ ${longPut.AskPrice:F2}");
                SmartLog($"  Short Call: {shortCall.Strike} @ ${shortCall.BidPrice:F2}");
                SmartLog($"  Long Call: {longCall.Strike} @ ${longCall.AskPrice:F2}");
                SmartLog($"  Total Premium: ${totalPremium:F2} (Min required: ${_config.MinimumPremium:F2})");
                
                if (totalPremium >= _config.MinimumPremium)
                {
                    // QC-First: Execute Iron Condor using ComboMarketOrder with smart overlap prevention
                    SmartLog($"[COMBO ORDER] Executing Iron Condor as atomic multi-leg order");
                    SmartLog($"   Framework overlap prevention validates combo orders as atomic units");
                    
                    var legs = new List<Leg>
                    {
                        Leg.Create(shortPut.Symbol, -1),   // Short Put
                        Leg.Create(longPut.Symbol, 1),     // Long Put  
                        Leg.Create(shortCall.Symbol, -1),  // Short Call
                        Leg.Create(longCall.Symbol, 1)     // Long Call
                    };
                    
                    var tickets = ComboMarketOrder(legs, 1, tag: "Iron Condor");
                    
                    SmartLog($"IRON CONDOR OPENED! Premium: ${totalPremium:F2}");
                    SmartLog($"   Put Spread: {longPut.Strike:F0}/{shortPut.Strike:F0}");
                    SmartLog($"   Call Spread: {shortCall.Strike:F0}/{longCall.Strike:F0}");
                    SmartLog($"   Expiration: {shortPut.Expiry:MM/dd/yyyy}");
                    
                    // Log combo order results
                    if (tickets != null && tickets.Any())
                    {
                        SmartLog($"[SUCCESS] Combo Order Status: {tickets.Count} tickets created");
                        foreach (var ticket in tickets.Take(4))
                        {
                            SmartLog($"   Ticket {ticket.OrderId}: {ticket.Symbol.Value} x{ticket.Quantity} - {ticket.Status}");
                        }
                    }
                    else
                    {
                        SmartLog($"[ERROR] Combo order failed - no tickets returned");
                    }
                }
                else
                {
                    Debug($"Premium too low: ${totalPremium:F2} < ${_config.MinimumPremium:F2}");
                }
            }
        }

        protected override void OnGetPerformanceMetrics(System.Collections.Generic.Dictionary<string, double> metrics)
        {
            // Add Iron Condor specific metrics
            metrics["ActiveIronCondors"] = Portfolio.Count(x => x.Value.Invested && x.Key.SecurityType == SecurityType.Option) / 4.0; // 4 legs per IC
        }
    }
}
using System;
using System.Collections.Generic;
using System.Linq;
using QuantConnect;
using QuantConnect.Data;
using QuantConnect.Orders;
using QuantConnect.Securities;
using QuantConnect.Securities.Option;
using QuantConnect.Data.Market;
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>
    /// Multi-Asset Iron Condor strategy template demonstrating asset-specific parameter adaptation.
    /// Based on proven IronCondorTemplate pattern but with multi-asset configuration support.
    /// </summary>
    public class MultiAssetIronCondorTemplate : SimpleBaseStrategy
    {
        private MultiAssetIronCondorConfig _config;
        private Dictionary<string, Symbol> _underlyings = new Dictionary<string, Symbol>();
        private Dictionary<string, Symbol> _optionSymbols = new Dictionary<string, Symbol>();
        private Dictionary<string, DateTime> _lastTradeTime = new Dictionary<string, DateTime>();
        private CoreAlgoRiskManager _riskManager;
        
        // Strike range caching for performance optimization
        private Dictionary<string, (StrikeRangeCalculator.StrikeRange strikes, DateTime calculated, decimal underlyingPrice)> _strikeCache = 
            new Dictionary<string, (StrikeRangeCalculator.StrikeRange, DateTime, decimal)>();
        private readonly TimeSpan _cacheTimeout = TimeSpan.FromHours(1); // Cache strikes for 1 hour
        private readonly decimal _priceChangeThreshold = 0.02m; // Invalidate cache if price moves >2%

        /// <inheritdoc/>
        public override string Name => "Multi-Asset Iron Condor";

        /// <inheritdoc/>
        public override string Description => "Iron Condor strategy with asset-specific parameter adaptation for SPX, QQQ, AAPL, etc.";

        /// <summary>
        /// Initialize the multi-asset Iron Condor strategy
        /// </summary>
        public override void OnInitialize()
        {
            SmartLog("MultiAssetIronCondor.OnInitialize() starting...");
            
            // Determine configuration type based on strategy parameter
            var strategyType = Algorithm.GetParameter("Strategy", "MULTIASSET").ToUpper();
            
            // Configure the strategy with appropriate multi-asset configuration
            try
            {
                // CRITICAL: Create config but don't load parameters yet
                if (strategyType.Contains("SPX") || strategyType.Contains("INDEX"))
                {
                    _config = new MultiAssetIronCondorConfig();
                    SmartLog("MultiAsset: Created SPX/Index configuration");
                }
                else if (strategyType.Contains("EQUITY") || strategyType.Contains("STOCK"))
                {
                    _config = new MultiAssetIronCondorConfig();
                    SmartLog("MultiAsset: Created Equity/Stock configuration");
                }
                else
                {
                    _config = new MultiAssetIronCondorConfig();
                    SmartLog("MultiAsset: Created default configuration");
                }
                
                // Fix symbols BEFORE any parameter loading
                FixSymbolsFromParameters();
                
                // NOW load parameters with correct symbols
                _config.LoadFromParameters(this);
                SmartLog("MultiAsset configuration loaded with corrected symbols");
            }
            catch (Exception ex)
            {
                SmartError($"Failed to load MultiAsset configuration: {ex.Message}");
                throw;
            }

            // Initialize risk manager (optional - can be disabled via config)
            try
            {
                _riskManager = new CoreAlgoRiskManager(this);
                SmartLog("MultiAsset: Risk manager initialized successfully");
            }
            catch (Exception ex)
            {
                SmartError($"Warning: Could not initialize risk manager: {ex.Message}");
                // Continue without risk manager - template remains functional
                _riskManager = null;
            }

            // Add ALL underlying assets from the symbols array
            try
            {
                SmartLog($"MultiAsset: Adding {_config.Symbols.Length} assets: {string.Join(", ", _config.Symbols)}");
                
                foreach (var symbol in _config.Symbols)
                {
                    try
                    {
                        var security = AssetManager.AddAsset(this, symbol, Resolution.Minute);
                        _underlyings[symbol] = security.Symbol;
                        
                        // Add options chain for this asset
                        var optionSymbol = AssetManager.AddOptionsChain(this, security, Resolution.Minute);
                        _optionSymbols[symbol] = optionSymbol;
                        
                        SmartLog($"MultiAsset: Added {symbol} (Type: {security.Type}) with options");
                        
                        // Set up option chain filter for better performance
                        // Optimized: Reduced from (-10, 10, 20-60 days) to (-5, 5, 30-45 days)
                        // This significantly reduces data volume while maintaining strategy effectiveness
                        var option = Algorithm.Securities[optionSymbol] as Option;
                        if (option != null)
                        {
                            option.SetFilter(-5, 5, TimeSpan.FromDays(30), TimeSpan.FromDays(45));
                            SmartLog($"MultiAsset: Optimized option chain filter configured for {symbol} (strikes: ±5, DTE: 30-45)");
                        }
                        
                        // Log asset-specific parameters
                        var strikeWidth = _config.GetStrikeWidthForAsset(symbol);
                        var (deltaMin, deltaMax) = _config.GetDeltaTargetsForAsset(symbol);
                        var (minPos, maxPos, allocation) = _config.GetPositionLimitsForAsset(symbol);
                        
                        SmartLog($"MultiAsset: {symbol} parameters - " +
                                $"StrikeWidth: {strikeWidth:P1}, " +
                                $"Delta: {deltaMin:F2}-{deltaMax:F2}, " +
                                $"MaxPos: {maxPos}, Allocation: {allocation:P1}");
                    }
                    catch (Exception ex)
                    {
                        SmartError($"Failed to add asset {symbol}: {ex.Message}");
                        // Continue with other symbols instead of failing completely
                    }
                }
                
                SmartLog($"MultiAsset: Successfully added {_underlyings.Count} out of {_config.Symbols.Length} requested assets");
            }
            catch (Exception ex)
            {
                SmartError($"Failed to add multi-asset symbols: {ex.Message}");
                throw;
            }

            SmartLog("MultiAssetIronCondor initialization completed successfully");
        }

        /// <summary>
        /// Handle data updates - main execution logic for multi-asset trading
        /// </summary>
        protected override void OnExecute(Slice data)
        {
            // Debug logging for troubleshooting
            SmartLog($"OnExecute called at {Algorithm.Time} with {data.OptionChains.Count} option chains");
            
            // Process each asset independently
            foreach (var kvp in _optionSymbols)
            {
                var assetName = kvp.Key;
                var optionSymbol = kvp.Value;
                var underlyingSymbol = _underlyings[assetName];
                
                try
                {
                    // Only process if we have options data for this asset
                    if (!data.OptionChains.ContainsKey(optionSymbol))
                    {
                        SmartLog($"No option chain data for {assetName} ({optionSymbol})");
                        continue;
                    }

                    var chain = data.OptionChains[optionSymbol];
                    if (chain.Count == 0)
                    {
                        SmartLog($"Option chain for {assetName} is empty");
                        continue;
                    }

                    // Get current underlying price
                    var underlyingPrice = Algorithm.Securities[underlyingSymbol].Price;
                    if (underlyingPrice <= 0)
                    {
                        SmartLog($"Invalid underlying price for {assetName}: {underlyingPrice}");
                        continue;
                    }

                    // Price tracking handled by QC's portfolio system

                    SmartLog($"{assetName} price: ${underlyingPrice:F2}, option chain: {chain.Count} contracts");

                    // Check if we should enter new positions for this asset
                    if (ShouldEnterNewPosition(assetName))
                    {
                        TryEnterIronCondor(assetName, chain, underlyingPrice);
                    }
                }
                catch (Exception ex)
                {
                    SmartError($"Error processing {assetName}: {ex.Message}");
                }
            }

            // Check existing positions for exit conditions (all assets)
            CheckExitConditions();
        }

        /// <summary>
        /// Check if we should enter a new Iron Condor position for a specific asset
        /// </summary>
        private bool ShouldEnterNewPosition(string assetName)
        {
            // Count positions for this specific asset
            var assetPositions = Algorithm.Portfolio.Values
                .Where(x => x.Invested && 
                           x.Symbol.SecurityType == SecurityType.Option && 
                           x.Symbol.Underlying.Value == assetName)
                .Count();
                
            var totalPositions = Algorithm.Portfolio.Values.Where(x => x.Invested && x.Symbol.SecurityType == SecurityType.Option).Count();
            var currentTime = Algorithm.Time.TimeOfDay;
            
            // Get asset-specific limits
            var (minPos, maxPos, allocation) = _config.GetPositionLimitsForAsset(assetName);
            
            // Check minimum time between trades (prevent excessive trading)
            // Increased from 24h to 48h to reduce trade frequency and improve performance
            var minTimeBetweenTrades = TimeSpan.FromHours(48); // Only trade every 2 days per asset
            if (_lastTradeTime.ContainsKey(assetName))
            {
                var timeSinceLastTrade = Algorithm.Time - _lastTradeTime[assetName];
                if (timeSinceLastTrade < minTimeBetweenTrades)
                {
                    SmartLog($"{assetName}: Too soon since last trade ({timeSinceLastTrade.TotalHours:F1}h ago, min: {minTimeBetweenTrades.TotalHours}h)");
                    return false;
                }
            }
            
            var shouldEnter = assetPositions == 0 && 
                   totalPositions < _config.MaxPositions &&
                   currentTime >= _config.TradingStartTime &&
                   currentTime <= _config.TradingEndTime;
            
            // Debug logging to understand why we're not trading
            if (assetPositions > 0)
                SmartLog($"{assetName}: Already have {assetPositions} positions for this asset");
            if (totalPositions >= _config.MaxPositions)
                SmartLog($"{assetName}: At max total positions ({totalPositions}/{_config.MaxPositions})");
            if (currentTime < _config.TradingStartTime || currentTime > _config.TradingEndTime)
                SmartLog($"{assetName}: Outside trading hours: {currentTime} (window: {_config.TradingStartTime}-{_config.TradingEndTime})");
            
            if (shouldEnter)
                SmartLog($"{assetName}: Ready to enter new position");
            
            return shouldEnter;
        }

        /// <summary>
        /// Try to enter an Iron Condor position for a specific asset using proven QC patterns
        /// </summary>
        private void TryEnterIronCondor(string assetName, OptionChain chain, decimal underlyingPrice)
        {
            try
            {
                // Filter expiration: 30-45 days (matching our optimized option filter)
                var validExpirations = chain.Select(x => x.Expiry).Distinct()
                    .Where(exp => (exp - Algorithm.Time.Date).TotalDays >= 30 && 
                                  (exp - Algorithm.Time.Date).TotalDays <= 45)
                    .OrderBy(exp => exp);

                if (!validExpirations.Any())
                {
                    SmartLog("No valid expirations found");
                    return;
                }

                var targetExpiry = validExpirations.First();
                var contractsForExpiry = chain.Where(x => x.Expiry == targetExpiry).ToList();

                // Use cached strikes if available and still valid, otherwise calculate new ones
                StrikeRangeCalculator.StrikeRange strikes;
                
                if (_strikeCache.ContainsKey(assetName))
                {
                    var cached = _strikeCache[assetName];
                    var timeSinceCalculation = Algorithm.Time - cached.calculated;
                    var priceChange = Math.Abs(underlyingPrice - cached.underlyingPrice) / cached.underlyingPrice;
                    
                    // Use cache if it's fresh and price hasn't moved significantly
                    if (timeSinceCalculation < _cacheTimeout && priceChange < _priceChangeThreshold)
                    {
                        strikes = cached.strikes;
                        SmartLog($"{assetName}: Using cached strikes (age: {timeSinceCalculation.TotalMinutes:F1}m, price change: {priceChange:P1})");
                    }
                    else
                    {
                        SmartLog($"{assetName}: Cache invalidated - age: {timeSinceCalculation.TotalMinutes:F1}m, price change: {priceChange:P1}");
                        strikes = CalculateAndCacheStrikes(assetName, chain, underlyingPrice);
                    }
                }
                else
                {
                    SmartLog($"{assetName}: No cached strikes, calculating new ones");
                    strikes = CalculateAndCacheStrikes(assetName, chain, underlyingPrice);
                }
                
                SmartLog($"{assetName}: Calculated strikes: Put@{strikes.ShortPutStrike}, Call@{strikes.ShortCallStrike} (ATM: {strikes.ATMStrike})");

                // Find the option contracts using calculated strikes
                var shortPut = contractsForExpiry.FirstOrDefault(x => 
                    x.Right == OptionRight.Put && x.Strike == strikes.ShortPutStrike);
                var shortCall = contractsForExpiry.FirstOrDefault(x => 
                    x.Right == OptionRight.Call && x.Strike == strikes.ShortCallStrike);

                if (shortPut == null || shortCall == null)
                {
                    SmartLog($"{assetName}: Could not find contracts at calculated strikes");
                    return;
                }
                
                SmartLog($"{assetName}: Found contracts: Put@{shortPut.Strike}, Call@{shortCall.Strike}");

                // Use PositionAdapter for dynamic position sizing
                var (minPos, maxPos, configAllocation) = _config.GetPositionLimitsForAsset(assetName);
                var allocation = GetAllocationForAsset(assetName); // Use template-specific allocation
                
                // Calculate base position size from configuration
                var baseSize = Math.Max(1, (int)(Algorithm.Portfolio.TotalPortfolioValue * allocation / (underlyingPrice * 100)));
                
                // Use simple position sizing based on asset profile
                var assetProfile2 = MultiAssetHelper.GetAssetProfile(assetName);
                var maxPosition = assetProfile2?.MaxPosition ?? 5;
                var adaptedSize = Math.Min(baseSize, maxPosition);
                int contracts = (int)Math.Max(minPos, Math.Min(adaptedSize, maxPos));
                
                // Asset-specific validation
                var profile = MultiAssetHelper.GetAssetProfile(assetName);
                var accountSize = _config.AccountSize; // Use configurable account size
                
                if (profile != null && accountSize < profile.MinAccountSize)
                {
                    SmartLog($"{assetName}: Account ${accountSize:F0} below minimum ${profile.MinAccountSize:F0} for this asset - skipping");
                    contracts = 0; // Skip trading for insufficient account size
                }
                else if (AssetManager.IsIndex(assetName))
                {
                    // Always cap at 1 contract for indices
                    contracts = Math.Min(contracts, 1);
                    SmartLog($"{assetName}: Index position sizing - Adapted size: {adaptedSize}, Final: {contracts} contracts (max 1 for indices)");
                }
                else
                {
                    SmartLog($"{assetName}: Equity position sizing - Base: {baseSize}, Adapted: {adaptedSize}, Final: {contracts} contracts");
                }

                // Pre-flight risk validation (optional - continues if risk manager not available)
                var passesRiskValidation = true;
                if (_riskManager != null && contracts > 0)
                {
                    passesRiskValidation = _riskManager.ValidateNewPosition(assetName, contracts, underlyingPrice);
                    if (!passesRiskValidation)
                    {
                        SmartLog($"MultiAsset: {assetName} position blocked by risk manager");
                    }
                }

                // Pre-flight margin check before placing orders
                if (contracts > 0 && passesRiskValidation && ValidateMarginRequirements(assetName, contracts, underlyingPrice))
                {
                    // Enter the Iron Condor using ComboMarketOrder for atomic execution
                    var legs = new List<Leg>
                    {
                        Leg.Create(shortPut.Symbol, -contracts),  // Short Put
                        Leg.Create(shortCall.Symbol, -contracts)  // Short Call
                    };
                    
                    var tickets = ComboMarketOrder(legs, 1, tag: $"MultiAsset Iron Condor {assetName}");

                    // Record trade time to prevent excessive trading
                    _lastTradeTime[assetName] = Algorithm.Time;

                    SmartLog($"MultiAsset: Entered Iron Condor (ComboOrder) for {assetName} - " +
                            $"Put: ${shortPut.Strike}, Call: ${shortCall.Strike}, " +
                            $"Contracts: {contracts}, Price: ${underlyingPrice:F2}");
                    
                    // Log combo order results
                    if (tickets != null && tickets.Any())
                    {
                        SmartLog($"  Combo Order Status: {tickets.Count} tickets created");
                        foreach (var ticket in tickets.Take(2))
                        {
                            SmartLog($"    Ticket {ticket.OrderId}: {ticket.Symbol.Value} x{ticket.Quantity} - {ticket.Status}");
                        }
                    }
                    else
                    {
                        SmartLog($"  [ERROR] Combo order failed - no tickets returned");
                    }
                }
                else
                {
                    var reason = contracts == 0 ? "zero contracts calculated" : 
                                !passesRiskValidation ? "failed risk validation" : 
                                "failed margin validation";
                    SmartLog($"MultiAsset: Skipped {assetName} - {reason}");
                }
            }
            catch (Exception ex)
            {
                SmartError($"Error entering Iron Condor for {assetName}: {ex.Message}");
            }
        }

        /// <summary>
        /// Fix symbols array from QuantConnect parameters before config adaptation
        /// This is critical because QC passes arrays as "System.String[]" string
        /// </summary>
        private void FixSymbolsFromParameters()
        {
            try
            {
                // Read parameters directly from QuantConnect
                var symbolOverride = Algorithm.GetParameter("Symbol", "");
                var underlyingSymbolLegacy = Algorithm.GetParameter("UnderlyingSymbol", "");
                var symbolsParameter = Algorithm.GetParameter("Symbols", "");
                
                SmartLog($"MultiAsset: Raw Parameters - Symbol='{symbolOverride}', UnderlyingSymbol='{underlyingSymbolLegacy}', Symbols='{symbolsParameter}'");
                
                string[] actualSymbols = null;
                string primarySymbol = "";
                
                // Priority order: Symbol > Symbols > UnderlyingSymbol
                if (!string.IsNullOrEmpty(symbolOverride))
                {
                    primarySymbol = symbolOverride;
                    actualSymbols = new[] { symbolOverride };
                    SmartLog($"MultiAsset: Using Symbol override: {primarySymbol}");
                }
                else if (!string.IsNullOrEmpty(symbolsParameter) && symbolsParameter != "System.String[]")
                {
                    // Parse Symbols parameter directly (comma-separated)
                    actualSymbols = symbolsParameter.Split(',').Select(s => s.Trim()).Where(s => !string.IsNullOrEmpty(s)).ToArray();
                    if (actualSymbols.Length > 0)
                    {
                        primarySymbol = actualSymbols[0];
                        SmartLog($"MultiAsset: Parsed Symbols parameter: [{string.Join(", ", actualSymbols)}] (Primary: {primarySymbol})");
                    }
                }
                else if (!string.IsNullOrEmpty(underlyingSymbolLegacy) && underlyingSymbolLegacy != "SPY")
                {
                    primarySymbol = underlyingSymbolLegacy;
                    actualSymbols = new[] { underlyingSymbolLegacy };
                    SmartLog($"MultiAsset: Using UnderlyingSymbol (legacy): {primarySymbol}");
                }
                
                // Update config with correct symbols if we found any
                if (actualSymbols != null && actualSymbols.Length > 0)
                {
                    // Update the symbol arrays BEFORE config loads parameters
                    _config.Symbols = actualSymbols;
                    if (_config.Symbols.Length > 0)
                    {
                        _config.UnderlyingSymbol = actualSymbols[0]; // Set primary symbol too
                    }
                    SmartLog($"MultiAsset: Pre-configured with {actualSymbols.Length} symbols: [{string.Join(", ", actualSymbols)}]");
                }
                else
                {
                    SmartLog($"MultiAsset: No valid symbols found in parameters, keeping defaults: [{string.Join(", ", _config.Symbols)}]");
                }
            }
            catch (Exception ex)
            {
                SmartError($"Error fixing symbols from parameters: {ex.Message}");
            }
        }

        /// <summary>
        /// Validate margin requirements before placing orders
        /// </summary>
        private bool ValidateMarginRequirements(string assetName, int contracts, decimal underlyingPrice)
        {
            try
            {
                // Create a list of legs for the Iron Condor strategy
                var legs = new List<Leg>();
                
                // For margin calculation purposes, we need to estimate the position
                // Since we're doing a simplified Iron Condor (just short put and call)
                // we'll validate based on strategy type
                // Use QC's built-in margin validation
                var isValid = Algorithm.Portfolio.MarginRemaining > 0;
                
                // Also check current margin utilization
                var marginUtilization = Algorithm.Portfolio.TotalMarginUsed / Algorithm.Portfolio.TotalPortfolioValue;
                var isApproachingLimit = marginUtilization > 0.6m; // 60% threshold
                
                if (isApproachingLimit)
                {
                    SmartLog($"{assetName}: Margin utilization high ({marginUtilization:P1}) - skipping trade");
                    return false;
                }
                
                // Get margin statistics for logging
                // Use QC's built-in portfolio metrics
                SmartLog($"{assetName}: Margin validation - " +
                        $"Used: ${Algorithm.Portfolio.TotalMarginUsed:F0}, " +
                        $"Remaining: ${Algorithm.Portfolio.MarginRemaining:F0}, " +
                        $"Utilization: {marginUtilization:P1}, " +
                        $"Result: {(isValid ? "PASS" : "FAIL")}");
                
                return isValid;
            }
            catch (Exception ex)
            {
                SmartError($"Error validating margin for {assetName}: {ex.Message}");
                return false; // Fail safe - don't trade if validation fails
            }
        }

        /// <summary>
        /// Get template-specific allocation for each asset
        /// </summary>
        private decimal GetAllocationForAsset(string assetName)
        {
            return assetName switch
            {
                "SPX" => 0.03m,  // 3% for SPX (high margin, lower allocation)
                "SPY" => 0.15m,  // 15% for SPY (lower margin, higher allocation)
                "QQQ" => 0.12m,  // 12% for QQQ
                "AAPL" => 0.10m, // 10% for individual stocks
                "TSLA" => 0.08m, // 8% for high volatility stocks
                _ => _config.AllocationPerPosition // Use config default for unknown assets
            };
        }

        /// <summary>
        /// Calculate strikes using StrikeRangeCalculator and cache the result for performance
        /// </summary>
        private StrikeRangeCalculator.StrikeRange CalculateAndCacheStrikes(string assetName, OptionChain chain, decimal underlyingPrice)
        {
            try
            {
                // Use asset profile volatility from MultiAssetHelper
                var assetProfile = MultiAssetHelper.GetAssetProfile(assetName);
                var currentVolatility = assetProfile?.TypicalVolatility ?? 0.20m; // Default 20% volatility
                    
                var deltaTargets = StrikeRangeCalculator.GetAssetSpecificDeltas(assetName, currentVolatility);
                
                // For simplified Iron Condor, we only need short strikes
                var simplifiedTargets = new StrikeRangeCalculator.DeltaTargets
                {
                    ShortPut = deltaTargets.ShortPut,
                    ShortCall = deltaTargets.ShortCall,
                    LongPut = 0, // Not used in simplified version
                    LongCall = 0  // Not used in simplified version
                };
                
                // Calculate optimal strikes using the calculator
                var strikes = StrikeRangeCalculator.CalculateStrikeRange(chain, simplifiedTargets);
                
                // Cache the result with current time and price
                _strikeCache[assetName] = (strikes, Algorithm.Time, underlyingPrice);
                
                SmartLog($"{assetName}: Calculated and cached new strikes at price ${underlyingPrice:F2}");
                
                return strikes;
            }
            catch (Exception ex)
            {
                SmartError($"Error calculating strikes for {assetName}: {ex.Message}");
                
                // Return a simple fallback strike range if calculation fails
                var atmStrike = underlyingPrice;
                return new StrikeRangeCalculator.StrikeRange
                {
                    ATMStrike = atmStrike,
                    ShortPutStrike = atmStrike * 0.95m,
                    ShortCallStrike = atmStrike * 1.05m,
                    LongPutStrike = atmStrike * 0.90m,
                    LongCallStrike = atmStrike * 1.10m
                };
            }
        }

        /// <summary>
        /// Check exit conditions for existing positions across all assets
        /// </summary>
        private void CheckExitConditions()
        {
            try
            {
                var optionPositions = Algorithm.Portfolio.Values
                    .Where(x => x.Invested && x.Symbol.SecurityType == SecurityType.Option)
                    .ToList();

                SmartLog($"MultiAsset: Checking exit conditions for {optionPositions.Count} option positions");

                foreach (var position in optionPositions)
                {
                    // Determine which asset this position belongs to
                    var underlyingSymbol = position.Symbol.Underlying.Value;
                    var assetName = _underlyings.FirstOrDefault(kvp => kvp.Value.Value == underlyingSymbol).Key;
                    
                    if (string.IsNullOrEmpty(assetName))
                    {
                        SmartLog($"MultiAsset: Could not determine asset for position {position.Symbol}");
                        assetName = underlyingSymbol; // Fallback to underlying symbol
                    }

                    var currentValue = position.HoldingsValue;
                    var entryValue = position.HoldingsCost;
                    
                    if (entryValue != 0)
                    {
                        var pnlPercent = (currentValue - entryValue) / Math.Abs(entryValue);
                        
                        // Exit at profit target or stop loss from config
                        if (pnlPercent >= _config.ProfitTarget || pnlPercent <= _config.StopLoss)
                        {
                            Algorithm.MarketOrder(position.Symbol, -position.Quantity);
                            SmartLog($"MultiAsset: Exited {assetName} position {position.Symbol} at {pnlPercent:P1} P&L (Value: ${currentValue:F0}, Cost: ${entryValue:F0})");
                        }
                        else
                        {
                            SmartLog($"MultiAsset: {assetName} position {position.Symbol} P&L: {pnlPercent:P1} (holding, target: {_config.ProfitTarget:P1}, stop: {_config.StopLoss:P1})");
                        }
                    }
                    else
                    {
                        SmartLog($"MultiAsset: {assetName} position {position.Symbol} has zero entry value, skipping exit check");
                    }
                }
            }
            catch (Exception ex)
            {
                SmartError($"Error checking exit conditions: {ex.Message}");
            }
        }
    }
}
using System;
using System.Collections.Generic;
using System.Linq;
using QuantConnect;
using QuantConnect.Data;
using QuantConnect.Data.Market;
using QuantConnect.Orders;
using QuantConnect.Orders.Slippage;
using QuantConnect.Securities;
using QuantConnect.Securities.Option;
using QuantConnect.Indicators;
using CoreAlgo.Architecture.Core.Implementations;
using CoreAlgo.Architecture.Core.Models;

namespace CoreAlgo.Architecture.Core.Templates
{
	/// <summary>
	/// Opening Range Breakout (ORB) strategy template
	/// Monitors opening range and opens 0DTE credit spreads at 12:00 PM based on price action
	/// Includes SMA(20) filter, FOMC day skipping, and capital allocation limits
	/// </summary>
	public class ORBTemplate : SimpleBaseStrategy
	{
		private ORBConfig _config;
		private Symbol _underlying;
		private Symbol _optionSymbol;
		private SimpleMovingAverage _sma20;
		
		// Opening range tracking
		private decimal _orbHigh = decimal.MinValue;
		private decimal _orbLow = decimal.MaxValue;
		private decimal _openingPrice = 0;
		private DateTime _rangeEndTime;
		private bool _rangeEstablished = false;
		private bool _validRangeWidth = false;
		
		// Day-long price monitoring
		private bool _dayHighExceededORB = false;
		private bool _dayLowBrokeORB = false;
		private decimal _dayHigh = decimal.MinValue;
		private decimal _dayLow = decimal.MaxValue;
		
		// Position tracking
		private bool _positionOpenedToday = false;
		private DateTime _lastTradingDay = DateTime.MinValue;

		// New state for entry gating and breakout resolution
		private bool _entryEvaluatedToday = false;
		private DateTime? _firstBreakoutTime = null;
		private string _firstBreakoutDirection = null; // "Up" or "Down"

		public override string Name => "Opening Range Breakout";
		
		public override string Description => 
			"Monitors opening range and opens 0DTE credit spreads at 12:00 PM on full trading days based on breakout conditions with SMA(20) filter and capital allocation limits";

		public override void OnInitialize()
		{
			SmartLog("ORBTemplate.OnInitialize() starting...");
			
			// Configure with ORB-specific settings
			try
			{
				Configure<ORBConfig>();
				_config = (ORBConfig)Config;
				SmartLog("Configuration loaded successfully");
			}
			catch (Exception ex)
			{
				SmartError($"Failed to load configuration: {ex.Message}");
				throw;
			}
			
			// Setup underlying and options
			SmartLog($"Setting up {_config.UnderlyingSymbol} for ORB trading");
			
			// For 0DTE we need options expiring today, so use 0-1 day range
			// Use configurable resolution from config (Minute for intraday, Daily for EOD)
			var dataResolution = _config.GetDataResolution();
			SmartLog($"Using data resolution: {dataResolution} (from config: {_config.DataResolution})");
			
			(_underlying, _optionSymbol) = SetupOptionsForSymbol(_config.UnderlyingSymbol, 
				strikeRange: 50, minDTE: 0, maxDTE: 1, resolution: dataResolution);
			
			SmartLog($"Successfully added {_underlying} with options chain {_optionSymbol}");
			
			// Initialize SMA(20) indicator for trend filter
			if (_config.UseSmaTwentyFilter)
			{
				_sma20 = new SimpleMovingAverage(20);
				SmartLog("SMA(20) indicator initialized for trend filtering");
			}
			
			// Slippage is now handled globally in Main.cs CompleteSecurityInitializer using VolumeShareSlippageModel
			
			// Strategy will execute during OnExecute when conditions are met
			SmartLog($"Strategy will check for entry conditions at {_config.EntryTime} during regular OnExecute calls");
			
			SmartLog($"ORB Strategy initialized:");
			SmartLog($"  Range Period: {_config.RangePeriodMinutes} minutes");
			SmartLog($"  Min Range Width: {_config.MinRangeWidthPercent:P1}");
			SmartLog($"  Spread Width: ${_config.SpreadWidth}");
			SmartLog($"  Entry Time: {_config.EntryTime} (on full trading days only)");
			SmartLog($"  Contract Size: {_config.ContractSize}");
			SmartLog($"  SMA(20) Filter: {(_config.UseSmaTwentyFilter ? "Enabled" : "Disabled")}");
			SmartLog($"  Capital Allocation: ${_config.CapitalAllocation:N0}");
			SmartLog($"  Skip FOMC Days: {(_config.SkipFomcDays ? "Yes" : "No")}");
		}

		protected override void OnPreExecuteAlways(Slice slice)
		{
			// Ensure daily tracking and opening range collection run before central gating
			if (slice.Time.Date > _lastTradingDay)
			{
				ResetDailyTracking(slice);
				_lastTradingDay = slice.Time.Date;
			}
			// Build opening range during establishment window
			var currentPricePre = Securities[_underlying].Price;
			if (currentPricePre > 0)
			{
				var currentTime = slice.Time;
				var marketOpen = currentTime.Date.AddHours(9).AddMinutes(30);
				if (!_rangeEstablished && currentTime >= marketOpen && currentTime <= _rangeEndTime)
				{
					UpdateOpeningRange(currentPricePre, currentTime);
				}
			}
		}

		protected override void OnExecute(Slice slice)
		{
			// Get current price
			var currentPrice = Securities[_underlying].Price;
			if (currentPrice <= 0) return;
			
			var currentTime = slice.Time;
			var marketOpen = currentTime.Date.AddHours(9).AddMinutes(30);
			
			// Track day-long price movement after range is established
			if (_rangeEstablished)
			{
				UpdateDayTracking(currentPrice, currentTime);
			}
			
			// Entry timing based on mode
			if (ShouldEvaluateEntry(currentTime))
			{
				_entryEvaluatedToday = true;
				TryExecuteORBWithSlice(slice);
			}
		}
		
		private bool TryParseTime(string hhmm, DateTime date, out DateTime resolved)
		{
			resolved = default;
			if (string.IsNullOrWhiteSpace(hhmm)) return false;
			var parts = hhmm.Split(':');
			if (parts.Length < 2) return false;
			if (!int.TryParse(parts[0], out var h)) return false;
			if (!int.TryParse(parts[1], out var m)) return false;
			if (h < 0 || h > 23 || m < 0 || m > 59) return false;
			resolved = date.AddHours(h).AddMinutes(m);
			return true;
		}

		private bool ShouldEvaluateEntry(DateTime currentTime)
		{
			// Always require range to be valid and position not opened
			if (!_rangeEstablished || !_validRangeWidth || _positionOpenedToday) return false;
			
			var mode = (_config.EntryEvaluationMode ?? "ExactTime").Trim();
			if (mode.Equals("ExactTime", StringComparison.OrdinalIgnoreCase))
			{
				if (_entryEvaluatedToday) return false;
				if (TryParseTime(_config.EntryTime, currentTime.Date, out var exact))
					return currentTime.Hour == exact.Hour && currentTime.Minute == exact.Minute;
				return false;
			}
			else if (mode.Equals("Window", StringComparison.OrdinalIgnoreCase))
			{
				// Central gating enforces the actual time window; here we only ensure range/position conditions
				return true;
			}
			else if (mode.Equals("AllDay", StringComparison.OrdinalIgnoreCase))
			{
				// Central trading hours gating already applies; allow evaluation all day within trading hours
				return true;
			}
			
			return false;
		}
		
		private void ResetDailyTracking(Slice slice)
		{
			SmartLog($"=== Resetting daily tracking for {slice.Time.Date:yyyy-MM-dd} ===");
			
			_orbHigh = decimal.MinValue;
			_orbLow = decimal.MaxValue;
			_openingPrice = 0;
			_rangeEstablished = false;
			_validRangeWidth = false;
			
			_dayHighExceededORB = false;
			_dayLowBrokeORB = false;
			_dayHigh = decimal.MinValue;
			_dayLow = decimal.MaxValue;
			
			_positionOpenedToday = false;
			_entryEvaluatedToday = false;
			_firstBreakoutTime = null;
			_firstBreakoutDirection = null;
			
			// Set range end time
			_rangeEndTime = slice.Time.Date.AddHours(9).AddMinutes(30).AddMinutes(_config.RangePeriodMinutes);
		}
		
		private void UpdateOpeningRange(decimal price, DateTime time)
		{
			// Capture opening price
			if (_openingPrice == 0)
			{
				_openingPrice = price;
				SmartLog($"DIAGNOSTIC: Opening price captured: ${_openingPrice:F2} at {time:HH:mm:ss}");
			}
			
			// Update range high/low
			if (price > _orbHigh || _orbHigh == decimal.MinValue)
			{
				_orbHigh = price;
			}
			if (price < _orbLow || _orbLow == decimal.MaxValue)
			{
				_orbLow = price;
			}
			
			// Check if range period is complete
			if (time >= _rangeEndTime && !_rangeEstablished)
			{
				_rangeEstablished = true;
				var rangeWidth = _orbHigh - _orbLow;
				var rangeWidthPercent = rangeWidth / _openingPrice * 100;
				
				_validRangeWidth = rangeWidthPercent >= _config.MinRangeWidthPercent;
				
				SmartLog("DIAGNOSTIC: Opening Range Established:");
				SmartLog($"  High: ${_orbHigh:F2}");
				SmartLog($"  Low: ${_orbLow:F2}");
				SmartLog($"  Width: ${rangeWidth:F2} ({rangeWidthPercent:F2}%)");
				SmartLog($"  Valid: {(_validRangeWidth ? "YES" : "NO - Too narrow")}");
				SmartLog($"  Required Width: {_config.MinRangeWidthPercent:F2}%");
				
				// Initialize day tracking with opening range values
				_dayHigh = _orbHigh;
				_dayLow = _orbLow;
			}
		}
		
		private void UpdateDayTracking(decimal price, DateTime currentTime)
		{
			// Update day high/low
			if (price > _dayHigh)
			{
				_dayHigh = price;
				
				// Check if exceeded ORB high
				if (price > _orbHigh && !_dayHighExceededORB)
				{
					_dayHighExceededORB = true;
					if (_firstBreakoutTime == null)
					{
						_firstBreakoutTime = currentTime;
						_firstBreakoutDirection = "Up";
					}
					SmartLog($"DIAGNOSTIC: Day high EXCEEDED ORB high at ${price:F2} > ${_orbHigh:F2}");
				}
			}
			
			if (price < _dayLow)
			{
				_dayLow = price;
				
				// Check if broke below ORB low
				if (price < _orbLow && !_dayLowBrokeORB)
				{
					_dayLowBrokeORB = true;
					if (_firstBreakoutTime == null)
					{
						_firstBreakoutTime = currentTime;
						_firstBreakoutDirection = "Down";
					}
					SmartLog($"DIAGNOSTIC: Day low BROKE below ORB low at ${price:F2} < ${_orbLow:F2}");
				}
			}
		}
		
		private void TryExecuteORBWithSlice(Slice slice)
		{
			SmartLog($"DIAGNOSTIC: === ORB Strategy Execution at {slice.Time:HH:mm:ss} ===");
			SmartLog($"DIAGNOSTIC: Range established: {_rangeEstablished}, Valid width: {_validRangeWidth}");
			
			// Check FOMC day skip condition
			if (_config.SkipFomcDays && ORBConfig.FomcDates2024_2025.Contains(slice.Time.Date))
			{
				SmartLog($"DIAGNOSTIC: Skipping trading on FOMC day: {slice.Time.Date:yyyy-MM-dd}");
				return;
			}
			SmartLog($"DIAGNOSTIC: Not an FOMC day - proceeding with strategy evaluation");
			
			// Check capital allocation limits
			var availableCapital = GetAvailableCapital();
			if (availableCapital <= 0)
			{
				SmartLog($"Capital allocation limit reached. Available: ${availableCapital:F2}");
				return;
			}
			
			// Check if this is a full trading day by verifying market close time
			var todayMarketHours = Algorithm.Securities[_underlying].Exchange.Hours.GetMarketHours(slice.Time.Date);
			
			// Check if market is open for full day by verifying it closes at normal time or later
			if (todayMarketHours.IsClosedAllDay)
			{
				SmartLog($"Market closed all day, skipping ORB strategy");
				return;
			}
			
			// For simplicity, assume if market is open and we have data, it's a full trading day
			SmartLog($"Full trading day assumed - ORB strategy proceeding");
			
			// Determine base entry conditions
			var callSpreadBaseCondition = _dayLowBrokeORB && !_dayHighExceededORB;
			var putSpreadBaseCondition = _dayHighExceededORB && !_dayLowBrokeORB;
			
			// Resolve when both sides triggered
			if (_dayHighExceededORB && _dayLowBrokeORB)
			{
				if (_config.FirstBreakout)
				{
					if (_firstBreakoutDirection == "Up")
					{
						// Favor put spread
						callSpreadBaseCondition = false;
						putSpreadBaseCondition = true;
						SmartLog($"DIAGNOSTIC: Both sides triggered; FirstBreakout=true, first was Up -> prefer PUT spread");
					}
					else if (_firstBreakoutDirection == "Down")
					{
						callSpreadBaseCondition = true;
						putSpreadBaseCondition = false;
						SmartLog($"DIAGNOSTIC: Both sides triggered; FirstBreakout=true, first was Down -> prefer CALL spread");
					}
					else
					{
						SmartLog("DIAGNOSTIC: Both sides triggered; unknown first direction, skipping");
						return;
					}
				}
				else
				{
					SmartLog("DIAGNOSTIC: Both sides triggered; FirstBreakout=false, skipping entry");
					return;
				}
			}
			
			// Get current price and update SMA
			var currentPrice = Algorithm.Securities[_underlying].Price;
			
			// Update SMA manually if enabled
			if (_config.UseSmaTwentyFilter && _sma20 != null)
			{
				_sma20.Update(slice.Time, currentPrice);
			}
			
			// SMA filter for call spreads only
			var smaFilterPassed = !_config.UseSmaTwentyFilter || _sma20 == null || !_sma20.IsReady || currentPrice < _sma20.Current.Value;
			
			// Apply SMA filter to call spread condition
			var shouldOpenCallSpread = callSpreadBaseCondition && smaFilterPassed;
			var shouldOpenPutSpread = putSpreadBaseCondition; // No SMA filter for put spreads
			
			SmartLog($"DIAGNOSTIC: Entry Analysis:");
			SmartLog($"  Day Low Broke ORB: {_dayLowBrokeORB}");
			SmartLog($"  Day High Exceeded ORB: {_dayHighExceededORB}");
			SmartLog($"  Current Price: ${currentPrice:F2}");
			if (_config.UseSmaTwentyFilter && _sma20?.IsReady == true)
			{
				SmartLog($"  SMA(20): ${_sma20.Current.Value:F2}");
				SmartLog($"  Price < SMA(20): {(currentPrice < _sma20.Current.Value ? "YES" : "NO")}");
			}
			SmartLog($"  SMA Filter Passed: {smaFilterPassed}");
			SmartLog($"  Available Capital: ${availableCapital:F2}");
			SmartLog($"  Call Spread Base Condition: {callSpreadBaseCondition}");
			SmartLog($"  Put Spread Base Condition: {putSpreadBaseCondition}");
			SmartLog($"  Should Open Call Spread (with SMA filter): {shouldOpenCallSpread}");
			SmartLog($"  Should Open Put Spread (no SMA filter): {shouldOpenPutSpread}");
			
			// Check if any spread conditions are met
			if (!shouldOpenCallSpread && !shouldOpenPutSpread)
			{
				SmartLog("DIAGNOSTIC: No entry conditions met - neither call nor put spread criteria satisfied");
				return;
			}
			
			// Get option chain from slice (QC-standard approach)
			if (!slice.OptionChains.TryGetValue(_optionSymbol, out var optionChain))
			{
				SmartLog($"No option chain available in slice for symbol {_optionSymbol}");
				
				// Log what option chains ARE available for debugging
				if (slice.OptionChains.Count > 0)
				{
					SmartLog($"Available option chains in slice: {string.Join(", ", slice.OptionChains.Keys)}");
				}
				else
				{
					SmartLog("No option chains available in slice at all");
				}
				return;
			}
			
			if (!optionChain.Any())
			{
				SmartLog("Option chain is empty - no contracts available");
				return;
			}
			
			SmartLog($"SUCCESS: Found option chain with {optionChain.Count()} contracts at {Algorithm.Time}");
			
			// Log available expiration dates for debugging
			var expirations = optionChain.Select(x => x.Expiry.Date).Distinct().OrderBy(x => x).ToList();
			SmartLog($"Available expirations: {string.Join(", ", expirations.Select(x => x.ToString("yyyy-MM-dd")))}");
			
			var currentDate = Algorithm.Time.Date;
			SmartLog($"Looking for 0DTE options expiring on: {currentDate:yyyy-MM-dd}");
			
			var todayOptions = optionChain.Where(x => x.Expiry.Date == currentDate).ToList();
			SmartLog($"Options expiring today ({currentDate:yyyy-MM-dd}): {todayOptions.Count}");
			
			if (todayOptions.Count == 0)
			{
				if (_config.StrictZeroDteOnly)
				{
					SmartLog("StrictZeroDteOnly is true and no 0DTE options found - skipping entry");
					return;
				}
				SmartLog("No options expiring today - trying nearest expiration");
				var nearestExpiry = expirations.FirstOrDefault(x => x >= currentDate);
				if (nearestExpiry != default)
				{
					SmartLog($"Using nearest expiration: {nearestExpiry:yyyy-MM-dd}");
					todayOptions = optionChain.Where(x => x.Expiry.Date == nearestExpiry).ToList();
					SmartLog($"Options with nearest expiration: {todayOptions.Count}");
				}
			}
			
			// Execute spreads independently - call spread first (with SMA filter)
			if (shouldOpenCallSpread)
			{
				SmartLog($"DIAGNOSTIC: Attempting to execute CALL SPREAD (with SMA filter)");
				ExecuteCallSpread(todayOptions, availableCapital);
				_positionOpenedToday = true;
				SmartLog($"DIAGNOSTIC: Call spread executed, position opened flag set to true");
				return;
			}
			
			// Execute put spread (no SMA filter)
			if (shouldOpenPutSpread)
			{
				SmartLog($"DIAGNOSTIC: Attempting to execute PUT SPREAD (no SMA filter)");
				ExecutePutSpread(todayOptions, availableCapital);
				_positionOpenedToday = true;
				SmartLog($"DIAGNOSTIC: Put spread executed, position opened flag set to true");
				return;
			}
			
			SmartLog($"DIAGNOSTIC: This should not happen - no spread executed despite conditions being met");
		}
		
		private decimal GetAvailableCapital()
		{
			// Calculate current SPX position value using Algorithm.Portfolio
			var currentPositionValue = Algorithm.Portfolio
				.Where(kvp => kvp.Key.Underlying?.Value == "SPX")
				.Sum(kvp => Math.Abs(kvp.Value.HoldingsValue));
			
			return Math.Max(0, _config.CapitalAllocation - currentPositionValue);
		}
		
		private int CalculateContractSize(decimal availableCapital)
		{
			// Calculate max risk per contract (spread width * 100)
			var maxRiskPerContract = _config.SpreadWidth * 100;
			
			// Calculate max contracts based on available capital
			var maxContractsByCapital = (int)Math.Floor(availableCapital / maxRiskPerContract);
			
			// Use the minimum of configured size and capital-limited size
			return Math.Min(_config.ContractSize, maxContractsByCapital);
		}
		
		private void ExecuteCallSpread(IEnumerable<OptionContract> chain, decimal availableCapital)
		{
			SmartLog("Executing CALL SPREAD (bearish position)...");
			
			var underlyingPrice = Securities[_underlying].Price;
			var currentDate = Algorithm.Time.Date;
			var symbol = _underlying.Value;
			
			// Get the best expiration (preferably 0DTE, otherwise nearest if allowed)
			var expirations = chain.Select(x => x.Expiry.Date).Distinct().OrderBy(x => x).ToList();
			var targetExpiry = expirations.FirstOrDefault(x => x == currentDate);
			if (targetExpiry == default)
			{
				if (_config.StrictZeroDteOnly)
				{
					SmartLog("StrictZeroDteOnly is true and no 0DTE (calls) - skipping");
					return;
				}
				targetExpiry = expirations.FirstOrDefault(x => x >= currentDate);
				SmartLog($"No 0DTE options available, using nearest expiration: {targetExpiry:yyyy-MM-dd}");
			}
			else
			{
				SmartLog($"Using 0DTE options expiring: {targetExpiry:yyyy-MM-dd}");
			}
			
			// Filter for calls with target expiration
			var calls = chain.Where(x => 
				x.Right == OptionRight.Call &&
				x.Expiry.Date == targetExpiry &&
				x.BidPrice > 0 && x.AskPrice > 0)
				.OrderBy(x => x.Strike)
				.ToList();
			
			if (calls.Count < 2)
			{
				SmartLog($"Not enough calls available for {targetExpiry:yyyy-MM-dd}: {calls.Count}");
				return;
			}
			
			// Determine short call strike threshold using unified basis
			decimal callThreshold;
			if (string.Equals(_config.ShortStrikeBasis, "ORB", StringComparison.OrdinalIgnoreCase))
			{
				if (!_rangeEstablished)
				{
					SmartLog("ShortStrikeBasis=ORB but range not established - skipping");
					return;
				}
				callThreshold = _orbHigh + _config.MinStrikeOffset;
				SmartLog($"Call threshold by ORB: {_orbHigh:F2} + {_config.MinStrikeOffset:F2} => {callThreshold:F2}");
			}
			else
			{
				callThreshold = underlyingPrice + _config.MinStrikeOffset;
				SmartLog($"Call threshold by Underlying: {underlyingPrice:F2} + {_config.MinStrikeOffset:F2} => {callThreshold:F2}");
			}
			
			var shortCall = calls.FirstOrDefault(c => c.Strike >= callThreshold);
			if (shortCall == null)
			{
				SmartLog("Could not find suitable short call strike");
				return;
			}
			
			// Determine long call strike aiming for configured spread width (ensure >= width)
			var increment = CoreAlgo.Architecture.QC.Helpers.MultiAssetHelper.GetStrikeIncrement(symbol);
			var targetLongStrike = shortCall.Strike + _config.SpreadWidth;
			targetLongStrike = Math.Ceiling(targetLongStrike / increment) * increment;
			var longCall = calls.FirstOrDefault(c => c.Strike >= targetLongStrike);
			if (longCall == null)
			{
				SmartLog($"Could not find long call at or beyond target strike ${targetLongStrike:F2}");
				return;
			}
			
			// Calculate contract size based on available capital
			var contractSize = CalculateContractSize(availableCapital);
			
			if (contractSize <= 0)
			{
				SmartLog("Insufficient capital for call spread - skipping");
				return;
			}
			
			var legs = new List<Leg>
			{
				Leg.Create(shortCall.Symbol, -1),
				Leg.Create(longCall.Symbol, 1)
			};
			
			var tickets = ComboMarketOrder(legs, contractSize, tag: "ORB Call Spread");
			
			SmartLog($"CALL SPREAD OPENED (ComboOrder):");
			SmartLog($"  Contracts: {contractSize}");
			SmartLog($"  Short Call: {shortCall.Strike} @ ${shortCall.BidPrice:F2}");
			SmartLog($"  Long Call: {longCall.Strike} @ ${longCall.AskPrice:F2}");
			SmartLog($"  Credit: ${(shortCall.BidPrice - longCall.AskPrice) * 100 * contractSize:F2}");
			SmartLog($"  Max Risk: ${_config.SpreadWidth * 100 * contractSize:F2}");
			
			if (tickets != null && tickets.Any())
			{
				SmartLog($"  Combo Order Status: {tickets.Count} tickets created");
				foreach (var ticket in tickets.Take(2))
				{
					SmartLog($"    Ticket {ticket.OrderId}: {ticket.Symbol.Value} x{ticket.Quantity} - {ticket.Status}");
				}
			}
			else
			{
				SmartLog($"  [ERROR] Combo order failed - no tickets returned");
			}
			
			_positionOpenedToday = true;
		}
		
		private void ExecutePutSpread(IEnumerable<OptionContract> chain, decimal availableCapital)
		{
			SmartLog("Executing PUT SPREAD (bullish position)...");
			
			var underlyingPrice = Securities[_underlying].Price;
			var currentDate = Algorithm.Time.Date;
			var symbol = _underlying.Value;
			
			// Get the best expiration (preferably 0DTE, otherwise nearest if allowed)
			var expirations = chain.Select(x => x.Expiry.Date).Distinct().OrderBy(x => x).ToList();
			var targetExpiry = expirations.FirstOrDefault(x => x == currentDate);
			if (targetExpiry == default)
			{
				if (_config.StrictZeroDteOnly)
				{
					SmartLog("StrictZeroDteOnly is true and no 0DTE (puts) - skipping");
					return;
				}
				targetExpiry = expirations.FirstOrDefault(x => x >= currentDate);
				SmartLog($"No 0DTE options available, using nearest expiration: {targetExpiry:yyyy-MM-dd}");
			}
			else
			{
				SmartLog($"Using 0DTE options expiring: {targetExpiry:yyyy-MM-dd}");
			}
			
			// Filter for puts with target expiration
			var puts = chain.Where(x => 
				x.Right == OptionRight.Put &&
				x.Expiry.Date == targetExpiry &&
				x.BidPrice > 0 && x.AskPrice > 0)
				.OrderByDescending(x => x.Strike)
				.ToList();
			
			if (puts.Count < 2)
			{
				SmartLog($"Not enough puts available for {targetExpiry:yyyy-MM-dd}: {puts.Count}");
				return;
			}
			
			// Determine short put strike threshold using unified basis
			decimal putThreshold;
			if (string.Equals(_config.ShortStrikeBasis, "ORB", StringComparison.OrdinalIgnoreCase))
			{
				if (!_rangeEstablished)
				{
					SmartLog("ShortStrikeBasis=ORB but range not established - skipping");
					return;
				}
				putThreshold = _orbLow - _config.MinStrikeOffset;
				SmartLog($"Put threshold by ORB: {_orbLow:F2} - {_config.MinStrikeOffset:F2} => {putThreshold:F2}");
			}
			else
			{
				putThreshold = underlyingPrice - _config.MinStrikeOffset;
				SmartLog($"Put threshold by Underlying: {underlyingPrice:F2} - {_config.MinStrikeOffset:F2} => {putThreshold:F2}");
			}
			
			var shortPut = puts.FirstOrDefault(p => p.Strike <= putThreshold);
			if (shortPut == null)
			{
				SmartLog("Could not find suitable short put strike");
				return;
			}
			
			// Determine long put strike aiming for configured spread width (ensure >= width)
			var increment = CoreAlgo.Architecture.QC.Helpers.MultiAssetHelper.GetStrikeIncrement(symbol);
			var targetLongStrike = shortPut.Strike - _config.SpreadWidth;
			targetLongStrike = Math.Floor(targetLongStrike / increment) * increment;
			var longPut = puts.FirstOrDefault(p => p.Strike <= targetLongStrike);
			if (longPut == null)
			{
				SmartLog($"Could not find long put at or beyond target strike ${targetLongStrike:F2}");
				return;
			}
			
			// Calculate contract size based on available capital
			var contractSize = CalculateContractSize(availableCapital);
			
			if (contractSize <= 0)
			{
				SmartLog("Insufficient capital for put spread - skipping");
				return;
			}
			
			var legs = new List<Leg>
			{
				Leg.Create(shortPut.Symbol, -1),
				Leg.Create(longPut.Symbol, 1)
			};
			
			var tickets = ComboMarketOrder(legs, contractSize, tag: "ORB Put Spread");
			
			SmartLog($"PUT SPREAD OPENED (ComboOrder):");
			SmartLog($"  Contracts: {contractSize}");
			SmartLog($"  Short Put: {shortPut.Strike} @ ${shortPut.BidPrice:F2}");
			SmartLog($"  Long Put: {longPut.Strike} @ ${longPut.AskPrice:F2}");
			SmartLog($"  Credit: ${(shortPut.BidPrice - longPut.AskPrice) * 100 * contractSize:F2}");
			SmartLog($"  Max Risk: ${_config.SpreadWidth * 100 * contractSize:F2}");
			
			if (tickets != null && tickets.Any())
			{
				SmartLog($"  Combo Order Status: {tickets.Count} tickets created");
				foreach (var ticket in tickets.Take(2))
				{
					SmartLog($"    Ticket {ticket.OrderId}: {ticket.Symbol.Value} x{ticket.Quantity} - {ticket.Status}");
				}
			}
			else
			{
				SmartLog($"  [ERROR] Combo order failed - no tickets returned");
			}
			
			_positionOpenedToday = true;
		}
		
		protected override void OnGetPerformanceMetrics(System.Collections.Generic.Dictionary<string, double> metrics)
		{
			// Add ORB specific metrics
			metrics["ORBRangeValid"] = _validRangeWidth ? 1.0 : 0.0;
			metrics["DayHighExceededORB"] = _dayHighExceededORB ? 1.0 : 0.0;
			metrics["DayLowBrokeORB"] = _dayLowBrokeORB ? 1.0 : 0.0;
			metrics["PositionOpenedToday"] = _positionOpenedToday ? 1.0 : 0.0;
			metrics["AvailableCapital"] = (double)GetAvailableCapital();
			
			if (_config.UseSmaTwentyFilter && _sma20?.IsReady == true)
			{
				metrics["SMA20Value"] = (double)_sma20.Current.Value;
				metrics["PriceBelowSMA20"] = Algorithm.Securities[_underlying].Price < _sma20.Current.Value ? 1.0 : 0.0;
			}
		}
	}
}
using System;
using System.Linq;
using QuantConnect;
using QuantConnect.Data;
using QuantConnect.Orders;
using QuantConnect.Securities;
using QuantConnect.Securities.Option;
using CoreAlgo.Architecture.Core.Implementations;
using CoreAlgo.Architecture.Core.Models;
using CoreAlgo.Architecture.Core.Configuration;
using CoreAlgo.Architecture.QC.Helpers;

namespace CoreAlgo.Architecture.Core.Templates
{
    /// <summary>
    /// Template demonstrating the Entry/Exit Restriction Framework.
    /// Shows how to use EntryRestrictions and ExitRestrictions for controlled trading.
    /// This example trades both stocks and options with full restriction checks.
    /// </summary>
    public class RestrictedTradingTemplate : SimpleBaseStrategy
    {
        private RestrictedTradingConfig _config;
        private Symbol _underlying;
        private Symbol _optionSymbol;

        public override string Name => "Restricted Trading";
        
        public override string Description => 
            "Demonstrates Entry/Exit Restriction Framework with configurable trading rules for stocks and options";

        public override void OnInitialize()
        {
            SmartLog("RestrictedTradingTemplate.OnInitialize() starting...");
            
            try
            {
                Configure<RestrictedTradingConfig>();
                _config = (RestrictedTradingConfig)Config;
                SmartLog("Restricted Trading configuration loaded successfully");
            }
            catch (Exception ex)
            {
                SmartError($"Failed to load configuration: {ex.Message}");
                throw;
            }

            // Log restriction configuration
            SmartLog("=== Entry/Exit Restrictions Configuration ===");
            SmartLog($"Trading Hours: {_config.TradingStartTime} - {_config.TradingEndTime}");
            SmartLog($"Max Positions: {_config.MaxPositions}");
            SmartLog($"Allocation Per Position: {_config.AllocationPerPosition:P1}");
            SmartLog($"Profit Target: {_config.ProfitTarget:P1}");
            SmartLog($"Stop Loss: {_config.StopLoss:P1}");
            SmartLog($"Max Days in Trade: {_config.MaxDaysInTrade}");
            
            if (_config.TradeOptions)
            {
                SmartLog($"Options Entry Delta: {_config.EntryDeltaMin:F2} - {_config.EntryDeltaMax:F2}");
                SmartLog($"Options Exit Delta: {_config.ExitDelta:F2}");
                SmartLog($"Min Implied Volatility: {_config.MinImpliedVolatility:P1}");
            }

            // Add underlying using AssetManager (supports SPY/SPX/QQQ/ES)
            try
            {
                var asset = AssetManager.AddAsset(this, _config.UnderlyingSymbol, Resolution.Minute);
                _underlying = asset.Symbol;
                SmartLog($"Added underlying: {_underlying} (Type: {asset.Type})");
            }
            catch (Exception ex)
            {
                SmartError($"Failed to add underlying {_config.UnderlyingSymbol}: {ex.Message}");
                throw;
            }

            // Add options if configured
            if (_config.TradeOptions)
            {
                try
                {
                    _optionSymbol = AssetManager.AddOptionsChain(this, Securities[_underlying], Resolution.Minute);
                    
                    // Configure option filter based on our entry delta requirements
                    var option = Securities[_optionSymbol] as Option;
                    option?.SetFilter(universe => universe
                        .IncludeWeeklys()
                        .Strikes(-10, 10)
                        .Expiration(7, 45));
                    
                    SmartLog($"Added options chain: {_optionSymbol}");
                }
                catch (Exception ex)
                {
                    SmartWarn($"Could not add options for {_underlying}: {ex.Message}");
                    _config.TradeOptions = false; // Disable options trading
                }
            }

            SmartLog("Restricted Trading strategy initialized successfully!");
        }

        protected override bool OnShouldExecuteTrade(Slice slice, out string blockReason)
        {
            blockReason = "";
            if (_underlying == default)
            {
                blockReason = "Underlying not initialized";
                return false;
            }

            // Delegate to EntryRestrictions; base will already have checked hours/margin/positions
            if (!EntryRestrictions.CanEnterPosition(_underlying, slice, out blockReason))
            {
                return false;
            }
            return true;
        }

        protected override void OnExecute(Slice slice)
        {
            // Then, look for new entry opportunities
            CheckEntryOpportunities(slice);
        }

        // Centralized exits are handled by base CheckExitConditions(); remove template override

        private void CheckEntryOpportunities(Slice slice)
        {
            // EntryRestrictions already validated in OnShouldExecuteTrade for the underlying;
            // still observe options/stock data availability below.

            // Decide whether to trade stocks or options
            if (_config.TradeOptions && slice.OptionChains.ContainsKey(_optionSymbol))
            {
                CheckOptionEntry(slice);
            }
            else if (slice.Bars.ContainsKey(_underlying))
            {
                CheckStockEntry(slice);
            }
        }

        private void CheckStockEntry(Slice slice)
        {
            var bar = slice.Bars[_underlying];
            var price = bar.Close;

            // Simple momentum signal for demonstration
            if (IsStockEntrySignal(_underlying, price, slice.Time))
            {
                // Use PositionSizer to calculate quantity
                var quantity = PositionSizer.CalculateSafeQuantity(
                    Portfolio, 
                    _config.AllocationPerPosition, 
                    price,
                    safetyBuffer: 0.05m // 5% safety buffer
                );

                if (quantity > 0)
                {
                    SmartLog($"=== STOCK ENTRY SIGNAL ===");
                    SmartLog($"  Symbol: {_underlying}");
                    SmartLog($"  Price: ${price:F2}");
                    SmartLog($"  Quantity: {quantity}");
                    SmartLog($"  Position Value: ${quantity * price:F2}");

                    // Route through wrapper (SmartOrderManager-aware)
                    var ticket = MarketOrder(_underlying, (int)quantity, tag: "Entry Stock");
                    if (ticket != null)
                    {
                        SmartLog($"[OK] Entry order placed: {ticket.Status}");
                        ExitRestrictions.RecordPositionEntry(_underlying, slice.Time);
                    }
                    else
                    {
                        SmartError("[FAILED] Entry order blocked or null ticket returned");
                    }
                }
            }
        }

        private void CheckOptionEntry(Slice slice)
        {
            var chain = slice.OptionChains[_optionSymbol];
            if (!chain.Any())
                return;

            // Filter for puts (cash secured put strategy)
            var puts = chain.Where(x => x.Right == OptionRight.Put);
            
            // Find candidates that meet our entry restrictions
            var candidates = puts
                .Where(contract => EntryRestrictions.CanEnterOptionPosition(contract, out _))
                .OrderBy(x => Math.Abs(x.Greeks.Delta - (_config.EntryDeltaMin + _config.EntryDeltaMax) / 2))
                .Take(3);

            var selected = candidates.FirstOrDefault();
            if (selected != null)
            {
                // Additional IV check
                if (selected.ImpliedVolatility < _config.MinImpliedVolatility)
                {
                    Debug($"IV too low: {selected.ImpliedVolatility:F2} < {_config.MinImpliedVolatility:F2}");
                    return;
                }

                // Calculate safe position size for options
                var contracts = PositionSizer.CalculateSafeOptionsQuantity(
                    Portfolio,
                    _config.AllocationPerPosition,
                    selected.BidPrice,
                    safetyBuffer: 0.10m // 10% buffer for options
                );

                if (contracts > 0)
                {
                    SmartLog($"=== OPTION ENTRY SIGNAL ===");
                    SmartLog($"  Contract: {selected.Symbol}");
                    SmartLog($"  Strike: ${selected.Strike}");
                    SmartLog($"  Expiry: {selected.Expiry:yyyy-MM-dd}");
                    SmartLog($"  Delta: {selected.Greeks.Delta:F3}");
                    SmartLog($"  IV: {selected.ImpliedVolatility:P1}");
                    SmartLog($"  Premium: ${selected.BidPrice:F2} x {contracts} = ${selected.BidPrice * contracts * 100:F2}");

                    // Sell the put (negative quantity) via wrapper
                    var ticket = MarketOrder(selected.Symbol, -(int)contracts, tag: "Entry Short Put");
                    if (ticket != null)
                    {
                        SmartLog($"[OK] Option order placed: {ticket.Status}");
                        ExitRestrictions.RecordPositionEntry(selected.Symbol, slice.Time);
                    }
                    else
                    {
                        SmartError("[FAILED] Failed to enter option position (null ticket)");
                    }
                }
            }
        }

        private bool IsStockEntrySignal(Symbol symbol, decimal price, DateTime time)
        {
            try
            {
                // Simple RSI-based entry signal for demonstration
                var history = Algorithm.History(symbol, 14, Resolution.Daily);
                if (history.Count() < 14)
                    return false;

                var prices = history.Select(x => x.Close).ToArray();
                var rsi = CalculateRSI(prices);

                // Buy on oversold conditions
                return rsi < 35;
            }
            catch
            {
                return false;
            }
        }

        private double CalculateRSI(decimal[] prices, int period = 14)
        {
            if (prices.Length < period)
                return 50; // Neutral

            var gains = new decimal[prices.Length - 1];
            var losses = new decimal[prices.Length - 1];

            for (int i = 1; i < prices.Length; i++)
            {
                var change = prices[i] - prices[i - 1];
                gains[i - 1] = change > 0 ? change : 0;
                losses[i - 1] = change < 0 ? -change : 0;
            }

            var avgGain = gains.Skip(gains.Length - period).Average();
            var avgLoss = losses.Skip(losses.Length - period).Average();

            if (avgLoss == 0)
                return 100;

            var rs = avgGain / avgLoss;
            return 100 - (100 / (1 + (double)rs));
        }

        protected void LogDailySummary()
        {
            SmartLog("=== Daily Restriction Summary ===");
            
            // Log entry restriction status
            var entryStatus = EntryRestrictions.GetRestrictionStatus();
            SmartLog($"Entry Status:");
            SmartLog($"  Trading Hours Active: {entryStatus["TradingHoursActive"]}");
            SmartLog($"  Active Positions: {entryStatus["ActivePositions"]}/{entryStatus["MaxPositions"]}");
            SmartLog($"  Available Cash: ${entryStatus["AvailableCash"]:F2}");
            SmartLog($"  Required per Position: ${entryStatus["RequiredCapitalPerPosition"]:F2}");
            
            // Log exit status for all positions
            var exitStatuses = ExitRestrictions.GetAllPositionExitStatus();
            if (exitStatuses.Any())
            {
                SmartLog($"Exit Status:");
                foreach (var status in exitStatuses)
                {
                    SmartLog($"  {status.Symbol}: P/L={status.UnrealizedProfitPercent:P2}, " +
                             $"Days={status.DaysHeld:F1}, Urgency={status.ExitUrgency:F2}");
                }
            }
            
            SmartLog($"Portfolio Value: ${Portfolio.TotalPortfolioValue:N2}");
        }
    }
}
using System;
using System.Collections.Generic;
using System.Linq;
using QuantConnect;
using QuantConnect.Data;
using QuantConnect.Data.Fundamental;
using QuantConnect.Data.UniverseSelection;
using QuantConnect.Securities;
using CoreAlgo.Architecture.Core.Implementations;
using CoreAlgo.Architecture.Core.Models;
using CoreAlgo.Architecture.QC.Helpers;

namespace CoreAlgo.Architecture.Core.Templates
{
    /// <summary>
    /// Universe Selection strategy template that demonstrates the new QC helpers.
    /// Tests AssetManager, PositionSizer, OrderExtensions, and enhanced StrategyConfig.
    /// Can dynamically select stocks based on fundamental criteria and trade them with options.
    /// </summary>
    public class UniverseSelectionTemplate : SimpleBaseStrategy
    {
        private UniverseSelectionConfig _config;
        private Dictionary<Symbol, Symbol> _underlyingToOptions = new Dictionary<Symbol, Symbol>();
        private Dictionary<Symbol, DateTime> _lastTradeTime = new Dictionary<Symbol, DateTime>();
        private List<Symbol> _activeSymbols = new List<Symbol>();

        public override string Name => "Universe Selection";
        
        public override string Description => 
            "Dynamic universe selection strategy that demonstrates AssetManager, PositionSizer, and OrderExtensions with configurable fundamental filters";

        public override void OnInitialize()
        {
            SmartLog("UniverseSelectionTemplate.OnInitialize() starting...");
            
            try
            {
                Configure<UniverseSelectionConfig>();
                _config = (UniverseSelectionConfig)Config;
                SmartLog("Universe selection configuration loaded successfully");
            }
            catch (Exception ex)
            {
                SmartError($"Failed to load configuration: {ex.Message}");
                throw;
            }

            // Use the enhanced StrategyConfig parameters
            SmartLog($"Strategy Configuration:");
            SmartLog($"  UseUniverseSelection: {_config.UseUniverseSelection}");
            SmartLog($"  Manual Symbols: [{string.Join(", ", _config.Symbols)}]");
            SmartLog($"  Max Positions: {_config.MaxPositions}");
            SmartLog($"  Allocation Per Position: {_config.AllocationPerPosition:P1}");
            SmartLog($"  Min Market Cap: ${_config.MinMarketCap:N0}");
            SmartLog($"  Min Volume: {_config.MinDollarVolume:N0}");
            SmartLog($"  Trading Hours: {_config.TradingStartTime} - {_config.TradingEndTime}");
            SmartLog($"  P/E Ratio Range: {_config.MinPERatio} - {_config.MaxPERatio}");

            if (_config.UseUniverseSelection)
            {
                SmartLog("Setting up dynamic universe selection...");
                
                // Add universe selection using coarse data (simpler approach)
                Algorithm.AddUniverse(coarse =>
                {
                    return coarse
                        .Where(c => c.HasFundamentalData)
                        .Where(c => c.DollarVolume >= (double)_config.MinDollarVolume)
                        .Where(c => c.Price > 10) // Avoid penny stocks
                        .OrderByDescending(c => c.DollarVolume)
                        .Take(_config.MaxPositions)
                        .Select(c => c.Symbol);
                });
                
                SmartLog($"Universe selection configured with fundamental filters");
            }
            else
            {
                SmartLog("Using manual symbol list...");
                
                // Add manual symbols using AssetManager
                foreach (var symbolStr in _config.Symbols)
                {
                    try
                    {
                        SmartLog($"Adding asset: {symbolStr}");
                        
                        // Test AssetManager with different asset types
                        var underlying = AssetManager.AddAsset(this, symbolStr, Resolution.Minute);
                        _activeSymbols.Add(underlying.Symbol);
                        
                        SmartLog($"  Asset Type: {underlying.Type}");
                        SmartLog($"  Is Index: {AssetManager.IsIndex(symbolStr)}");
                        SmartLog($"  Is Future: {AssetManager.IsFuture(symbolStr)}");
                        SmartLog($"  Is Equity: {AssetManager.IsEquity(symbolStr)}");
                        
                        // Add options if configured
                        if (_config.TradeOptions)
                        {
                            try
                            {
                                var optionSymbol = AssetManager.AddOptionsChain(this, underlying, Resolution.Minute);
                                _underlyingToOptions[underlying.Symbol] = optionSymbol;
                                SmartLog($"  Options added: {optionSymbol}");
                            }
                            catch (Exception ex)
                            {
                                SmartLog($"  Could not add options for {symbolStr}: {ex.Message}");
                            }
                        }
                    }
                    catch (Exception ex)
                    {
                        SmartError($"Failed to add asset {symbolStr}: {ex.Message}");
                    }
                }
            }
            
            SmartLog($"Universe Selection initialized with {_activeSymbols.Count} initial symbols");
            SmartLog("Ready for dynamic trading!");
        }

        protected override bool OnShouldExecuteTrade(Slice slice, out string blockReason)
        {
            // Base handles hours/margin/max positions. Allow trading by default here.
            blockReason = "";
            return true;
        }

        public void HandleSecuritiesChanged(SecurityChanges changes)
        {
            SmartLog($"HandleSecuritiesChanged: +{changes.AddedSecurities.Count}, -{changes.RemovedSecurities.Count}");
            
            // Handle added securities from universe selection
            foreach (var security in changes.AddedSecurities)
            {
                if (!_activeSymbols.Contains(security.Symbol))
                {
                    _activeSymbols.Add(security.Symbol);
                    SmartLog($"Added to universe: {security.Symbol} (Type: {security.Type})");
                    
                    // Add options for new securities if configured
                    if (_config.TradeOptions && security.Type == SecurityType.Equity)
                    {
                        try
                        {
                            var optionSymbol = AssetManager.AddOptionsChain(this, security, Resolution.Minute);
                            _underlyingToOptions[security.Symbol] = optionSymbol;
                            SmartLog($"  Options added for {security.Symbol}: {optionSymbol}");
                        }
                        catch (Exception ex)
                        {
                            SmartLog($"  Could not add options for {security.Symbol}: {ex.Message}");
                        }
                    }
                }
            }
            
            // Handle removed securities
            foreach (var security in changes.RemovedSecurities)
            {
                if (_activeSymbols.Contains(security.Symbol))
                {
                    _activeSymbols.Remove(security.Symbol);
                    _underlyingToOptions.Remove(security.Symbol);
                    _lastTradeTime.Remove(security.Symbol);
                    SmartLog($"Removed from universe: {security.Symbol}");
                    
                    // Liquidate positions if we have any
                    if (Portfolio[security.Symbol].Invested)
                    {
                        SmartLog($"Liquidating position in removed security: {security.Symbol}");
                        var ticket = MarketOrder(security.Symbol, -(int)Portfolio[security.Symbol].Quantity, tag: "Universe Removal");
                        
                        if (ticket != null)
                        {
                            SmartLog($"Successfully liquidated {security.Symbol}: {ticket.Status}");
                        }
                        else
                        {
                            SmartError($"Failed to liquidate {security.Symbol}: null ticket");
                        }
                    }
                }
            }
        }

        protected override void OnExecute(Slice slice)
        {
            // Look for trading opportunities in our universe
            foreach (var symbol in _activeSymbols.Take(_config.MaxPositions))
            {
                // Skip if we already have a position or recently traded
                if (Portfolio[symbol].Invested)
                    continue;
                    
                if (_lastTradeTime.ContainsKey(symbol) && 
                    slice.Time - _lastTradeTime[symbol] < TimeSpan.FromMinutes(_config.MinutesBetwenTrades))
                    continue;

                // Check if we have price data
                if (!slice.Bars.ContainsKey(symbol))
                    continue;

                var bar = slice.Bars[symbol];
                var price = bar.Close;

                // Simple momentum strategy: buy if price is above recent average
                if (ShouldBuySymbol(symbol, price, slice.Time))
                {
                    TakePosition(symbol, price, slice.Time);
                    
                    // Limit to one trade per OnData call to avoid overtrading
                    break;
                }
            }
        }

        private bool ShouldBuySymbol(Symbol symbol, decimal price, DateTime time)
        {
            try
            {
                // Simple momentum check: price above 5-day average
                var history = Algorithm.History(symbol, 5, Resolution.Daily);
                if (history.Count() < 5)
                    return false;

                var averagePrice = history.Select(h => h.Close).Average();
                var momentum = (price - averagePrice) / averagePrice;

                // Buy if momentum is positive and reasonable
                return momentum > 0.02m && momentum < 0.10m; // 2-10% above average
            }
            catch (Exception ex)
            {
                SmartError($"Error checking buy signal for {symbol}: {ex.Message}");
                return false;
            }
        }

        private void TakePosition(Symbol symbol, decimal price, DateTime time)
        {
            try
            {
                // Use PositionSizer to calculate appropriate quantity
                var quantity = PositionSizer.CalculateQuantity(Portfolio, _config.AllocationPerPosition, price);
                
                if (quantity <= 0)
                {
                    SmartLog($"Calculated quantity <= 0 for {symbol}, skipping");
                    return;
                }

                SmartLog($"Taking position in {symbol}:");
                SmartLog($"  Price: ${price:F2}");
                SmartLog($"  Quantity: {quantity}");
                SmartLog($"  Allocation: {_config.AllocationPerPosition:P1}");
                SmartLog($"  Position Value: ${PositionSizer.CalculatePositionValue(quantity, price):F2}");

                // Use wrapper for order routing
                var ticket = MarketOrder(symbol, quantity, tag: "Entry Buy");
                
                if (ticket != null)
                {
                    _lastTradeTime[symbol] = time;
                    SmartLog($"[SUCCESS] Position taken in {symbol}: {ticket.Status}");
                    ExitRestrictions.RecordPositionEntry(symbol, time);
                }
                else
                {
                    SmartError($"[FAILED] Failed to take position in {symbol}: null ticket");
                }
            }
            catch (Exception ex)
            {
                SmartError($"Error taking position in {symbol}: {ex.Message}");
            }
        }

        // Remove template exit override; centralized exits handled by base
        // protected override void CheckExitConditions(Slice slice) { }

        private void ExitPosition(Symbol symbol, decimal quantity, string reason)
        {
            try
            {
                var ticket = MarketOrder(symbol, -(int)quantity, tag: reason);
                
                if (ticket != null)
                {
                    SmartLog($"[SUCCESS] Exited position in {symbol} ({reason}): {ticket.Status}");
                    ExitRestrictions.ClearPositionEntry(symbol);
                }
                else
                {
                    SmartError($"[FAILED] Failed to exit position in {symbol} ({reason}): null ticket");
                }
            }
            catch (Exception ex)
            {
                SmartError($"Error exiting position in {symbol}: {ex.Message}");
            }
        }

        public void PrintSummary()
        {
            SmartLog("=== Universe Selection Strategy Summary ===");
            SmartLog($"Final Portfolio Value: ${Portfolio.TotalPortfolioValue:F2}");
            SmartLog($"Total Trades: {Algorithm.Transactions.GetOrders().Count()}");
            SmartLog($"Active Symbols Tracked: {_activeSymbols.Count}");
            SmartLog($"Symbols with Options: {_underlyingToOptions.Count}");
            
            // Test final AssetManager capabilities
            SmartLog("=== AssetManager Test Results ===");
            var testSymbols = new[] { "SPY", "SPX", "QQQ", "VIX", "ES" };
            foreach (var symbol in testSymbols)
            {
                SmartLog($"{symbol}: Index={AssetManager.IsIndex(symbol)}, Future={AssetManager.IsFuture(symbol)}, Equity={AssetManager.IsEquity(symbol)}");
            }
        }
    }
}
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)
        {
            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}");
                    
                    if (useContinuousContract)
                    {
                        // Add future with continuous contract support
                        addedSecurity = context.Algorithm.AddFuture(upperSymbol, resolution, 
                            extendedMarketHours: false,
                            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);
                    }
                }
                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}"
            };
        }
    }
}
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 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
            };
        }
    }
}
#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;
        
        // 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()
        {
            // CRITICAL: Absolute minimal test - if this doesn't appear, our class isn't running
            Log("HELLO WORLD - COREALGO INITIALIZE CALLED");
            Error("HELLO WORLD - COREALGO INITIALIZE CALLED VIA ERROR");
            Debug("HELLO WORLD - COREALGO INITIALIZE CALLED VIA DEBUG");
            
            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);
                
                // Set warmup period to ensure indicators (especially SMA) have sufficient data
                // 30 days ensures SMA(20) is fully ready and provides buffer for market holidays
                var warmupDays = GetParameter("WarmupPeriodDays", 30);
                SetWarmUp(TimeSpan.FromDays(warmupDays));
                Log($"WARMUP: Set {warmupDays}-day warmup period for indicator initialization");
                
                // 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;
                Log($"UNIVERSE SETTINGS -> Resolution: {UniverseSettings.Resolution}, FillForward: {UniverseSettings.FillForward}, ExtendedMktHours: {UniverseSettings.ExtendedMarketHours}");
                Logger?.Info($"UniverseSettings configured. Resolution={UniverseSettings.Resolution}, FillForward={UniverseSettings.FillForward}, ExtendedMarketHours={UniverseSettings.ExtendedMarketHours}");
                
                Log("BASIC SETUP COMPLETED");
                
                // Initialize smart logger with debug logging
                Log("=== DEBUG: About to create QCLogger ===");
                var logLevel = GetParameter("LogLevel", 2); // 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");
                }
                
                _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");
                }
                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, post-warmup diagnostics for option chain visibility
            if (!_postWarmupSliceLogged)
            {
                var chainsCount = data.OptionChains.Count;
                var keys = chainsCount > 0 ? string.Join(", ", data.OptionChains.Keys) : "<none>";
                Logger?.Info($"POST-WARMUP: OptionChains.Count={chainsCount}; Keys=[{keys}] at {Time:yyyy-MM-dd HH:mm:ss}");
                _postWarmupSliceLogged = true;
            }
            
            // Only log option chain activity when chains are available
            if (data.OptionChains.Count > 0)
            {
                Logger.Info($"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);
            
            // Route to strategy's SmartOrderManager if available
            if (_strategy is SimpleBaseStrategy baseStrategy && baseStrategy.SmartOrderManager != null)
            {
                try
                {
                    baseStrategy.SmartOrderManager.OnOrderEvent(orderEvent);
                }
                catch (Exception ex)
                {
                    Logger.LogError(ex, $"Error processing order event {orderEvent.OrderId}");
                }
            }
            
            // Track order events for trade tracking system (like Python position tracking)
            if (_strategy is SimpleBaseStrategy strategy)
            {
                try
                {
                    // Track filled orders
                    if (orderEvent.Status == OrderStatus.Filled)
                    {
                        strategy.TrackOrderFilled(orderEvent);
                    }
                    // Track cancelled orders
                    else if (orderEvent.Status == OrderStatus.Canceled)
                    {
                        strategy.TrackOrderCancelled(orderEvent.OrderId.ToString());
                    }
                }
                catch (Exception ex)
                {
                    Logger.LogError(ex, $"Error tracking order event {orderEvent.OrderId}");
                }
            }
        }
        
        /// <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}");
            
            // 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);
            
            // Additional live mode notification
            if (LiveMode)
            {
                Log($"=== End of trading day for {symbol} - Live mode active ===");
            }
        }

        public override void OnEndOfAlgorithm()
        {
            // DEBUG: Immediate verification that OnEndOfAlgorithm() is called
            Log("=== DEBUG: OnEndOfAlgorithm() CALLED - IMMEDIATE CONFIRMATION ===");
            Error("=== DEBUG: OnEndOfAlgorithm() CALLED - VIA ERROR FOR VISIBILITY ===");
            
            // 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 all securities and applies backtesting configurations
        /// </summary>
        private void CompleteSecurityInitializer(Security security)
        {
            Logger?.Debug($"CompleteSecurityInitializer: {security.Symbol} ({security.Type})");
            
            // CRITICAL: Disable buying power on ALL 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
            #pragma warning disable CS0618 // Type or member is obsolete
            security.SetDataNormalizationMode(DataNormalizationMode.Raw);
            #pragma warning restore CS0618
            
            var lastPrices = GetLastKnownPrices(security);
            if (lastPrices != null && lastPrices.Any())
            {
                security.SetMarketPrice(lastPrices.First());
            }
            
            // Type-specific configurations for index options
            // Note: Option assignment model is handled at the strategy level
        }

    }
}
using System;
using System.Collections.Generic;
using System.Linq;
using System.Reflection;
using CoreAlgo.Architecture.Core.Attributes;
using CoreAlgo.Architecture.Core.Models;
using CoreAlgo.Architecture.Core.Services;

namespace TestUtils.ConfigGenerator
{
    /// <summary>
    /// Extracts configuration defaults from template classes using reflection.
    /// Reads [StrategyParameter] attributes to build JSON configuration for test.sh
    /// </summary>
    public static class ConfigExtractor
    {
        /// <summary>
        /// Extract template configuration defaults for a given strategy
        /// </summary>
        /// <param name="strategyName">Strategy name (e.g., MULTIASSETSPX)</param>
        /// <returns>Dictionary of parameter names to default values</returns>
        public static Dictionary<string, object> ExtractTemplateDefaults(string strategyName)
        {
            return ExtractTemplateDefaults(strategyName, null);
        }

        /// <summary>
        /// Extract template configuration defaults for a given strategy with optional config variant
        /// </summary>
        /// <param name="strategyName">Strategy name (e.g., MULTIASSETIRONCONDOR)</param>
        /// <param name="configVariant">Config variant name (e.g., MultiAssetSPX, MultiAssetEquity)</param>
        /// <returns>Dictionary of parameter names to default values</returns>
        public static Dictionary<string, object> ExtractTemplateDefaults(string strategyName, string configVariant)
        {
            var configType = GetConfigTypeForStrategy(strategyName, configVariant);
            if (configType == null)
            {
                var errorMessage = $"No configuration type found for strategy: {strategyName}";
                if (!string.IsNullOrEmpty(configVariant))
                {
                    errorMessage += $" with config variant: {configVariant}";
                }
                throw new ArgumentException(errorMessage);
            }

            Console.Error.WriteLine($"Using configuration type: {configType.Name}");

            // Create instance of the config class
            var configInstance = Activator.CreateInstance(configType);
            var parameters = new Dictionary<string, object>();

            // Get optimization parameters filter if available
            string[] optimizationParameters = null;
            try
            {
                var optimizationProperty = configType.GetProperty("OptimizationParameters");
                if (optimizationProperty != null)
                {
                    optimizationParameters = (string[])optimizationProperty.GetValue(configInstance);
                    Console.Error.WriteLine($"Found OptimizationParameters: [{string.Join(", ", optimizationParameters)}]");
                }
                else
                {
                    Console.Error.WriteLine("No OptimizationParameters property found - extracting all parameters");
                }
            }
            catch (Exception ex)
            {
                Console.Error.WriteLine($"Warning: Could not get OptimizationParameters: {ex.Message}");
            }

            // Extract all properties with [StrategyParameter] attributes
            var properties = configType.GetProperties(BindingFlags.Public | BindingFlags.Instance);
            
            foreach (var property in properties)
            {
                var attribute = property.GetCustomAttribute<StrategyParameterAttribute>();
                if (attribute != null)
                {
                    // Check if this parameter should be included in optimization
                    bool shouldInclude = optimizationParameters == null || 
                                       optimizationParameters.Contains(attribute.Name) ||
                                       attribute.Name == "Strategy"; // Always include Strategy parameter
                    
                    if (!shouldInclude)
                    {
                        Console.Error.WriteLine($"  Skipping {attribute.Name} (not in OptimizationParameters)");
                        continue;
                    }

                    try
                    {
                        var value = property.GetValue(configInstance);
                        
                        // Convert value to JSON-serializable format
                        var jsonValue = ConvertToJsonValue(value, property.PropertyType);
                        parameters[attribute.Name] = jsonValue;
                        
                        Console.Error.WriteLine($"  {attribute.Name} = {jsonValue} (from {property.Name})");
                    }
                    catch (Exception ex)
                    {
                        Console.Error.WriteLine($"  Warning: Could not extract {property.Name}: {ex.Message}");
                    }
                }
            }

            // Always ensure Strategy parameter is set
            parameters["Strategy"] = strategyName;

            return parameters;
        }

        /// <summary>
        /// Map strategy names to their corresponding configuration types using discovery
        /// </summary>
        private static Type GetConfigTypeForStrategy(string strategyName)
        {
            return GetConfigTypeForStrategy(strategyName, null);
        }

        /// <summary>
        /// Map strategy names to their corresponding configuration types with optional config variant
        /// </summary>
        /// <param name="strategyName">Strategy name (e.g., MULTIASSETIRONCONDOR)</param>
        /// <param name="configVariant">Config variant name (e.g., MultiAssetSPX, MultiAssetEquity)</param>
        /// <returns>Configuration Type</returns>
        private static Type GetConfigTypeForStrategy(string strategyName, string configVariant)
        {
            // If config variant is specified, look for exact config class name
            if (!string.IsNullOrEmpty(configVariant))
            {
                var configClassName = $"{configVariant}Config";
                Console.Error.WriteLine($"Looking for config variant: {configClassName}");
                
                // Look in all loaded assemblies, not just the executing assembly
                var assemblies = AppDomain.CurrentDomain.GetAssemblies();
                Type configType = null;
                
                foreach (var assembly in assemblies)
                {
                    try
                    {
                        configType = assembly.GetTypes()
                            .FirstOrDefault(t => t.Name == configClassName && 
                                               typeof(StrategyConfig).IsAssignableFrom(t));
                        if (configType != null)
                            break;
                    }
                    catch (ReflectionTypeLoadException)
                    {
                        // Ignore assemblies that can't be loaded
                        continue;
                    }
                }
                
                if (configType != null)
                {
                    Console.Error.WriteLine($"Found config variant: {configType.FullName}");
                    return configType;
                }
                else
                {
                    Console.Error.WriteLine($"Config variant not found: {configClassName}");
                    return null;
                }
            }
            
            // Fall back to standard strategy-based discovery
            return StrategyDiscovery.GetConfigType(strategyName);
        }

        /// <summary>
        /// Convert property values to JSON-serializable format
        /// </summary>
        private static object ConvertToJsonValue(object value, Type propertyType)
        {
            if (value == null)
                return null;

            // Handle arrays (especially string arrays like Symbols)
            if (propertyType.IsArray && propertyType.GetElementType() == typeof(string))
            {
                var stringArray = (string[])value;
                return string.Join(",", stringArray);
            }

            // Handle TimeSpan - convert to string format
            if (propertyType == typeof(TimeSpan))
            {
                var timeSpan = (TimeSpan)value;
                return timeSpan.ToString(@"hh\:mm\:ss");
            }

            // Handle DateTime - convert to string format
            if (propertyType == typeof(DateTime))
            {
                var dateTime = (DateTime)value;
                return dateTime.ToString("yyyy-MM-dd");
            }

            // Handle decimals - ensure proper formatting
            if (propertyType == typeof(decimal))
            {
                return value;
            }

            // Handle other numeric types
            if (propertyType == typeof(int) || propertyType == typeof(bool))
            {
                return value;
            }

            // Default: convert to string
            return value.ToString();
        }
    }
}
using System;
using System.Collections.Generic;
using System.Linq;
using Newtonsoft.Json;
using CoreAlgo.Architecture.Core.Services;

namespace TestUtils.ConfigGenerator
{
    /// <summary>
    /// Console tool to extract template configuration defaults for test.sh
    /// Usage: dotnet run --project TestUtils/ConfigGenerator -- MULTIASSETSPX
    /// Usage: dotnet run --project TestUtils/ConfigGenerator -- MULTIASSETIRONCONDOR --config=MultiAssetSPX
    /// Usage: dotnet run --project TestUtils/ConfigGenerator -- --list-strategies
    /// Output: JSON configuration parameters from template class or list of strategies
    /// </summary>
    class Program
    {
        static void Main(string[] args)
        {
            if (args.Length == 0)
            {
                Console.Error.WriteLine("Usage: ConfigGenerator <STRATEGY_NAME> [--config=CONFIG_VARIANT]");
                Console.Error.WriteLine("       ConfigGenerator --list-strategies");
                Console.Error.WriteLine("Examples:");
                Console.Error.WriteLine("  ConfigGenerator MULTIASSETSPX");
                Console.Error.WriteLine("  ConfigGenerator MULTIASSETIRONCONDOR --config=MultiAssetSPX");
                Console.Error.WriteLine("  ConfigGenerator MULTIASSETIRONCONDOR --config=MultiAssetEquity");
                Environment.Exit(1);
            }

            // Handle --list-strategies option
            if (args[0] == "--list-strategies")
            {
                try
                {
                    var strategies = StrategyDiscovery.GetAllStrategyNames().OrderBy(s => s).ToList();
                    foreach (var strategy in strategies)
                    {
                        Console.WriteLine(strategy.ToUpperInvariant());
                    }
                    Environment.Exit(0);
                }
                catch (Exception ex)
                {
                    Console.Error.WriteLine($"Error discovering strategies: {ex.Message}");
                    Environment.Exit(1);
                }
            }

            try
            {
                var strategy = args[0].ToUpperInvariant();
                string configVariant = null;
                
                // Parse optional --config parameter
                if (args.Length > 1)
                {
                    for (int i = 1; i < args.Length; i++)
                    {
                        if (args[i].StartsWith("--config="))
                        {
                            configVariant = args[i].Substring("--config=".Length);
                            break;
                        }
                    }
                }
                
                if (!string.IsNullOrEmpty(configVariant))
                {
                    Console.Error.WriteLine($"Extracting configuration for strategy: {strategy} with config variant: {configVariant}");
                }
                else
                {
                    Console.Error.WriteLine($"Extracting configuration for strategy: {strategy}");
                }
                
                var parameters = ConfigExtractor.ExtractTemplateDefaults(strategy, configVariant);
                
                if (parameters.Count == 0)
                {
                    Console.Error.WriteLine($"No configuration parameters found for strategy: {strategy}" + 
                        (!string.IsNullOrEmpty(configVariant) ? $" with config variant: {configVariant}" : ""));
                    Environment.Exit(1);
                }

                // Output clean JSON for shell consumption (stdout only)
                var json = JsonConvert.SerializeObject(parameters, Formatting.None);
                Console.WriteLine(json);
                
                Console.Error.WriteLine($"Successfully extracted {parameters.Count} parameters");
            }
            catch (Exception ex)
            {
                Console.Error.WriteLine($"Error extracting config for {args[0]}: {ex.Message}");
                Console.Error.WriteLine($"Stack trace: {ex.StackTrace}");
                Environment.Exit(1);
            }
        }
    }
}
using System;
using System.Linq;
using NUnit.Framework;
using QuantConnect;
using QuantConnect.Securities;
using CoreAlgo.Architecture.Core.Templates;
using CoreAlgo.Architecture.Core.Models;

namespace CoreAlgo.Tests
{
    [TestFixture]
    public class CashSecuredPutTemplateTests
    {
        private CashSecuredPutTemplate _strategy;

        [SetUp]
        public void SetUp()
        {
            _strategy = new CashSecuredPutTemplate();
        }

        [Test]
        public void Name_ReturnsCorrectValue()
        {
            // Act & Assert
            Assert.That(_strategy.Name, Is.EqualTo("Cash Secured Put"));
        }

        [Test]
        public void Description_ReturnsCorrectValue()
        {
            // Act & Assert
            Assert.That(_strategy.Description, Is.EqualTo("Income strategy that generates premium by selling put options backed by cash"));
        }

        [Test]
        public void CashSecuredPutConfig_DefaultValues_AreCorrect()
        {
            // Arrange & Act
            var config = new CashSecuredPutConfig();

            // Assert
            Assert.That(config.UnderlyingSymbol, Is.EqualTo("SPY"));
            Assert.That(config.PutStrikeOffset, Is.EqualTo(0.02m));
            Assert.That(config.MinDaysToExpiration, Is.EqualTo(1));
            Assert.That(config.MaxDaysToExpiration, Is.EqualTo(60));
            Assert.That(config.MinimumPremium, Is.EqualTo(0.25m));
            Assert.That(config.MaxActivePositions, Is.EqualTo(2));
            Assert.That(config.AcceptAssignment, Is.True);
        }

        [Test]
        public void CashSecuredPutConfig_Validate_WithValidValues_ReturnsNoErrors()
        {
            // Arrange
            var config = new CashSecuredPutConfig
            {
                PutStrikeOffset = 0.03m,
                MinDaysToExpiration = 7,
                MaxDaysToExpiration = 30,
                MinimumPremium = 0.50m,
                MaxActivePositions = 3
            };

            // Act
            var errors = config.Validate();

            // Assert
            Assert.That(errors.Length, Is.EqualTo(0));
        }

        [Test]
        public void CashSecuredPutConfig_Validate_WithNegativeStrikeOffset_ReturnsError()
        {
            // Arrange
            var config = new CashSecuredPutConfig
            {
                PutStrikeOffset = -0.01m // Invalid negative offset
            };

            // Act
            var errors = config.Validate();

            // Assert
            Assert.That(errors.Length, Is.GreaterThan(0));
            Assert.That(errors.Any(e => e.Contains("PutStrikeOffset")), Is.True);
        }

        [Test]
        public void CashSecuredPutConfig_Validate_WithInvalidDTERange_ReturnsError()
        {
            // Arrange
            var config = new CashSecuredPutConfig
            {
                MinDaysToExpiration = 45,
                MaxDaysToExpiration = 7 // Max < Min - invalid
            };

            // Act
            var errors = config.Validate();

            // Assert
            Assert.That(errors.Length, Is.GreaterThan(0));
            Assert.That(errors.Any(e => e.Contains("DTE")), Is.True);
        }

        [Test]
        public void CashSecuredPutConfig_Validate_WithNegativePremium_ReturnsError()
        {
            // Arrange
            var config = new CashSecuredPutConfig
            {
                MinimumPremium = -0.10m // Invalid negative premium
            };

            // Act
            var errors = config.Validate();

            // Assert
            Assert.That(errors.Length, Is.GreaterThan(0));
            Assert.That(errors.Any(e => e.Contains("MinimumPremium")), Is.True);
        }

        [Test]
        public void CashSecuredPutConfig_Validate_WithInvalidMaxPositions_ReturnsError()
        {
            // Arrange
            var config = new CashSecuredPutConfig
            {
                MaxActivePositions = 0 // Must be positive
            };

            // Act
            var errors = config.Validate();

            // Assert
            Assert.That(errors.Length, Is.GreaterThan(0));
            Assert.That(errors.Any(e => e.Contains("MaxActivePositions")), Is.True);
        }

        [Test]
        public void CashSecuredPutConfig_Validate_WithExcessiveStrikeOffset_ReturnsError()
        {
            // Arrange
            var config = new CashSecuredPutConfig
            {
                PutStrikeOffset = 0.25m // 25% offset - too high
            };

            // Act
            var errors = config.Validate();

            // Assert
            Assert.That(errors.Length, Is.GreaterThan(0));
            Assert.That(errors.Any(e => e.Contains("PutStrikeOffset")), Is.True);
        }

        [Test]
        public void CashSecuredPutConfig_ToString_ReturnsFormattedString()
        {
            // Arrange
            var config = new CashSecuredPutConfig();

            // Act
            var result = config.ToString();

            // Assert
            Assert.IsNotNull(result);
            Assert.That(result.Contains("CashSecuredPut"), Is.True);
            Assert.That(result.Contains("SPY"), Is.True);
            Assert.That(result.Contains("2.0%"), Is.True);
        }

        [Test]
        public void CashSecuredPutConfig_HasStrategyParameterAttributes()
        {
            // Arrange
            var config = new CashSecuredPutConfig();
            var type = config.GetType();

            // Act & Assert - Check that properties have StrategyParameterAttribute
            var putOffsetProperty = type.GetProperty("PutStrikeOffset");
            Assert.IsNotNull(putOffsetProperty);
            
            var minDteProperty = type.GetProperty("MinDaysToExpiration");
            Assert.IsNotNull(minDteProperty);
            
            var maxDteProperty = type.GetProperty("MaxDaysToExpiration");
            Assert.IsNotNull(maxDteProperty);
            
            var minPremiumProperty = type.GetProperty("MinimumPremium");
            Assert.IsNotNull(minPremiumProperty);
        }

        [Test]
        public void CashSecuredPutConfig_LoadFromParameters_UpdatesConfigCorrectly()
        {
            // This test would require a mock QCAlgorithm instance
            // For now, we'll test the parameter validation and structure
            
            // Arrange
            var config = new CashSecuredPutConfig();
            var originalOffset = config.PutStrikeOffset;

            // Act - manually set a new value
            config.PutStrikeOffset = 0.05m;

            // Assert
            Assert.That(config.PutStrikeOffset, Is.Not.EqualTo(originalOffset));
            Assert.That(config.PutStrikeOffset, Is.EqualTo(0.05m));
        }

        [Test]
        public void CashSecuredPutConfig_EdgeCases_HandleCorrectly()
        {
            // Test boundary values
            var config = new CashSecuredPutConfig
            {
                PutStrikeOffset = 0.01m,   // Minimum reasonable value
                MinDaysToExpiration = 1,    // Minimum DTE
                MaxDaysToExpiration = 365,  // Maximum reasonable DTE
                MinimumPremium = 0.01m,     // Very small premium
                MaxActivePositions = 1      // Single position
            };

            var errors = config.Validate();
            Assert.That(errors.Length, Is.EqualTo(0), "Boundary values should be valid");
        }

        [Test]
        public void CashSecuredPutConfig_MultipleValidationErrors_ReturnsAllErrors()
        {
            // Arrange
            var config = new CashSecuredPutConfig
            {
                PutStrikeOffset = -0.01m,      // Invalid
                MinDaysToExpiration = 90,      // Invalid (> Max)
                MaxDaysToExpiration = 7,       // Invalid (< Min)
                MinimumPremium = -0.10m,       // Invalid
                MaxActivePositions = 0         // Invalid
            };

            // Act
            var errors = config.Validate();

            // Assert
            Assert.That(errors.Length, Is.GreaterThan(2)); // Multiple validation errors
        }

        [Test]
        public void CashSecuredPutConfig_AcceptAssignment_BehaviorCorrect()
        {
            // Arrange
            var config = new CashSecuredPutConfig();

            // Act & Assert - Test default behavior
            Assert.That(config.AcceptAssignment, Is.True, "Should accept assignment by default");

            // Act & Assert - Test setting to false
            config.AcceptAssignment = false;
            Assert.That(config.AcceptAssignment, Is.False, "Should be able to disable assignment acceptance");
        }

        [Test]
        public void CashSecuredPutConfig_AggressiveDefaults_AreOptimal()
        {
            // Arrange & Act
            var config = new CashSecuredPutConfig();

            // Assert - Verify aggressive defaults that led to 14,912 trades
            Assert.That(config.PutStrikeOffset, Is.EqualTo(0.02m), "Strike offset should be aggressive 2%");
            Assert.That(config.MinimumPremium, Is.EqualTo(0.25m), "Minimum premium should be low for more opportunities");
            Assert.That(config.MinDaysToExpiration, Is.EqualTo(1), "Min DTE should be 1 for maximum opportunities");
            Assert.That(config.MaxDaysToExpiration, Is.EqualTo(60), "Max DTE should be 60 for broader range");
            Assert.That(config.MaxActivePositions, Is.EqualTo(2), "Should allow multiple positions");
        }

        [Test]
        public void CashSecuredPutConfig_ComparedToOtherStrategies_HasCorrectRiskProfile()
        {
            // Arrange
            var cspConfig = new CashSecuredPutConfig();
            var ccConfig = new CoveredCallConfig();

            // Act & Assert - CSP should be more aggressive for income generation
            Assert.That(cspConfig.PutStrikeOffset, Is.LessThanOrEqualTo(ccConfig.CallStrikeOffset), 
                "CSP offset should be equal or smaller for more aggressive income generation");
            
            Assert.That(cspConfig.MaxActivePositions, Is.LessThanOrEqualTo(ccConfig.MaxActivePositions), 
                "CSP currently uses conservative position limits");

            Assert.That(cspConfig.MinimumPremium, Is.LessThan(ccConfig.MinimumPremium), 
                "CSP should accept smaller premiums for more opportunities");
        }
    }
}
using System;
using Microsoft.CSharp.RuntimeBinder;
using NUnit.Framework;

namespace CoreAlgo.Tests
{
    [TestFixture]
    public class ContextInjectionTests
    {
        [Test]
        public void SetContext_WithValidLogger_ShouldSetLoggerProperty()
        {
            // Arrange
            var strategy = new TestStrategy();
            var mockLogger = new MockLogger();
            
            // Act
            strategy.SetContext(mockLogger);
            
            // Assert
            Assert.That(strategy.Logger, Is.EqualTo(mockLogger));
        }

        [Test]
        public void SetContext_WithNullLogger_ShouldThrowArgumentNullException()
        {
            // Arrange
            var strategy = new TestStrategy();
            
            // Act & Assert
            Assert.Throws<ArgumentNullException>(() => strategy.SetContext(null));
        }

        [Test]
        public void DynamicLoggerCall_WithValidLogger_ShouldWork()
        {
            // Arrange
            var strategy = new TestStrategy();
            var mockLogger = new MockLogger();
            strategy.SetContext(mockLogger);
            
            // Act & Assert - Test dynamic logger call
            Assert.DoesNotThrow(() => strategy.TestDynamicLoggerCall("Test message"));
            Assert.That(mockLogger.LastMessage, Is.EqualTo("Test message"));
        }

        [Test]
        public void DynamicLoggerCall_WithNullLogger_ShouldThrowRuntimeBinderException()
        {
            // Arrange
            var strategy = new TestStrategy();
            // Note: Don't call SetContext, so Logger remains null
            
            // Act & Assert - This simulates the exact error from the backtest logs
            var ex = Assert.Throws<RuntimeBinderException>(() => strategy.TestDynamicLoggerCall("Test message"));
            Assert.That(ex.Message, Does.Contain("Cannot perform runtime binding on a null reference"));
        }

        [Test]
        public void CorrectSequence_SetContextThenInitialize_ShouldWork()
        {
            // This test simulates the correct sequence we implemented in Main.cs
            
            // Arrange
            var strategy = new TestStrategy();
            var mockLogger = new MockLogger();
            
            // Act 1: Set context FIRST (like Main.cs does now)
            strategy.SetContext(mockLogger);
            
            // Act 2: Then initialize (which calls dynamic logger methods)
            Assert.DoesNotThrow(() => strategy.SimulateInitializeWithLogging());
            
            // Assert
            Assert.That(mockLogger.LastMessage, Is.EqualTo("Initializing strategy"));
        }

        [Test]
        public void IncorrectSequence_InitializeWithoutSetContext_ShouldThrow()
        {
            // This test simulates the broken sequence that caused the backtest error
            
            // Arrange
            var strategy = new TestStrategy();
            // Note: Skip SetContext call
            
            // Act & Assert: Initialize without setting context should fail
            var ex = Assert.Throws<RuntimeBinderException>(() => strategy.SimulateInitializeWithLogging());
            Assert.That(ex.Message, Does.Contain("Cannot perform runtime binding on a null reference"));
        }
    }

    // Test strategy that mimics the pattern in SimpleBaseStrategy
    public class TestStrategy
    {
        public object Logger { get; private set; }

        public void SetContext(object logger)
        {
            Logger = logger ?? throw new ArgumentNullException(nameof(logger));
        }

        // This simulates the dynamic logger calls like SmartLog() in SimpleBaseStrategy
        public void TestDynamicLoggerCall(string message)
        {
            // This is the exact pattern used in SimpleBaseStrategy for dynamic logger calls
            ((dynamic)Logger).Info(message);
        }

        // This simulates OnInitialize() which calls SmartLog during strategy initialization
        public void SimulateInitializeWithLogging()
        {
            // This simulates what happens in MultiAssetIronCondorTemplate.OnInitialize()
            // It calls SmartLog() which uses dynamic Logger
            ((dynamic)Logger).Info("Initializing strategy");
        }
    }

    // Mock logger that implements the expected interface
    public class MockLogger
    {
        public string LastMessage { get; private set; }

        public void Info(string message)
        {
            LastMessage = message;
        }

        public void Debug(string message)
        {
            LastMessage = message;
        }

        public void Error(string message)
        {
            LastMessage = message;
        }

        public void Warning(string message)
        {
            LastMessage = message;
        }
    }
}
using System;
using System.Linq;
using NUnit.Framework;
using QuantConnect;
using QuantConnect.Securities;
using CoreAlgo.Architecture.Core.Templates;
using CoreAlgo.Architecture.Core.Models;

namespace CoreAlgo.Tests
{
    [TestFixture]
    public class CoveredCallTemplateTests
    {
        private CoveredCallTemplate _strategy;

        [SetUp]
        public void SetUp()
        {
            _strategy = new CoveredCallTemplate();
        }

        [Test]
        public void Name_ReturnsCorrectValue()
        {
            // Act & Assert
            Assert.That(_strategy.Name, Is.EqualTo("Covered Call"));
        }

        [Test]
        public void Description_ReturnsCorrectValue()
        {
            // Act & Assert
            Assert.That(_strategy.Description, Is.EqualTo("Income strategy that generates premium by selling call options against owned stock"));
        }

        [Test]
        public void CoveredCallConfig_DefaultValues_AreCorrect()
        {
            // Arrange & Act
            var config = new CoveredCallConfig();

            // Assert
            Assert.That(config.UnderlyingSymbol, Is.EqualTo("SPY"));
            Assert.That(config.CallStrikeOffset, Is.EqualTo(0.02m));
            Assert.That(config.MinDaysToExpiration, Is.EqualTo(7));
            Assert.That(config.MaxDaysToExpiration, Is.EqualTo(45));
            Assert.That(config.MinimumPremium, Is.EqualTo(0.50m));
            Assert.That(config.MaxActivePositions, Is.EqualTo(3));
            Assert.That(config.SharesPerContract, Is.EqualTo(100));
            Assert.That(config.BuySharesIfNeeded, Is.True);
        }

        [Test]
        public void CoveredCallConfig_Validate_WithValidValues_ReturnsNoErrors()
        {
            // Arrange
            var config = new CoveredCallConfig
            {
                CallStrikeOffset = 0.02m,
                MinDaysToExpiration = 5,
                MaxDaysToExpiration = 30,
                MinimumPremium = 0.25m,
                MaxActivePositions = 2
            };

            // Act
            var errors = config.Validate();

            // Assert
            Assert.That(errors.Length, Is.EqualTo(0));
        }

        [Test]
        public void CoveredCallConfig_Validate_WithNegativeStrikeOffset_ReturnsError()
        {
            // Arrange
            var config = new CoveredCallConfig
            {
                CallStrikeOffset = -0.01m // Invalid negative offset
            };

            // Act
            var errors = config.Validate();

            // Assert
            Assert.That(errors.Length, Is.GreaterThan(0));
            Assert.That(errors.Any(e => e.Contains("CallStrikeOffset")), Is.True);
        }

        [Test]
        public void CoveredCallConfig_Validate_WithInvalidDTERange_ReturnsError()
        {
            // Arrange
            var config = new CoveredCallConfig
            {
                MinDaysToExpiration = 30,
                MaxDaysToExpiration = 7 // Max < Min - invalid
            };

            // Act
            var errors = config.Validate();

            // Assert
            Assert.That(errors.Length, Is.GreaterThan(0));
            Assert.That(errors.Any(e => e.Contains("DTE")), Is.True);
        }

        [Test]
        public void CoveredCallConfig_Validate_WithNegativePremium_ReturnsError()
        {
            // Arrange
            var config = new CoveredCallConfig
            {
                MinimumPremium = -0.10m // Invalid negative premium
            };

            // Act
            var errors = config.Validate();

            // Assert
            Assert.That(errors.Length, Is.GreaterThan(0));
            Assert.That(errors.Any(e => e.Contains("MinimumPremium")), Is.True);
        }

        [Test]
        public void CoveredCallConfig_Validate_WithInvalidSharesPerContract_ReturnsError()
        {
            // Arrange
            var config = new CoveredCallConfig
            {
                SharesPerContract = 50 // Must be 100 for standard options
            };

            // Act
            var errors = config.Validate();

            // Assert
            Assert.That(errors.Length, Is.GreaterThan(0));
            Assert.That(errors.Any(e => e.Contains("SharesPerContract")), Is.True);
        }

        [Test]
        public void CoveredCallConfig_Validate_WithExcessiveStrikeOffset_ReturnsError()
        {
            // Arrange
            var config = new CoveredCallConfig
            {
                CallStrikeOffset = 0.25m // 25% - too high
            };

            // Act
            var errors = config.Validate();

            // Assert
            Assert.That(errors.Length, Is.GreaterThan(0));
            Assert.That(errors.Any(e => e.Contains("CallStrikeOffset")), Is.True);
        }

        [Test]
        public void CoveredCallConfig_ToString_ReturnsFormattedString()
        {
            // Arrange
            var config = new CoveredCallConfig();

            // Act
            var result = config.ToString();

            // Assert
            Assert.IsNotNull(result);
            Assert.That(result.Contains("CoveredCall"), Is.True);
            Assert.That(result.Contains("SPY"), Is.True);
            Assert.That(result.Contains("2.0%"), Is.True);
        }

        [Test]
        public void CoveredCallConfig_HasStrategyParameterAttributes()
        {
            // Arrange
            var config = new CoveredCallConfig();
            var type = config.GetType();

            // Act & Assert - Check that properties have StrategyParameterAttribute
            var callOffsetProperty = type.GetProperty("CallStrikeOffset");
            Assert.IsNotNull(callOffsetProperty);
            
            var minDteProperty = type.GetProperty("MinDaysToExpiration");
            Assert.IsNotNull(minDteProperty);
            
            var maxDteProperty = type.GetProperty("MaxDaysToExpiration");
            Assert.IsNotNull(maxDteProperty);
            
            var minPremiumProperty = type.GetProperty("MinimumPremium");
            Assert.IsNotNull(minPremiumProperty);
        }

        [Test]
        public void CoveredCallConfig_LoadFromParameters_UpdatesConfigCorrectly()
        {
            // This test would require a mock QCAlgorithm instance
            // For now, we'll test the parameter validation and structure
            
            // Arrange
            var config = new CoveredCallConfig();
            var originalOffset = config.CallStrikeOffset;

            // Act - manually set a new value
            config.CallStrikeOffset = 0.05m;

            // Assert
            Assert.That(config.CallStrikeOffset, Is.Not.EqualTo(originalOffset));
            Assert.That(config.CallStrikeOffset, Is.EqualTo(0.05m));
        }

        [Test]
        public void CoveredCallConfig_EdgeCases_HandleCorrectly()
        {
            // Test boundary values
            var config = new CoveredCallConfig
            {
                CallStrikeOffset = 0.01m, // Minimum reasonable value
                MinDaysToExpiration = 1,   // Minimum DTE
                MaxDaysToExpiration = 365, // Maximum reasonable DTE
                MinimumPremium = 0.01m,    // Very small premium
                MaxActivePositions = 1     // Single position
            };

            var errors = config.Validate();
            Assert.That(errors.Length, Is.EqualTo(0), "Boundary values should be valid");
        }

        [Test] 
        public void CoveredCallConfig_MultipleValidationErrors_ReturnsAllErrors()
        {
            // Arrange
            var config = new CoveredCallConfig
            {
                CallStrikeOffset = -0.01m,     // Invalid
                MinDaysToExpiration = 50,      // Invalid (> Max)
                MaxDaysToExpiration = 7,       // Invalid (< Min)
                MinimumPremium = -0.10m,       // Invalid
                SharesPerContract = 50         // Invalid - must be 100
            };

            // Act
            var errors = config.Validate();

            // Assert
            Assert.That(errors.Length, Is.GreaterThan(2)); // Multiple validation errors
        }
    }
}
using System;
using System.Collections.Generic;
using NUnit.Framework;
using QuantConnect;
using QuantConnect.Orders;
using QuantConnect.Securities;
using CoreAlgo.Architecture.Core.Execution;

namespace CoreAlgo.Tests
{
    /// <summary>
    /// Comprehensive test suite for smart combo pricing functionality
    /// Tests the combo pricing engine, quote calculation, and progressive pricing logic
    /// </summary>
    [TestFixture]
    public class SmartComboPricingTests
    {
        /// <summary>
        /// Tests ComboQuote net pricing calculation for a simple two-leg spread
        /// </summary>
        [Test]
        public void ComboQuote_TwoLegSpread_CalculatesCorrectNetPricing()
        {
            // Arrange
            var symbol1 = CreateTestSymbol("AAPL_Call_150");
            var symbol2 = CreateTestSymbol("AAPL_Call_160");
            
            var legs = new List<Leg>
            {
                Leg.Create(symbol1, 1),   // Buy call
                Leg.Create(symbol2, -1)   // Sell call
            };

            var legQuotes = new Dictionary<Symbol, Quote>
            {
                { symbol1, new Quote(2.50m, 2.70m) }, // Mid: 2.60
                { symbol2, new Quote(1.20m, 1.40m) }  // Mid: 1.30
            };

            // Act
            var comboQuote = new ComboQuote(legs, legQuotes, DateTime.UtcNow);

            // Assert - Net debit spread: we pay ask for long, receive bid for short
            Assert.That(comboQuote.NetAsk, Is.EqualTo(2.70m - 1.20m).Within(0.01m)); // Pay 2.70, receive 1.20 = net debit 1.50
            Assert.That(comboQuote.NetBid, Is.EqualTo(2.50m - 1.40m).Within(0.01m)); // Pay 2.50, receive 1.40 = net debit 1.10
            Assert.That(comboQuote.NetMid, Is.EqualTo(1.30m).Within(0.01m)); // Mid between 1.50 and 1.10 = 1.30
            Assert.That(comboQuote.NetSpread, Is.EqualTo(0.40m).Within(0.01m)); // 1.50 - 1.10 = 0.40
            Assert.That(comboQuote.IsValid, Is.True);
        }

        /// <summary>
        /// Tests ComboQuote calculation for a credit spread (Iron Condor style)
        /// </summary>
        [Test]
        public void ComboQuote_CreditSpread_CalculatesCorrectNetPricing()
        {
            // Arrange - Iron Condor: sell put spread, sell call spread
            var symbol1 = CreateTestSymbol("SPX_Put_4900");
            var symbol2 = CreateTestSymbol("SPX_Put_4950");
            var symbol3 = CreateTestSymbol("SPX_Call_5050");
            var symbol4 = CreateTestSymbol("SPX_Call_5100");
            
            var legs = new List<Leg>
            {
                Leg.Create(symbol1, 1),   // Buy protective put
                Leg.Create(symbol2, -1),  // Sell put
                Leg.Create(symbol3, -1), // Sell call
                Leg.Create(symbol4, 1)   // Buy protective call
            };

            var legQuotes = new Dictionary<Symbol, Quote>
            {
                { symbol1, new Quote(10.00m, 10.50m) },  // Long put
                { symbol2, new Quote(15.00m, 15.50m) },  // Short put
                { symbol3, new Quote(16.00m, 16.50m) }, // Short call  
                { symbol4, new Quote(11.00m, 11.50m) }  // Long call
            };

            // Act
            var comboQuote = new ComboQuote(legs, legQuotes, DateTime.UtcNow);

            // Assert - Should be a net credit (negative net price means we receive money)
            // For an Iron Condor (credit spread):
            // NetBid = Long put bid + Long call bid - Short put ask - Short call ask
            // NetAsk = Long put ask + Long call ask - Short put bid - Short call bid
            var expectedNetBid = 10.00m + 11.00m - 15.50m - 16.50m;  // = -11.00 (receive 11.00)
            var expectedNetAsk = 10.50m + 11.50m - 15.00m - 16.00m;  // = -9.00 (receive 9.00)
            
            Assert.That(comboQuote.NetBid, Is.EqualTo(expectedNetBid).Within(0.01m));
            Assert.That(comboQuote.NetAsk, Is.EqualTo(expectedNetAsk).Within(0.01m));
            Assert.That(comboQuote.NetMid, Is.EqualTo(-10.00m).Within(0.01m));
            Assert.That(comboQuote.IsValid, Is.True);
        }

        /// <summary>
        /// Tests ComboPricingEngine initial price calculation
        /// </summary>
        [Test]
        public void ComboPricingEngine_InitialPrice_StartsAtNetMid()
        {
            // Arrange
            var engine = new ComboPricingEngine(SmartPricingMode.Normal, 5.0m);
            var legs = CreateSimpleDebitSpreadLegs();
            var comboQuote = CreateTestComboQuote(netBid: 1.10m, netAsk: 1.50m); // NetMid = 1.30

            // Act
            var initialPrice = engine.CalculateInitialComboPrice(legs, comboQuote, CoreAlgo.Architecture.Core.Execution.OrderDirection.Buy);

            // Assert
            Assert.That(initialPrice.Value, Is.EqualTo(1.30m).Within(0.01m), "Initial price should be at net mid");
        }

        /// <summary>
        /// Tests ComboPricingEngine progressive pricing for buy combos
        /// </summary>
        [Test]
        public void ComboPricingEngine_ProgressivePricing_BuyCombo_MovesTowardAsk()
        {
            // Arrange
            var engine = new ComboPricingEngine(SmartPricingMode.Normal, 5.0m); // 4 attempts
            var legs = CreateSimpleDebitSpreadLegs();
            var comboQuote = CreateTestComboQuote(netBid: 1.00m, netAsk: 2.00m); // NetMid = 1.50, Spread = 1.00

            var currentPrice = 1.50m; // Starting at mid

            // Act & Assert - Progressive steps toward ask for buy combo
            var price1 = engine.CalculateNextComboPrice(currentPrice, comboQuote, CoreAlgo.Architecture.Core.Execution.OrderDirection.Buy, 1);
            Assert.That(price1.HasValue, Is.True, "Price1 should have value");
            
            var price2 = engine.CalculateNextComboPrice(price1.Value, comboQuote, CoreAlgo.Architecture.Core.Execution.OrderDirection.Buy, 2);
            Assert.That(price2.HasValue, Is.True, "Price2 should have value");
            
            var price3 = engine.CalculateNextComboPrice(price2.Value, comboQuote, CoreAlgo.Architecture.Core.Execution.OrderDirection.Buy, 3);
            Assert.That(price3.HasValue, Is.True, "Price3 should have value");
            
            // Attempt 4 should return null (maxAttempts is 4 for Normal mode)
            var price4 = engine.CalculateNextComboPrice(price3.Value, comboQuote, CoreAlgo.Architecture.Core.Execution.OrderDirection.Buy, 4);
            Assert.That(price4.HasValue, Is.False, "Price4 should be null at max attempts");

            Assert.That(price1.Value > currentPrice, Is.True, "Price should increase from mid");
            Assert.That(price2.Value > price1.Value, Is.True, "Price should continue increasing");
            Assert.That(price3.Value > price2.Value, Is.True, "Price should continue increasing");
            // The limit price should approach but not exceed the ask price
            Assert.That(price3.Value <= comboQuote.NetAsk, Is.True, $"Should not exceed ask price ({comboQuote.NetAsk})");
        }

        /// <summary>
        /// Tests ComboPricingEngine progressive pricing for sell combos
        /// </summary>
        [Test]
        public void ComboPricingEngine_ProgressivePricing_SellCombo_MovesTowardBid()
        {
            // Arrange  
            var engine = new ComboPricingEngine(SmartPricingMode.Normal, 5.0m); // 4 attempts (changed from Fast to match expected behavior)
            var (legs, symbols) = CreateSimpleCreditSpreadLegsWithSymbols();
            
            // Create a proper combo quote for credit spread
            var legQuotes = new Dictionary<Symbol, Quote>
            {
                { symbols[0], new Quote(4.00m, 4.20m) },  // Buy put
                { symbols[1], new Quote(6.00m, 6.20m) },  // Sell 2 puts  
                { symbols[2], new Quote(5.00m, 5.20m) },  // Sell 2 calls
                { symbols[3], new Quote(3.00m, 3.20m) }   // Buy call
            };
            var comboQuote = new ComboQuote(legs, legQuotes, DateTime.UtcNow);

            var currentPrice = comboQuote.NetMid; // Starting at mid

            // Act & Assert - Progressive steps toward bid for sell combo
            var price1 = engine.CalculateNextComboPrice(currentPrice, comboQuote, CoreAlgo.Architecture.Core.Execution.OrderDirection.Sell, 1);
            Assert.That(price1.HasValue, Is.True, "Price1 should have value");
            
            var price2 = engine.CalculateNextComboPrice(price1.Value, comboQuote, CoreAlgo.Architecture.Core.Execution.OrderDirection.Sell, 2);
            Assert.That(price2.HasValue, Is.True, "Price2 should have value");
            
            var price3 = engine.CalculateNextComboPrice(price2.Value, comboQuote, CoreAlgo.Architecture.Core.Execution.OrderDirection.Sell, 3);
            Assert.That(price3.HasValue, Is.True, "Price3 should have value");

            Assert.That(price1.Value < currentPrice, Is.True, "Price should decrease from mid");
            Assert.That(price2.Value < price1.Value, Is.True, "Price should continue decreasing");
            
            // Attempt 4 should return null
            var price4 = engine.CalculateNextComboPrice(price3.Value, comboQuote, CoreAlgo.Architecture.Core.Execution.OrderDirection.Sell, 4);
            Assert.That(price4.HasValue, Is.False, "Should return null after max attempts");
        }

        /// <summary>
        /// Tests combo direction determination based on leg structure
        /// </summary>
        [Test]
        public void ComboPricingEngine_DetermineDirection_IdentifiesCorrectDirection()
        {
            // Arrange & Act & Assert
            var debitSpread = CreateSimpleDebitSpreadLegs(); // More long than short
            Assert.That(ComboPricingEngine.DetermineComboDirection(debitSpread), Is.EqualTo(CoreAlgo.Architecture.Core.Execution.OrderDirection.Buy));

            var creditSpread = CreateSimpleCreditSpreadLegs(); // More short than long  
            Assert.That(ComboPricingEngine.DetermineComboDirection(creditSpread), Is.EqualTo(CoreAlgo.Architecture.Core.Execution.OrderDirection.Sell));
        }

        /// <summary>
        /// Tests that pricing is skipped for spreads that are too wide
        /// </summary>
        [Test]
        public void ComboPricingEngine_WideSpread_SkipsSmartPricing()
        {
            // Arrange
            var engine = new ComboPricingEngine(SmartPricingMode.Normal, 2.0m); // Max spread = 2.0
            var legs = CreateSimpleDebitSpreadLegs();
            var wideComboQuote = CreateTestComboQuote(netBid: 1.00m, netAsk: 4.00m); // Spread = 3.0 > 2.0

            // Act
            var shouldAttempt = engine.ShouldAttemptComboPricing(wideComboQuote, CoreAlgo.Architecture.Core.Execution.OrderDirection.Buy);
            var initialPrice = engine.CalculateInitialComboPrice(legs, wideComboQuote, CoreAlgo.Architecture.Core.Execution.OrderDirection.Buy);

            // Assert
            Assert.That(shouldAttempt, Is.False, "Should not attempt pricing for wide spreads");
            Assert.That(initialPrice, Is.Null, "Should return null for wide spreads");
        }

        /// <summary>
        /// Tests pricing interval configuration for different modes
        /// </summary>
        [Test]
        public void ComboPricingEngine_PricingIntervals_MatchExpectedTiming()
        {
            // Arrange & Act & Assert
            var fastEngine = new ComboPricingEngine(SmartPricingMode.Fast, 5.0m);
            Assert.That(fastEngine.GetPricingInterval(), Is.EqualTo(TimeSpan.FromSeconds(5)));
            Assert.That(fastEngine.GetMaxAttempts(), Is.EqualTo(3));

            var normalEngine = new ComboPricingEngine(SmartPricingMode.Normal, 5.0m);
            Assert.That(normalEngine.GetPricingInterval(), Is.EqualTo(TimeSpan.FromSeconds(10)));
            Assert.That(normalEngine.GetMaxAttempts(), Is.EqualTo(4));

            var patientEngine = new ComboPricingEngine(SmartPricingMode.Patient, 5.0m);
            Assert.That(patientEngine.GetPricingInterval(), Is.EqualTo(TimeSpan.FromSeconds(20)));
            Assert.That(patientEngine.GetMaxAttempts(), Is.EqualTo(5));
        }

        /// <summary>
        /// Tests edge case with invalid quotes
        /// </summary>
        [Test]
        public void ComboQuote_InvalidQuotes_HandledGracefully()
        {
            // Arrange
            var legs = CreateSimpleDebitSpreadLegs();
            var invalidLegQuotes = new Dictionary<Symbol, Quote>
            {
                { CreateTestSymbol("AAPL_Call_150"), new Quote(0, 0) },  // Invalid quote
                { CreateTestSymbol("AAPL_Call_160"), new Quote(1.20m, 1.40m) }
            };

            // Act
            var comboQuote = new ComboQuote(legs, invalidLegQuotes, DateTime.UtcNow);

            // Assert
            Assert.That(comboQuote.IsValid, Is.False, "Should be invalid with zero bid/ask");
        }

        #region Helper Methods

        private List<Leg> CreateSimpleDebitSpreadLegs()
        {
            var (legs, _, _) = CreateSimpleDebitSpreadLegsWithSymbols();
            return legs;
        }
        
        private (List<Leg> legs, Symbol symbol1, Symbol symbol2) CreateSimpleDebitSpreadLegsWithSymbols()
        {
            var symbol1 = CreateTestSymbol("AAPL_Call_150");
            var symbol2 = CreateTestSymbol("AAPL_Call_160");
            var legs = new List<Leg>
            {
                Leg.Create(symbol1, 1),   // Buy call (long)
                Leg.Create(symbol2, -1)   // Sell call (short)
            };
            return (legs, symbol1, symbol2);
        }

        private List<Leg> CreateSimpleCreditSpreadLegs()
        {
            var (legs, _) = CreateSimpleCreditSpreadLegsWithSymbols();
            return legs;
        }
        
        private (List<Leg> legs, Symbol[] symbols) CreateSimpleCreditSpreadLegsWithSymbols()
        {
            var symbols = new Symbol[] {
                CreateTestSymbol("SPX_Put_4900"),
                CreateTestSymbol("SPX_Put_4950"),
                CreateTestSymbol("SPX_Call_5050"),
                CreateTestSymbol("SPX_Call_5100")
            };
            var legs = new List<Leg>
            {
                Leg.Create(symbols[0], 1),   // Buy put (protective)
                Leg.Create(symbols[1], -2),  // Sell 2 puts (credit)
                Leg.Create(symbols[2], -2), // Sell 2 calls (credit) 
                Leg.Create(symbols[3], 1)   // Buy call (protective)
            };
            return (legs, symbols);
        }

        private ComboQuote CreateTestComboQuote(decimal netBid, decimal netAsk)
        {
            // Calculate the individual leg quotes that will produce the desired net bid/ask
            // For a simple debit spread: NetBid = LongBid - ShortAsk, NetAsk = LongAsk - ShortBid
            var (legs, symbol1, symbol2) = CreateSimpleDebitSpreadLegsWithSymbols();
            
            // Reverse engineer leg quotes to get desired net prices
            var longBid = netBid + 1.40m;  // Add back the short ask
            var longAsk = netAsk + 1.20m;  // Add back the short bid
            
            var legQuotes = new Dictionary<Symbol, Quote>
            {
                { symbol1, new Quote(longBid, longAsk) },
                { symbol2, new Quote(1.20m, 1.40m) }
            };

            return new ComboQuote(legs, legQuotes, DateTime.UtcNow);
        }

        private Symbol CreateTestSymbol(string value)
        {
            // Create a simple test symbol without requiring MapFileProvider
            // For testing purposes, we create a basic equity symbol since we only need
            // a valid Symbol object to test the ComboQuote logic
            var sid = SecurityIdentifier.GenerateEquity(value, Market.USA, false);
            return new Symbol(sid, value);
        }

        #endregion
    }
}
using NUnit.Framework;
using System.Linq;
using CoreAlgo.Architecture.Core.Models;
using CoreAlgo.Architecture.Core.Execution;

namespace CoreAlgo.Tests
{
    /// <summary>
    /// Tests for SmartPricing configuration integration with StrategyConfig
    /// </summary>
    [TestFixture]
    public class SmartPricingConfigurationTests
    {
        [Test]
        public void StrategyConfig_SmartPricingMode_DefaultValue_IsNormal()
        {
            // Arrange
            var config = new TestStrategyConfig();

            // Act & Assert
            Assert.That(config.SmartPricingMode, Is.EqualTo("Normal"));
        }

        [Test]
        public void StrategyConfig_CreateSmartPricingEngine_Normal_ReturnsNormalEngine()
        {
            // Arrange
            var config = new TestStrategyConfig { SmartPricingMode = "Normal" };

            // Act
            var engine = config.CreateSmartPricingEngine();

            // Assert
            Assert.IsNotNull(engine);
            Assert.That(engine.Mode, Is.EqualTo(SmartPricingMode.Normal));
            Assert.IsInstanceOf<NormalPricingStrategy>(engine);
        }

        [Test]
        public void StrategyConfig_CreateSmartPricingEngine_Fast_ReturnsFastEngine()
        {
            // Arrange
            var config = new TestStrategyConfig { SmartPricingMode = "Fast" };

            // Act
            var engine = config.CreateSmartPricingEngine();

            // Assert
            Assert.IsNotNull(engine);
            Assert.That(engine.Mode, Is.EqualTo(SmartPricingMode.Fast));
            Assert.IsInstanceOf<FastPricingStrategy>(engine);
        }

        [Test]
        public void StrategyConfig_CreateSmartPricingEngine_Patient_ReturnsPatientEngine()
        {
            // Arrange
            var config = new TestStrategyConfig { SmartPricingMode = "Patient" };

            // Act
            var engine = config.CreateSmartPricingEngine();

            // Assert
            Assert.IsNotNull(engine);
            Assert.That(engine.Mode, Is.EqualTo(SmartPricingMode.Patient));
            Assert.IsInstanceOf<PatientPricingStrategy>(engine);
        }

        [Test]
        public void StrategyConfig_CreateSmartPricingEngine_Off_ReturnsNull()
        {
            // Arrange
            var config = new TestStrategyConfig { SmartPricingMode = "Off" };

            // Act
            var engine = config.CreateSmartPricingEngine();

            // Assert
            Assert.IsNull(engine);
        }

        [Test]
        public void StrategyConfig_CreateSmartPricingEngine_Disabled_ReturnsNull()
        {
            // Arrange
            var config = new TestStrategyConfig { SmartPricingMode = "Disabled" };

            // Act
            var engine = config.CreateSmartPricingEngine();

            // Assert
            Assert.IsNull(engine);
        }

        [Test]
        public void StrategyConfig_CreateSmartPricingEngine_CaseInsensitive_Works()
        {
            // Arrange
            var config = new TestStrategyConfig { SmartPricingMode = "NORMAL" };

            // Act
            var engine = config.CreateSmartPricingEngine();

            // Assert
            Assert.IsNotNull(engine);
            Assert.That(engine.Mode, Is.EqualTo(SmartPricingMode.Normal));
        }

        [Test]
        public void StrategyConfig_Validate_InvalidSmartPricingMode_ReturnsError()
        {
            // Arrange
            var config = new TestStrategyConfig { SmartPricingMode = "InvalidMode" };

            // Act
            var errors = config.Validate();

            // Assert
            Assert.That(errors.Length, Is.GreaterThan(0));
            Assert.That(errors.Any(e => e.Contains("Invalid SmartPricingMode")), Is.True);
        }

        [Test]
        public void StrategyConfig_Validate_ValidSmartPricingModes_NoErrors()
        {
            // Test all valid modes
            var validModes = new[] { "Normal", "Fast", "Patient", "Off", "Disabled", "False" };

            foreach (var mode in validModes)
            {
                // Arrange
                var config = new TestStrategyConfig { SmartPricingMode = mode };

                // Act
                var errors = config.Validate();

                // Assert
                var smartPricingErrors = errors.Where(e => e.Contains("SmartPricingMode")).ToArray();
                Assert.That(smartPricingErrors.Length, Is.EqualTo(0), 
                    $"Mode '{mode}' should be valid but got errors: {string.Join(", ", smartPricingErrors)}");
            }
        }

        [Test]
        public void IronCondorConfig_InheritsSmartPricingMode()
        {
            // Arrange
            var config = new IronCondorConfig();

            // Act & Assert
            Assert.That(config.SmartPricingMode, Is.EqualTo("Normal"));
            
            // Test that it can create engine
            var engine = config.CreateSmartPricingEngine();
            Assert.IsNotNull(engine);
            Assert.That(engine.Mode, Is.EqualTo(SmartPricingMode.Normal));
        }

        [Test]
        public void CoveredCallConfig_InheritsSmartPricingMode()
        {
            // Arrange
            var config = new CoveredCallConfig();

            // Act & Assert
            Assert.That(config.SmartPricingMode, Is.EqualTo("Normal"));
            
            // Test that it can create engine
            var engine = config.CreateSmartPricingEngine();
            Assert.IsNotNull(engine);
            Assert.That(engine.Mode, Is.EqualTo(SmartPricingMode.Normal));
        }

        /// <summary>
        /// Test strategy config for testing purposes
        /// </summary>
        private class TestStrategyConfig : StrategyConfig
        {
            // Empty implementation for testing base class functionality
        }
    }
}
using NUnit.Framework;
using System;
using CoreAlgo.Architecture.Core.Execution;

namespace CoreAlgo.Tests
{
    /// <summary>
    /// Unit tests for SmartPricing execution functionality
    /// </summary>
    [TestFixture]
    public class SmartPricingTests
    {
        [Test]
        public void SmartPricingEngineFactory_Create_Normal_ReturnsNormalStrategy()
        {
            // Arrange & Act
            var engine = SmartPricingEngineFactory.Create(SmartPricingMode.Normal);

            // Assert
            Assert.IsNotNull(engine);
            Assert.That(engine.Mode, Is.EqualTo(SmartPricingMode.Normal));
            Assert.IsInstanceOf<NormalPricingStrategy>(engine);
        }

        [Test]
        public void SmartPricingEngineFactory_Create_Fast_ReturnsFastStrategy()
        {
            // Arrange & Act
            var engine = SmartPricingEngineFactory.Create(SmartPricingMode.Fast);

            // Assert
            Assert.IsNotNull(engine);
            Assert.That(engine.Mode, Is.EqualTo(SmartPricingMode.Fast));
            Assert.IsInstanceOf<FastPricingStrategy>(engine);
        }

        [Test]
        public void SmartPricingEngineFactory_Create_Patient_ReturnsPatientStrategy()
        {
            // Arrange & Act
            var engine = SmartPricingEngineFactory.Create(SmartPricingMode.Patient);

            // Assert
            Assert.IsNotNull(engine);
            Assert.That(engine.Mode, Is.EqualTo(SmartPricingMode.Patient));
            Assert.IsInstanceOf<PatientPricingStrategy>(engine);
        }

        [Test]
        public void SmartPricingEngineFactory_Create_Off_ThrowsException()
        {
            // Arrange & Act & Assert
            Assert.Throws<InvalidOperationException>(() => 
                SmartPricingEngineFactory.Create(SmartPricingMode.Off));
        }

        [Test]
        public void SmartPricingEngineFactory_CreateFromString_ValidModes_ReturnsCorrectEngine()
        {
            // Test case insensitive string parsing
            var normalEngine = SmartPricingEngineFactory.Create("normal");
            var fastEngine = SmartPricingEngineFactory.Create("FAST");
            var patientEngine = SmartPricingEngineFactory.Create("Patient");

            Assert.That(normalEngine.Mode, Is.EqualTo(SmartPricingMode.Normal));
            Assert.That(fastEngine.Mode, Is.EqualTo(SmartPricingMode.Fast));
            Assert.That(patientEngine.Mode, Is.EqualTo(SmartPricingMode.Patient));
        }

        [Test]
        public void SmartPricingEngineFactory_ParseMode_InvalidString_ReturnsNormalDefault()
        {
            // Arrange & Act
            var mode = SmartPricingEngineFactory.ParseMode("InvalidMode");

            // Assert
            Assert.That(mode, Is.EqualTo(SmartPricingMode.Normal));
        }

        [Test]
        public void SmartPricingEngineFactory_ParseMode_OffModes_ReturnsOff()
        {
            Assert.That(SmartPricingEngineFactory.ParseMode("OFF"), Is.EqualTo(SmartPricingMode.Off));
            Assert.That(SmartPricingEngineFactory.ParseMode("disabled"), Is.EqualTo(SmartPricingMode.Off));
            Assert.That(SmartPricingEngineFactory.ParseMode("false"), Is.EqualTo(SmartPricingMode.Off));
        }

        [Test]
        public void NormalPricingStrategy_CalculateInitialPrice_ReturnsMidSpread()
        {
            // Arrange
            var strategy = new NormalPricingStrategy();
            var quote = new Quote(10.0m, 10.50m); // Bid: $10.00, Ask: $10.50, Mid: $10.25

            // Act
            var initialPrice = strategy.CalculateInitialPrice(quote, OrderDirection.Buy);

            // Assert
            Assert.That(initialPrice, Is.EqualTo(10.25m));
        }

        [Test]
        public void NormalPricingStrategy_CalculateNextPrice_ProgressesTowardAsk()
        {
            // Arrange
            var strategy = new NormalPricingStrategy();
            var quote = new Quote(10.0m, 10.50m); // Spread: $0.50
            var currentPrice = 10.25m; // Mid-spread

            // Act - Second attempt should move 25% toward ask
            var nextPrice = strategy.CalculateNextPrice(currentPrice, quote, OrderDirection.Buy, 2);

            // Assert
            Assert.IsNotNull(nextPrice);
            // Should be mid + (halfSpread * 0.25) = 10.25 + (0.25 * 0.25) = 10.3125
            Assert.That(nextPrice.Value, Is.EqualTo(10.3125m));
        }

        [Test]
        public void NormalPricingStrategy_CalculateNextPrice_ProgressesTowardBid()
        {
            // Arrange
            var strategy = new NormalPricingStrategy();
            var quote = new Quote(10.0m, 10.50m); // Spread: $0.50
            var currentPrice = 10.25m; // Mid-spread

            // Act - Second attempt should move 25% toward bid
            var nextPrice = strategy.CalculateNextPrice(currentPrice, quote, OrderDirection.Sell, 2);

            // Assert
            Assert.IsNotNull(nextPrice);
            // Should be mid - (halfSpread * 0.25) = 10.25 - (0.25 * 0.25) = 10.1875
            Assert.That(nextPrice.Value, Is.EqualTo(10.1875m));
        }

        [Test]
        public void NormalPricingStrategy_CalculateNextPrice_ExceedsMaxAttempts_ReturnsNull()
        {
            // Arrange
            var strategy = new NormalPricingStrategy();
            var quote = new Quote(10.0m, 10.50m);
            var currentPrice = 10.25m;

            // Act - Attempt number exceeds max attempts (4)
            var nextPrice = strategy.CalculateNextPrice(currentPrice, quote, OrderDirection.Buy, 5);

            // Assert
            Assert.IsNull(nextPrice);
        }

        [Test]
        public void FastPricingStrategy_GetPricingInterval_Returns5Seconds()
        {
            // Arrange
            var strategy = new FastPricingStrategy();

            // Act
            var interval = strategy.GetPricingInterval();

            // Assert
            Assert.That(interval, Is.EqualTo(TimeSpan.FromSeconds(5)));
        }

        [Test]
        public void PatientPricingStrategy_GetPricingInterval_Returns20Seconds()
        {
            // Arrange
            var strategy = new PatientPricingStrategy();

            // Act
            var interval = strategy.GetPricingInterval();

            // Assert
            Assert.That(interval, Is.EqualTo(TimeSpan.FromSeconds(20)));
        }

        [Test]
        public void NormalPricingStrategy_GetMaxAttempts_Returns4()
        {
            // Arrange
            var strategy = new NormalPricingStrategy();

            // Act
            var maxAttempts = strategy.GetMaxAttempts();

            // Assert
            Assert.That(maxAttempts, Is.EqualTo(4));
        }

        [Test]
        public void FastPricingStrategy_GetMaxAttempts_Returns3()
        {
            // Arrange
            var strategy = new FastPricingStrategy();

            // Act
            var maxAttempts = strategy.GetMaxAttempts();

            // Assert
            Assert.That(maxAttempts, Is.EqualTo(3));
        }

        [Test]
        public void PatientPricingStrategy_GetMaxAttempts_Returns5()
        {
            // Arrange
            var strategy = new PatientPricingStrategy();

            // Act
            var maxAttempts = strategy.GetMaxAttempts();

            // Assert
            Assert.That(maxAttempts, Is.EqualTo(5));
        }

        [Test]
        public void PricingStrategy_ShouldAttemptPricing_NarrowSpread_ReturnsFalse()
        {
            // Arrange
            var strategy = new NormalPricingStrategy();
            var quote = new Quote(10.0m, 10.02m); // Very narrow spread: $0.02

            // Act
            var shouldAttempt = strategy.ShouldAttemptPricing(quote, OrderDirection.Buy);

            // Assert
            Assert.That(shouldAttempt, Is.False);
        }

        [Test]
        public void PricingStrategy_ShouldAttemptPricing_WideSpread_ReturnsFalse()
        {
            // Arrange
            var strategy = new NormalPricingStrategy();
            var quote = new Quote(10.0m, 20.0m); // Very wide spread: $10.00 (100% of mid-price)

            // Act
            var shouldAttempt = strategy.ShouldAttemptPricing(quote, OrderDirection.Buy);

            // Assert
            Assert.That(shouldAttempt, Is.False);
        }

        [Test]
        public void PricingStrategy_ShouldAttemptPricing_ReasonableSpread_ReturnsTrue()
        {
            // Arrange
            var strategy = new NormalPricingStrategy();
            var quote = new Quote(10.0m, 10.20m); // Reasonable spread: $0.20

            // Act
            var shouldAttempt = strategy.ShouldAttemptPricing(quote, OrderDirection.Buy);

            // Assert
            Assert.That(shouldAttempt, Is.True);
        }

        [Test]
        public void Quote_Properties_CalculatedCorrectly()
        {
            // Arrange
            var quote = new Quote(10.0m, 10.50m);

            // Act & Assert
            Assert.That(quote.Bid, Is.EqualTo(10.0m));
            Assert.That(quote.Ask, Is.EqualTo(10.50m));
            Assert.That(quote.Price, Is.EqualTo(10.25m)); // Mid-spread
            Assert.That(quote.Spread, Is.EqualTo(0.50m)); // Ask - Bid
        }
    }
}
using System;
using System.Linq;
using NUnit.Framework;
using QuantConnect;
using QuantConnect.Securities;
using CoreAlgo.Architecture.Core.Templates;
using CoreAlgo.Architecture.Core.Models;

namespace CoreAlgo.Tests
{
	[TestFixture]
	public class ORBTemplateTests
	{
		private ORBTemplate _strategy;
		private ORBConfig _config;

		[SetUp]
		public void Setup()
		{
			_strategy = new ORBTemplate();
			_config = new ORBConfig();
		}

		[Test]
		public void Name_ReturnsCorrectValue()
		{
			// Act & Assert
			Assert.That(_strategy.Name, Is.EqualTo("Opening Range Breakout"));
		}

		[Test]
		public void Description_ReturnsCorrectText()
		{
			// Assert
			Assert.That(_strategy.Description, 
				Does.Contain("Monitors opening range"));
			Assert.That(_strategy.Description, 
				Does.Contain("0DTE credit spreads"));
			Assert.That(_strategy.Description, 
				Does.Contain("12:00 PM"));
			Assert.That(_strategy.Description, 
				Does.Contain("full trading days"));
		}

		[Test]
		public void ToString_ReturnsFormattedConfigString()
		{
			// Arrange
			_config.UnderlyingSymbol = "SPX";
			_config.RangePeriodMinutes = 60;
			_config.MinRangeWidthPercent = 0.2m;
			_config.SpreadWidth = 15m;
			_config.EntryTime = "12:00";
			
			// Act
			var result = _config.ToString();
			
			// Assert
			Assert.That(result, Does.Contain("ORB[SPX]"));
			Assert.That(result, Does.Contain("Range:60min"));
			Assert.That(result, Does.Contain("MinWidth:20.0%"));
			Assert.That(result, Does.Contain("SpreadWidth:$15"));
			Assert.That(result, Does.Contain("Entry:12:00"));
		}

		[Test]
		public void ORBConfig_HasCorrectDefaults()
		{
			// Assert essential parameters
			Assert.That(_config.RangePeriodMinutes, Is.EqualTo(60));
			Assert.That(_config.MinRangeWidthPercent, Is.EqualTo(0.2m));
			Assert.That(_config.SpreadWidth, Is.EqualTo(15m));
			Assert.That(_config.EntryTime, Is.EqualTo("12:00"));
			Assert.That(_config.MaxPositionsPerDay, Is.EqualTo(1));
			Assert.That(_config.ContractSize, Is.EqualTo(10));
			Assert.That(_config.MinStrikeOffset, Is.EqualTo(0.01m));
		}

		[Test]
		public void ORBConfig_HasCorrectNewDefaults()
		{
			// Assert new ORB strategy parameters
			Assert.That(_config.UseSmaTwentyFilter, Is.EqualTo(true));
			Assert.That(_config.CapitalAllocation, Is.EqualTo(100000m));
			Assert.That(_config.SkipFomcDays, Is.EqualTo(true));
			Assert.That(_config.EntryEvaluationMode, Is.EqualTo("ExactTime"));
			// Entry window now centralized in StrategyConfig; ORB enables it by default
			Assert.That(_config.UseEntryTimeWindow, Is.EqualTo(true));
			Assert.That(_config.EntryWindowStart, Is.EqualTo(new TimeSpan(12,0,0)));
			Assert.That(_config.EntryWindowEnd, Is.EqualTo(new TimeSpan(15,30,0)));
			Assert.That(_config.ShortStrikeBasis, Is.EqualTo("Underlying"));
			Assert.That(_config.StrictZeroDteOnly, Is.EqualTo(true));
			Assert.That(_config.FirstBreakout, Is.EqualTo(true));
		}

		[Test]
		public void ORBConfig_FomcDatesArePopulated()
		{
			// Verify FOMC dates collection contains expected dates
			Assert.That(ORBConfig.FomcDates2024_2025, Is.Not.Empty);
			Assert.That(ORBConfig.FomcDates2024_2025.Count, Is.GreaterThan(10));
			
			// Test some specific known FOMC dates for 2024
			Assert.That(ORBConfig.FomcDates2024_2025, Does.Contain(new DateTime(2024, 1, 31)));
			Assert.That(ORBConfig.FomcDates2024_2025, Does.Contain(new DateTime(2024, 3, 20)));
			Assert.That(ORBConfig.FomcDates2024_2025, Does.Contain(new DateTime(2024, 12, 18)));
		}
	}
}