Overall Statistics
Total Orders
12237
Average Win
0.21%
Average Loss
-0.28%
Compounding Annual Return
6.096%
Drawdown
50.200%
Expectancy
0.075
Start Equity
100000
End Equity
466322.28
Net Profit
366.322%
Sharpe Ratio
0.201
Sortino Ratio
0.21
Probabilistic Sharpe Ratio
0.004%
Loss Rate
39%
Win Rate
61%
Profit-Loss Ratio
0.76
Alpha
-0.006
Beta
0.806
Annual Standard Deviation
0.145
Annual Variance
0.021
Information Ratio
-0.197
Tracking Error
0.076
Treynor Ratio
0.036
Total Fees
$12952.84
Estimated Strategy Capacity
$16000000.00
Lowest Capacity Asset
TOYOY R735QTJ8XC9X
Portfolio Turnover
3.61%
Drawdown Recovery
3592
#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;
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
{
    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));
        }
        public bool CanEnterPosition(Symbol symbol, Slice slice, out string reason)
        {
            reason = string.Empty;
            var shouldLog = _config.LogEntryRestrictions;
            if (shouldLog)
            {
                _algorithm.Debug($"[DEBUG EntryRestrictions] Checking restrictions at {slice.Time}");
            }
            var withinHours = IsWithinTradingHours(slice.Time);
            if (shouldLog)
            {
                _algorithm.Debug($"  [1/5] Trading Hours: {withinHours} | Current: {slice.Time.TimeOfDay} | Window: {_config.TradingStartTime}-{_config.TradingEndTime}");
            }
            if (!withinHours)
            {
                reason = $"Outside trading hours ({_config.TradingStartTime}-{_config.TradingEndTime})";
                return false;
            }
            var hasCapacity = true;
            var currentPositionCount = 0;
            if (_config.MaxPositions > 0)
            {
                hasCapacity = HasCapacityForNewPosition();
                currentPositionCount = _algorithm.Portfolio.Where(kvp => kvp.Value.Invested).Count();
                if (shouldLog)
                {
                    _algorithm.Debug($"  [2/5] Max Positions: {hasCapacity} | Current: {currentPositionCount}/{_config.MaxPositions}");
                }
                if (!hasCapacity)
                {
                    reason = $"Max positions reached ({_config.MaxPositions})";
                    return false;
                }
            }
            else if (shouldLog)
            {
                _algorithm.Debug($"  [2/5] Max Positions: DISABLED (MaxPositions={_config.MaxPositions})");
            }
            if (_config.EnableOverlapPrevention && _config.MaxPositionsPerUnderlying > 0)
            {
                var existingCount = CountExistingPositions(symbol);
                var limit = _config.MaxPositionsPerUnderlying;
                if (shouldLog)
                {
                    _algorithm.Debug($"  [3/5] Overlap Prevention: Enabled | Existing: {existingCount}/{limit} | Mode={_config.OverlapPreventionMode}");
                }
                if (existingCount >= limit)
                {
                    var mode = (_config.OverlapPreventionMode ?? "Block").ToUpperInvariant();
                    var msg = $"Underlying limit reached ({existingCount}/{limit}) for {symbol.Underlying ?? symbol}";
                    if (mode == "BLOCK")
                    {
                        reason = msg;
                        return false;
                    }
                    else if (mode == "WARN")
                    {
                        _algorithm.Debug($"[ENTRY WARN] {msg}");
                    }
                    else // LOG mode
                    {
                        _algorithm.Debug($"[ENTRY LOG] {msg}");
                    }
                }
            }
            else if (shouldLog)
            {
                _algorithm.Debug($"  [3/5] Overlap Prevention: DISABLED - allowing multiple entries");
            }
            var hasSufficientCapital = HasSufficientCapital();
            if (shouldLog)
            {
                var portfolioValue = _algorithm.Portfolio.TotalPortfolioValue;
                var requiredCapital = portfolioValue * _config.AllocationPerPosition;
                var availableCash = _algorithm.Portfolio.Cash;
                _algorithm.Debug($"  [4/5] Capital: {hasSufficientCapital} | Available: ${availableCash:F2} | Required: ${requiredCapital:F2} (Allocation: {_config.AllocationPerPosition:P0})");
            }
            if (!hasSufficientCapital)
            {
                reason = "Insufficient capital for new position";
                return false;
            }
            if (_config.MinImpliedVolatility > 0)
            {
                var meetsVol = MeetsVolatilityRequirement(symbol, slice);
                if (shouldLog)
                {
                    _algorithm.Debug($"  [5/5] Volatility: {meetsVol} | Min IV: {_config.MinImpliedVolatility:P0}");
                }
                if (!meetsVol)
                {
                    reason = $"Implied volatility below minimum ({_config.MinImpliedVolatility:P0})";
                    return false;
                }
            }
            else if (shouldLog)
            {
                _algorithm.Debug($"  [5/5] Volatility: DISABLED (MinIV={_config.MinImpliedVolatility:P0})");
            }
            if (shouldLog)
            {
                _algorithm.Debug($"[DEBUG EntryRestrictions] ✓ ALL CHECKS PASSED");
            }
            return true;
        }
        public bool CanEnterOptionPosition(dynamic contract, out string reason)
        {
            reason = string.Empty;
            if (contract.Greeks == null)
            {
                reason = "Greeks not available for option contract";
                return false;
            }
            var delta = Math.Abs(contract.Greeks.Delta);
            if (delta < _config.EntryDeltaMin || delta > _config.EntryDeltaMax)
            {
                reason = $"Delta {delta:F2} outside range [{_config.EntryDeltaMin:F2}-{_config.EntryDeltaMax:F2}]";
                return false;
            }
            return true;
        }
        private bool IsWithinTradingHours(DateTime currentTime)
        {
            var timeOfDay = currentTime.TimeOfDay;
            if (_config.TradingStartTime == TimeSpan.Zero && _config.TradingEndTime == TimeSpan.Zero)
            {
                return true;
            }
            if (_config.TradingEndTime < _config.TradingStartTime)
            {
                return timeOfDay >= _config.TradingStartTime || timeOfDay <= _config.TradingEndTime;
            }
            return timeOfDay >= _config.TradingStartTime && timeOfDay <= _config.TradingEndTime;
        }
        private bool HasCapacityForNewPosition()
        {
            var currentPositions = _algorithm.Portfolio
                .Where(kvp => kvp.Value.Invested)
                .Count();
            return currentPositions < _config.MaxPositions;
        }
        private int CountExistingPositions(Symbol symbol)
        {
            if (symbol.SecurityType == SecurityType.Option)
            {
                var underlying = symbol.Underlying;
                return _algorithm.Portfolio.Count(kvp =>
                    kvp.Value.Invested &&
                    (kvp.Key == underlying || (kvp.Key.SecurityType == SecurityType.Option && kvp.Key.Underlying == underlying)));
            }
            return _algorithm.Portfolio.Count(kvp => kvp.Key == symbol && kvp.Value.Invested);
        }
        private bool HasSufficientCapital()
        {
            var portfolioValue = _algorithm.Portfolio.TotalPortfolioValue;
            var requiredCapital = portfolioValue * _config.AllocationPerPosition;
            var availableCash = _algorithm.Portfolio.Cash;
            return availableCash >= requiredCapital;
        }
        private bool MeetsVolatilityRequirement(Symbol symbol, Slice slice)
        {
            return true;
        }
        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
{
    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>();
        }
        public void RecordPositionEntry(Symbol symbol, DateTime entryTime)
        {
            _positionEntryTimes[symbol] = entryTime;
        }
        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;
            }
            if (IsProfitTargetReached(holding))
            {
                reason = $"Profit target reached ({holding.UnrealizedProfitPercent:P2} >= {_config.ProfitTarget:P2})";
                return true;
            }
            if (IsStopLossTriggered(holding))
            {
                reason = $"Stop loss triggered ({holding.UnrealizedProfitPercent:P2} <= {_config.StopLoss:P2})";
                return true;
            }
            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;
            }
            if (!IsWithinTradingHours(slice.Time))
            {
            }
            return false;
        }
        public bool ShouldExitOptionPosition(Symbol symbol, dynamic contract, out string reason)
        {
            reason = string.Empty;
            if (ShouldExitPosition(symbol, _algorithm.CurrentSlice, out reason))
            {
                return true;
            }
            if (contract.Greeks != null)
            {
                var delta = Math.Abs(contract.Greeks.Delta);
                if (delta <= _config.ExitDelta)
                {
                    reason = $"Delta exit triggered ({delta:F3} <= {_config.ExitDelta:F3})";
                    return true;
                }
            }
            var daysToExpiry = (contract.Expiry - _algorithm.Time).TotalDays;
            if (daysToExpiry <= 1) // Exit if expiring tomorrow
            {
                reason = $"Near expiration ({daysToExpiry:F1} days)";
                return true;
            }
            return false;
        }
        private bool IsProfitTargetReached(SecurityHolding holding)
        {
            if (_config.ProfitTarget == 0) return false;
            return holding.UnrealizedProfitPercent >= _config.ProfitTarget;
        }
        private bool IsStopLossTriggered(SecurityHolding holding)
        {
            if (_config.StopLoss == 0) return false;
            return holding.UnrealizedProfitPercent <= _config.StopLoss;
        }
        private bool IsMaxTimeReached(Symbol symbol, DateTime currentTime)
        {
            if (!_positionEntryTimes.ContainsKey(symbol))
                return false;
            var daysInTrade = (currentTime - _positionEntryTimes[symbol]).TotalDays;
            return daysInTrade >= _config.MaxDaysInTrade;
        }
        private double GetDaysInTrade(Symbol symbol, DateTime currentTime)
        {
            if (!_positionEntryTimes.ContainsKey(symbol))
                return 0;
            return (currentTime - _positionEntryTimes[symbol]).TotalDays;
        }
        private bool IsWithinTradingHours(DateTime currentTime)
        {
            var timeOfDay = currentTime.TimeOfDay;
            return timeOfDay >= _config.TradingStartTime && timeOfDay <= _config.TradingEndTime;
        }
        public double GetExitUrgency(Symbol symbol)
        {
            var holding = _algorithm.Portfolio[symbol];
            if (!holding.Invested)
                return 0;
            var urgency = 0.0;
            if (_config.StopLoss != 0 && holding.UnrealizedProfitPercent <= _config.StopLoss)
            {
                urgency = 1.0;
            }
            else if (_config.ProfitTarget != 0 && holding.UnrealizedProfitPercent >= _config.ProfitTarget)
            {
                urgency = 0.8;
            }
            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;
        }
        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();
        }
        public void ClearPositionEntry(Symbol symbol)
        {
            _positionEntryTimes.Remove(symbol);
        }
    }
    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
{
    public class ComboOrderTracker
    {
        public List<OrderTicket> ComboTickets { get; }
        public List<Leg> Legs { get; }
        public decimal CurrentNetPrice { get; private set; }
        public ComboQuote LastQuote { get; private set; }
        public OrderDirection ComboDirection { get; }
        public SmartPricingMode PricingMode { get; }
        public int AttemptNumber { get; private set; }
        public DateTime StartTime { get; }
        public ScheduledEvent ScheduledEvent { get; set; }
        public bool IsCompletelyFilled => ComboTickets.All(ticket =>
            ticket.Status == OrderStatus.Filled);
        public bool HasPartialFills => ComboTickets.Any(ticket =>
            ticket.Status == OrderStatus.PartiallyFilled ||
            (ticket.Status == OrderStatus.Filled && ticket.QuantityFilled != ticket.Quantity));
        public bool IsActive => ComboTickets.Any(ticket =>
            ticket.Status == OrderStatus.Submitted ||
            ticket.Status == OrderStatus.PartiallyFilled);
        public OrderTicket PrimaryTicket => ComboTickets.FirstOrDefault();
        public int PrimaryOrderId => PrimaryTicket?.OrderId ?? -1;
        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));
        }
        public void UpdateNetPrice(decimal newNetPrice, ComboQuote newQuote)
        {
            CurrentNetPrice = newNetPrice;
            LastQuote = newQuote ?? throw new ArgumentNullException(nameof(newQuote));
            AttemptNumber++;
        }
        public void UpdatePartialFill(OrderEvent orderEvent)
        {
        }
        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}";
        }
        public string GetLegStatus()
        {
            var legStatuses = ComboTickets.Select((ticket, index) =>
                $"Leg{index + 1}[{ticket.OrderId}]: {ticket.Status} " +
                $"({ticket.QuantityFilled}/{ticket.Quantity})");
            return string.Join(", ", legStatuses);
        }
        public TimeSpan GetRuntime()
        {
            return DateTime.UtcNow - StartTime;
        }
        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
{
    public class ComboPricingEngine
    {
        private readonly SmartPricingMode _mode;
        private readonly decimal _maxNetSpreadWidth;
        public ComboPricingEngine(SmartPricingMode mode, decimal maxNetSpreadWidth = 5.0m)
        {
            _mode = mode;
            _maxNetSpreadWidth = maxNetSpreadWidth;
        }
        public decimal? CalculateInitialComboPrice(List<Leg> legs, ComboQuote comboQuote, OrderDirection orderDirection)
        {
            if (legs == null || legs.Count == 0 || comboQuote == null || !comboQuote.IsValid)
                return null;
            if (comboQuote.NetSpread > _maxNetSpreadWidth)
                return null;
            return comboQuote.NetMid;
        }
        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;
            var progressionRatio = CalculateProgressionRatio(attemptNumber, maxAttempts);
            decimal targetPrice;
            decimal startPrice = comboQuote.NetMid;
            if (orderDirection == OrderDirection.Buy)
            {
                targetPrice = comboQuote.NetAsk;
            }
            else
            {
                targetPrice = comboQuote.NetBid;
            }
            var nextPrice = startPrice + (targetPrice - startPrice) * progressionRatio;
            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);
            }
            return Math.Round(nextPrice, 2);
        }
        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)
            };
        }
        public int GetMaxAttempts()
        {
            return _mode switch
            {
                SmartPricingMode.Fast => 3,
                SmartPricingMode.Normal => 4,
                SmartPricingMode.Patient => 5,
                _ => 4
            };
        }
        public bool ShouldAttemptComboPricing(ComboQuote comboQuote, OrderDirection orderDirection)
        {
            if (comboQuote == null || !comboQuote.IsValid)
                return false;
            if (comboQuote.NetSpread > _maxNetSpreadWidth)
                return false;
            if (comboQuote.NetSpread < 0.10m)
                return false;
            return true;
        }
        public static OrderDirection DetermineComboDirection(List<Leg> legs)
        {
            if (legs == null || legs.Count == 0)
                return OrderDirection.Buy; // Default
            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);
            }
            return shortLegs > longLegs ? OrderDirection.Sell : OrderDirection.Buy;
        }
        private decimal CalculateProgressionRatio(int attemptNumber, int maxAttempts)
        {
            if (attemptNumber <= 0 || maxAttempts <= 1)
                return 0m;
            var ratio = (decimal)attemptNumber / maxAttempts;
            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
{
    public class ComboQuote
    {
        public decimal NetBid { get; }
        public decimal NetAsk { get; }
        public decimal NetMid => (NetBid + NetAsk) / 2;
        public decimal NetSpread => NetAsk - NetBid;
        public Dictionary<Symbol, Quote> LegQuotes { get; }
        public DateTime Timestamp { get; }
        public bool IsValid => LegQuotes.Values.All(q => q.Bid > 0 && q.Ask > q.Bid) && NetSpread >= 0;
        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;
            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));
                }
                if (leg.Quantity > 0)
                {
                    netAsk += quote.Ask * Math.Abs(leg.Quantity);
                    netBid += quote.Bid * Math.Abs(leg.Quantity);
                }
                else if (leg.Quantity < 0)
                {
                    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;
        }
        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;
            }
        }
        private static Quote GetCurrentQuote(Security security)
        {
            try
            {
                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);
                }
                var price = security.Price;
                if (price > 0)
                {
                    var estimatedSpread = price * 0.005m;
                    return new Quote(price - estimatedSpread / 2, price + estimatedSpread / 2);
                }
                return null;
            }
            catch
            {
                return null;
            }
        }
        public override string ToString()
        {
            return $"ComboQuote: NetBid={NetBid:F2}, NetAsk={NetAsk:F2}, NetMid={NetMid:F2}, NetSpread={NetSpread:F2}, Valid={IsValid}";
        }
        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
{
    public interface ISmartPricingEngine
    {
        SmartPricingMode Mode { get; }
        decimal CalculateInitialPrice(Quote quote, OrderDirection orderDirection);
        decimal? CalculateNextPrice(decimal currentPrice, Quote quote, OrderDirection orderDirection, int attemptNumber);
        TimeSpan GetPricingInterval();
        int GetMaxAttempts();
        bool ShouldAttemptPricing(Quote quote, OrderDirection orderDirection);
        decimal? CalculateInitialComboPrice(List<Leg> legs, ComboQuote comboQuote, OrderDirection orderDirection);
        decimal? CalculateNextComboPrice(decimal currentNetPrice, ComboQuote comboQuote, OrderDirection orderDirection, int attemptNumber);
        bool ShouldAttemptComboPricing(ComboQuote comboQuote, OrderDirection orderDirection);
    }
    public enum OrderDirection
    {
        Buy,
        Sell
    }
    public enum SmartPricingMode
    {
        Off,
        Normal,
        Fast,
        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
{
    public abstract class PricingStrategy : ISmartPricingEngine
    {
        public abstract SmartPricingMode Mode { get; }
        protected abstract int StepCount { get; }
        protected abstract TimeSpan StepInterval { get; }
        public virtual decimal CalculateInitialPrice(Quote quote, OrderDirection orderDirection)
        {
            if (quote == null)
                throw new ArgumentNullException(nameof(quote));
            return quote.Price;
        }
        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
            var progressionPct = (decimal)attemptNumber / StepCount;
            decimal targetPrice;
            if (orderDirection == OrderDirection.Buy)
            {
                targetPrice = quote.Price + (quote.Spread / 2 * progressionPct);
                targetPrice = Math.Min(targetPrice, quote.Ask);
            }
            else
            {
                targetPrice = quote.Price - (quote.Spread / 2 * progressionPct);
                targetPrice = Math.Max(targetPrice, quote.Bid);
            }
            var priceChange = Math.Abs(targetPrice - currentPrice);
            var minChange = quote.Spread * 0.1m; // Minimum 10% of spread movement
            return priceChange >= minChange ? targetPrice : null;
        }
        public virtual TimeSpan GetPricingInterval()
        {
            return StepInterval;
        }
        public virtual int GetMaxAttempts()
        {
            return StepCount;
        }
        public virtual bool ShouldAttemptPricing(Quote quote, OrderDirection orderDirection)
        {
            if (quote == null)
                return false;
            if (quote.Spread < 0.05m)
                return false;
            if (quote.Spread > quote.Price * 0.10m)
                return false;
            return true;
        }
        public virtual decimal? CalculateInitialComboPrice(List<Leg> legs, ComboQuote comboQuote, OrderDirection orderDirection)
        {
            if (legs == null || legs.Count == 0 || comboQuote == null || !comboQuote.IsValid)
                return null;
            var comboPricingEngine = new ComboPricingEngine(Mode, 5.0m); // Default max spread width
            return comboPricingEngine.CalculateInitialComboPrice(legs, comboQuote, orderDirection);
        }
        public virtual decimal? CalculateNextComboPrice(decimal currentNetPrice, ComboQuote comboQuote, OrderDirection orderDirection, int attemptNumber)
        {
            if (comboQuote == null || !comboQuote.IsValid)
                return null;
            var comboPricingEngine = new ComboPricingEngine(Mode, 5.0m); // Default max spread width
            return comboPricingEngine.CalculateNextComboPrice(currentNetPrice, comboQuote, orderDirection, attemptNumber);
        }
        public virtual bool ShouldAttemptComboPricing(ComboQuote comboQuote, OrderDirection orderDirection)
        {
            if (comboQuote == null || !comboQuote.IsValid)
                return false;
            var comboPricingEngine = new ComboPricingEngine(Mode, 5.0m); // Default max spread width
            return comboPricingEngine.ShouldAttemptComboPricing(comboQuote, orderDirection);
        }
        protected decimal CalculatePriceImprovement(Quote quote, OrderDirection orderDirection, int attemptNumber)
        {
            var aggressivePrice = orderDirection == OrderDirection.Buy ? quote.Ask : quote.Bid;
            var midPrice = quote.Price;
            var maxImprovement = Math.Abs(aggressivePrice - midPrice);
            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
{
    public class SmartOrderTracker
    {
        public OrderTicket OrderTicket { get; }
        public OrderDirection OrderDirection { get; }
        public SmartPricingMode PricingMode { get; }
        public decimal CurrentPrice { get; set; }
        public int AttemptNumber { get; set; }
        public DateTime StartTime { get; }
        public Quote InitialQuote { get; }
        public Quote CurrentQuote { get; private set; }
        public ScheduledEvent ScheduledEvent { get; set; }
        public decimal FilledQuantity { get; private set; }
        public decimal RemainingQuantity => Math.Abs(OrderTicket.Quantity) - FilledQuantity;
        public bool IsPartiallyFilled => FilledQuantity > 0 && FilledQuantity < Math.Abs(OrderTicket.Quantity);
        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;
            PriceHistory = new PricingAttempt[GetMaxAttempts(mode)];
            PriceHistory[0] = new PricingAttempt(1, CurrentPrice, initialQuote, StartTime);
        }
        public void UpdatePrice(decimal newPrice, Quote newQuote)
        {
            CurrentPrice = newPrice;
            CurrentQuote = newQuote;
            AttemptNumber++;
            if (AttemptNumber <= PriceHistory.Length)
            {
                PriceHistory[AttemptNumber - 1] = new PricingAttempt(AttemptNumber, newPrice, newQuote, DateTime.UtcNow);
            }
        }
        public void UpdatePartialFill(OrderEvent orderEvent)
        {
            if (orderEvent.Status == OrderStatus.PartiallyFilled)
            {
                FilledQuantity += Math.Abs(orderEvent.FillQuantity);
            }
        }
        public SmartPricingMetrics GetMetrics()
        {
            var elapsed = DateTime.UtcNow - StartTime;
            var initialSpread = InitialQuote.Spread;
            var currentSpread = CurrentQuote?.Spread ?? initialSpread;
            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
            };
        }
        private static decimal GetLimitPrice(OrderTicket orderTicket)
        {
            return 0; // Will be overridden by actual limit price from the order
        }
        private static int GetMaxAttempts(SmartPricingMode mode)
        {
            return mode switch
            {
                SmartPricingMode.Fast => 3,
                SmartPricingMode.Normal => 4,
                SmartPricingMode.Patient => 5,
                _ => 1
            };
        }
    }
    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;
        }
    }
    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
{
    public class SmartPricingExecutionModel : ExecutionModel
    {
        private readonly IAlgorithmContext _context;
        private readonly ISmartPricingEngine _pricingEngine;
        private readonly Dictionary<int, SmartOrderTracker> _activeOrders;
        private readonly HashSet<ScheduledEvent> _scheduledEvents;
        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>();
        }
        public override void Execute(QCAlgorithm algorithm, IPortfolioTarget[] targets)
        {
            try
            {
                if (_pricingEngine.Mode == SmartPricingMode.Off)
                {
                    base.Execute(algorithm, targets);
                    return;
                }
                _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;
                    var quote = GetCurrentQuote(security);
                    if (quote == null || !_pricingEngine.ShouldAttemptPricing(quote, quantity > 0 ? OrderDirection.Buy : OrderDirection.Sell))
                    {
                        algorithm.MarketOrder(target.Symbol, quantity, tag: "SmartPricing:Fallback", asynchronous: true);
                        continue;
                    }
                    var orderDirection = quantity > 0 ? OrderDirection.Buy : OrderDirection.Sell;
                    var initialPrice = _pricingEngine.CalculateInitialPrice(quote, orderDirection);
                    var orderTicket = algorithm.LimitOrder(target.Symbol, quantity, initialPrice,
                        tag: $"SmartPricing:{_pricingEngine.Mode}:Initial", asynchronous: true);
                    if (orderTicket != null)
                    {
                        var tracker = new SmartOrderTracker(orderTicket, quote, orderDirection, _pricingEngine.Mode, initialPrice);
                        _activeOrders[orderTicket.OrderId] = tracker;
                        ScheduleNextPricingUpdate(algorithm, tracker);
                        _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)
            {
                _context.Logger.Error($"SmartPricing execution error: {ex.Message}");
                base.Execute(algorithm, targets);
            }
        }
        public override void OnOrderEvent(QCAlgorithm algorithm, OrderEvent orderEvent)
        {
            if (!_activeOrders.TryGetValue(orderEvent.OrderId, out var tracker))
                return;
            switch (orderEvent.Status)
            {
                case OrderStatus.Filled:
                    _context.Logger.Debug($"SmartPricing: Order {orderEvent.OrderId} filled at ${orderEvent.FillPrice:F2} " +
                                                    $"after {tracker.AttemptNumber} attempts");
                    CleanupOrder(tracker);
                    break;
                case OrderStatus.PartiallyFilled:
                    _context.Logger.Debug($"SmartPricing: Order {orderEvent.OrderId} partially filled " +
                                                    $"({orderEvent.FillQuantity}/{tracker.OrderTicket.Quantity})");
                    tracker.UpdatePartialFill(orderEvent);
                    break;
                case OrderStatus.Canceled:
                case OrderStatus.Invalid:
                    _context.Logger.Warning($"SmartPricing: Order {orderEvent.OrderId} {orderEvent.Status}");
                    CleanupOrder(tracker);
                    break;
            }
        }
        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;
        }
        private void UpdateOrderPrice(QCAlgorithm algorithm, SmartOrderTracker tracker)
        {
            try
            {
                if (tracker.ScheduledEvent != null)
                {
                    _scheduledEvents.Remove(tracker.ScheduledEvent);
                    tracker.ScheduledEvent = null;
                }
                if (!_activeOrders.ContainsKey(tracker.OrderTicket.OrderId) ||
                    tracker.OrderTicket.Status == OrderStatus.Filled)
                {
                    return;
                }
                var security = algorithm.Securities[tracker.OrderTicket.Symbol];
                var currentQuote = GetCurrentQuote(security);
                if (currentQuote == null)
                {
                    _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;
                }
                var nextPrice = _pricingEngine.CalculateNextPrice(
                    tracker.CurrentPrice, currentQuote, tracker.OrderDirection, tracker.AttemptNumber + 1);
                if (nextPrice.HasValue)
                {
                    var updateResult = tracker.OrderTicket.Update(new UpdateOrderFields { LimitPrice = nextPrice.Value });
                    if (updateResult.IsSuccess)
                    {
                        tracker.UpdatePrice(nextPrice.Value, currentQuote);
                        _context.Logger.Debug($"SmartPricing: Updated order {tracker.OrderTicket.OrderId} " +
                                                        $"to ${nextPrice.Value:F2} (attempt {tracker.AttemptNumber})");
                        if (tracker.AttemptNumber < _pricingEngine.GetMaxAttempts())
                        {
                            ScheduleNextPricingUpdate(algorithm, tracker);
                        }
                        else
                        {
                            _context.Logger.Debug($"SmartPricing: Order {tracker.OrderTicket.OrderId} reached max attempts, keeping final price");
                        }
                    }
                    else
                    {
                        _context.Logger.Warning($"SmartPricing: Failed to update order {tracker.OrderTicket.OrderId}: {updateResult.ErrorMessage}");
                    }
                }
                else
                {
                    _context.Logger.Debug($"SmartPricing: No more price improvements for order {tracker.OrderTicket.OrderId}");
                }
            }
            catch (Exception ex)
            {
                _context.Logger.Error($"SmartPricing: Error updating order {tracker.OrderTicket.OrderId}: {ex.Message}");
            }
        }
        private Quote GetCurrentQuote(Security security)
        {
            try
            {
                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);
                }
                var price = security.Price;
                if (price > 0)
                {
                    var estimatedSpread = price * 0.005m;
                    return new Quote(price - estimatedSpread / 2, price + estimatedSpread / 2);
                }
                return null;
            }
            catch (Exception ex)
            {
                _context.Logger.Error($"SmartPricing: Error getting quote for {security.Symbol}: {ex.Message}");
                return null;
            }
        }
        private void CleanupOrder(SmartOrderTracker tracker)
        {
            _activeOrders.Remove(tracker.OrderTicket.OrderId);
            if (tracker.ScheduledEvent != null)
            {
                _scheduledEvents.Remove(tracker.ScheduledEvent);
            }
        }
    }
    public class Quote
    {
        public decimal Bid { get; }
        public decimal Ask { get; }
        public decimal Price => (Bid + Ask) / 2;
        public decimal Spread => Ask - Bid;
        public Quote(decimal bid, decimal ask)
        {
            Bid = bid;
            Ask = ask;
        }
    }
}
using System;
using System.Collections.Generic;
using QuantConnect;
using QuantConnect.Algorithm;
using QuantConnect.Orders;
using QuantConnect.Securities;
namespace CoreAlgo.Architecture.Core.Helpers
{
    public static class AssignmentHandler
    {
        private static readonly HashSet<string> _processedAssignments = new HashSet<string>();
        public static void HandleAssignment(QCAlgorithm algorithm, OrderEvent assignmentEvent)
        {
            try
            {
                algorithm.Log($"ASSIGNMENT DETECTED: {assignmentEvent.Symbol} at {algorithm.Time}");
                var assignmentKey = $"{assignmentEvent.Symbol}_{assignmentEvent.OrderId}_{algorithm.Time}";
                if (_processedAssignments.Contains(assignmentKey))
                {
                    algorithm.Log($"ASSIGNMENT ALREADY PROCESSED: {assignmentEvent.Symbol} (skipping duplicate)");
                    return;
                }
                _processedAssignments.Add(assignmentKey);
                var assignedSymbol = assignmentEvent.Symbol;
                if (IsCashSettledIndexOption(assignedSymbol))
                {
                    algorithm.Log($"INDEX OPTION ASSIGNMENT: {assignedSymbol} (Cash-settled - no equity position created)");
                    HandleCashSettledAssignment(algorithm, assignmentEvent);
                    return;
                }
                var underlyingSymbol = GetUnderlyingSymbol(assignedSymbol);
                if (underlyingSymbol == null)
                {
                    algorithm.Error($"Could not determine underlying symbol for assignment: {assignedSymbol}");
                    return;
                }
                if (!algorithm.Securities.ContainsKey(assignedSymbol))
                {
                    algorithm.Error($"Option contract not in securities: {assignedSymbol}");
                    return;
                }
                var optionSec = algorithm.Securities[assignedSymbol];
                var contractMultiplier = optionSec.SymbolProperties.ContractMultiplier;
                var contracts = Math.Abs(assignmentEvent.FillQuantity);
                int sharesToFlatten;
                if (algorithm.Portfolio.ContainsKey(underlyingSymbol) &&
                    algorithm.Portfolio[underlyingSymbol].Quantity != 0)
                {
                    sharesToFlatten = -(int)algorithm.Portfolio[underlyingSymbol].Quantity;
                    algorithm.Log($"Using portfolio holdings: {underlyingSymbol.Value} has {algorithm.Portfolio[underlyingSymbol].Quantity} shares");
                }
                else
                {
                    var optionRight = assignedSymbol.ID.OptionRight;
                    if (optionRight == OptionRight.Call)
                    {
                        sharesToFlatten = (int)(contracts * contractMultiplier);
                    }
                    else // OptionRight.Put
                    {
                        sharesToFlatten = -(int)(contracts * contractMultiplier);
                    }
                    algorithm.Log($"Using computed position for {optionRight}: {contracts} contracts → {sharesToFlatten} shares");
                }
                algorithm.Log($"Assignment Details: {underlyingSymbol.Value} - Contracts: {contracts}, " +
                             $"Multiplier: {contractMultiplier}, Shares to flatten: {sharesToFlatten}");
                var orderTicket = algorithm.MarketOrder(underlyingSymbol, sharesToFlatten, tag: "Assignment Auto-Liquidation", asynchronous: true);
                algorithm.Log($"LIQUIDATING ASSIGNMENT: {underlyingSymbol.Value} - " +
                             $"Quantity: {sharesToFlatten}, Order ID: {orderTicket.OrderId}");
                LogAssignmentImpact(algorithm, assignmentEvent, underlyingSymbol, contractMultiplier);
            }
            catch (Exception ex)
            {
                algorithm.Error($"Error handling assignment: {ex.Message}");
                algorithm.Error($"Stack trace: {ex.StackTrace}");
            }
        }
        private static bool IsCashSettledIndexOption(Symbol symbol)
        {
            if (symbol?.SecurityType == SecurityType.IndexOption)
            {
                return true;
            }
            var symbolStr = symbol?.Value?.ToUpperInvariant() ?? "";
            if (symbolStr.StartsWith("SPXW") || symbolStr.StartsWith("SPX"))
                return true;
            if (symbolStr.StartsWith("VIX") || symbolStr.StartsWith("NDX") || symbolStr.StartsWith("RUT"))
                return true;
            return false;
        }
        private static Symbol GetUnderlyingSymbol(Symbol optionSymbol)
        {
            if (optionSymbol.SecurityType == SecurityType.Option || optionSymbol.SecurityType == SecurityType.IndexOption)
            {
                return optionSymbol.Underlying;
            }
            if (optionSymbol.SecurityType == SecurityType.Equity || optionSymbol.SecurityType == SecurityType.Index)
            {
                return optionSymbol;
            }
            return null;
        }
        private static void LogAssignmentImpact(QCAlgorithm algorithm, OrderEvent assignmentEvent,
            Symbol underlyingSymbol, decimal contractMultiplier)
        {
            try
            {
                var holding = algorithm.Portfolio[underlyingSymbol];
                decimal assignmentValue;
                if (holding.Invested && holding.HoldingsValue != 0)
                {
                    assignmentValue = Math.Abs(holding.HoldingsValue);
                }
                else
                {
                    var contracts = Math.Abs(assignmentEvent.FillQuantity);
                    var underlyingPrice = algorithm.Securities.ContainsKey(underlyingSymbol)
                        ? algorithm.Securities[underlyingSymbol].Price
                        : assignmentEvent.FillPrice;
                    assignmentValue = contracts * contractMultiplier * underlyingPrice;
                }
                var portfolioImpact = algorithm.Portfolio.TotalPortfolioValue > 0
                    ? assignmentValue / algorithm.Portfolio.TotalPortfolioValue * 100
                    : 0;
                algorithm.Log($"ASSIGNMENT IMPACT: {underlyingSymbol.Value} - " +
                             $"Value: ${assignmentValue:F0}, Portfolio Impact: {portfolioImpact:F1}%, " +
                             $"Free Margin: ${algorithm.Portfolio.MarginRemaining:F0}");
            }
            catch (Exception ex)
            {
                algorithm.Debug($"Error logging assignment impact: {ex.Message}");
            }
        }
        private static void HandleCashSettledAssignment(QCAlgorithm algorithm, OrderEvent assignmentEvent)
        {
            try
            {
                var optionSymbol = assignmentEvent.Symbol;
                var underlyingSymbol = GetUnderlyingSymbol(optionSymbol);
                if (underlyingSymbol == null)
                {
                    algorithm.Error($"Could not determine underlying symbol for cash-settled assignment: {optionSymbol}");
                    return;
                }
                var underlyingPrice = algorithm.Securities.ContainsKey(underlyingSymbol)
                    ? algorithm.Securities[underlyingSymbol].Price
                    : 0m;
                algorithm.Log($"CASH SETTLEMENT: {optionSymbol} based on {underlyingSymbol.Value} price: ${underlyingPrice:F2}");
                algorithm.Log($"Settlement Value: ${assignmentEvent.FillPrice:F2} per contract");
                algorithm.Log($"Total Settlement: ${assignmentEvent.FillPrice * Math.Abs(assignmentEvent.FillQuantity):F2}");
                LogCashSettledAssignmentImpact(algorithm, assignmentEvent, underlyingSymbol, underlyingPrice);
            }
            catch (Exception ex)
            {
                algorithm.Error($"Error handling cash-settled assignment: {ex.Message}");
            }
        }
        private static void LogCashSettledAssignmentImpact(QCAlgorithm algorithm, OrderEvent assignmentEvent,
            Symbol underlyingSymbol, decimal underlyingPrice)
        {
            try
            {
                var settlementValue = Math.Abs(assignmentEvent.FillPrice * assignmentEvent.FillQuantity);
                var portfolioImpact = algorithm.Portfolio.TotalPortfolioValue > 0
                    ? settlementValue / algorithm.Portfolio.TotalPortfolioValue * 100
                    : 0;
                algorithm.Log($"ASSIGNMENT IMPACT: {underlyingSymbol.Value} - " +
                             $"Value: ${settlementValue:F0}, Portfolio Impact: {portfolioImpact:F1}%, " +
                             $"Free Margin: ${algorithm.Portfolio.MarginRemaining:F0}");
            }
            catch (Exception ex)
            {
                algorithm.Debug($"Error logging cash-settled assignment impact: {ex.Message}");
            }
        }
        public static void MonitorUnexpectedEquityPositions(QCAlgorithm algorithm)
        {
            try
            {
                var unexpectedEquities = 0;
                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 QuantConnect;
using QuantConnect.Indicators;

namespace CoreAlgo.Architecture.Core.Helpers
{
    /// <summary>
    /// BX-Trender indicator for consolidated weekly or monthly bars.
    /// Formula: RSI(EMA(close,5) - EMA(close,20), 5) - 50
    /// Colors: Lime (positive+rising), DarkGreen (positive+falling),
    ///         LightRed (negative+rising), DarkRed (negative+falling)
    /// </summary>
    public class BXTrender
    {
        private readonly ExponentialMovingAverage _emaShort;
        private readonly ExponentialMovingAverage _emaLong;
        private readonly RelativeStrengthIndex _rsiOfDiff;

        private decimal _prevValue = decimal.MinValue;
        private string _prevColorState = "NA";

        public string ColorState { get; private set; } = "NA";
        public bool IsBullish => ColorState != "DarkRed" && ColorState != "NA";
        public bool JustStalled { get; private set; }
        public bool JustTurnedDarkRed { get; private set; }
        public decimal Value { get; private set; }
        public bool IsReady => _rsiOfDiff.IsReady;

        public BXTrender(int emaShort = 5, int emaLong = 20, int rsiPeriod = 5)
        {
            _emaShort = new ExponentialMovingAverage(emaShort);
            _emaLong = new ExponentialMovingAverage(emaLong);
            _rsiOfDiff = new RelativeStrengthIndex(rsiPeriod, MovingAverageType.Wilders);
        }

        /// <summary>
        /// Feed a consolidated bar's close price. Call once per weekly/monthly bar.
        /// </summary>
        public void Update(DateTime time, decimal close)
        {
            // Update EMAs with the close price
            _emaShort.Update(time, close);
            _emaLong.Update(time, close);

            if (!_emaShort.IsReady || !_emaLong.IsReady)
                return;

            // Compute EMA diff and feed to RSI
            var diff = _emaShort.Current.Value - _emaLong.Current.Value;
            _rsiOfDiff.Update(time, diff);

            if (!_rsiOfDiff.IsReady)
                return;

            // shortTermXtrender = RSI - 50
            Value = _rsiOfDiff.Current.Value - 50m;

            // Determine color state
            _prevColorState = ColorState;
            var isPositive = Value > 0;
            var isRising = _prevValue != decimal.MinValue && Value > _prevValue;

            if (isPositive && isRising)
                ColorState = "Lime";
            else if (isPositive && !isRising)
                ColorState = "DarkGreen";
            else if (!isPositive && isRising)
                ColorState = "LightRed";
            else
                ColorState = "DarkRed";

            // Transition detection: just turned DarkRed (first bar of DarkRed)
            JustTurnedDarkRed = _prevValue != decimal.MinValue
                && ColorState == "DarkRed" && _prevColorState != "DarkRed";

            // Transition detection: just stalled (turned DarkGreen or DarkRed from a non-stall state)
            // Guard: _prevValue != MinValue ensures we don't fire on the very first ready bar
            JustStalled = _prevValue != decimal.MinValue
                && (ColorState == "DarkGreen" || ColorState == "DarkRed")
                && _prevColorState != "DarkGreen" && _prevColorState != "DarkRed";

            _prevValue = Value;
        }
    }
}
using System;
using System.Collections.Generic;
using System.Linq;
namespace CoreAlgo.Architecture.Core.Helpers
{
    public class FairValueBand
    {
        private readonly int _period;
        private readonly bool _computePivotBands;
        private readonly int _pivotLookback;
        private readonly Queue<decimal> _ohlc4Window;
        private readonly List<decimal> _highRatios = new List<decimal>();
        private readonly List<decimal> _lowRatios = new List<decimal>();
        private const int MaxDeviationSamples = 1000;
        private readonly Queue<decimal> _ohlcSpreadWindow;
        private readonly List<decimal> _pivotUpRatios = new List<decimal>();
        private readonly List<decimal> _pivotDownRatios = new List<decimal>();
        private const int MaxPivotSamples = 2000;
        private int _updateCount;
        public decimal FairPrice { get; private set; }
        public decimal UpperBand { get; private set; }
        public decimal LowerBand { get; private set; }
        public int DirSwitch { get; private set; }
        public decimal PivotBandUp { get; private set; }
        public decimal PivotBandDown { get; private set; }
        public bool IsReady => _updateCount >= _period && _highRatios.Count > 0;
        public FairValueBand(int period, bool computePivotBands = false, int pivotLookback = 5)
        {
            _period = period;
            _computePivotBands = computePivotBands;
            _pivotLookback = pivotLookback;
            _ohlc4Window = new Queue<decimal>(period + 1);
            if (_computePivotBands)
            {
                _ohlcSpreadWindow = new Queue<decimal>(2 * pivotLookback + 2);
            }
        }
        public void Update(DateTime time, decimal open, decimal high, decimal low, decimal close)
        {
            _updateCount++;
            var ohlc4 = (open + high + low + close) / 4m;
            _ohlc4Window.Enqueue(ohlc4);
            if (_ohlc4Window.Count > _period)
                _ohlc4Window.Dequeue();
            if (_ohlc4Window.Count < _period)
                return;
            FairPrice = _ohlc4Window.Sum() / _period;
            if (low < FairPrice && high > FairPrice)
            {
                if (_highRatios.Count >= MaxDeviationSamples)
                    _highRatios.RemoveAt(0);
                _highRatios.Add(high / FairPrice);
                if (_lowRatios.Count >= MaxDeviationSamples)
                    _lowRatios.RemoveAt(0);
                _lowRatios.Add(low / FairPrice);
            }
            if (_highRatios.Count > 0 && _lowRatios.Count > 0)
            {
                UpperBand = FairPrice * Median(_highRatios);
                LowerBand = FairPrice * Median(_lowRatios);
            }
            if (high < LowerBand)
                DirSwitch = -1;  // Red — price below lower band
            else if (low > UpperBand)
                DirSwitch = 1;   // Green — price above upper band
            if (_computePivotBands && FairPrice > 0)
            {
                var ohlcSpread = ohlc4 / FairPrice;
                _ohlcSpreadWindow.Enqueue(ohlcSpread);
                var windowSize = 2 * _pivotLookback + 1;
                if (_ohlcSpreadWindow.Count > windowSize)
                    _ohlcSpreadWindow.Dequeue();
                if (_ohlcSpreadWindow.Count == windowSize)
                {
                    var values = _ohlcSpreadWindow.ToArray();
                    var centerIdx = _pivotLookback;
                    var centerValue = values[centerIdx];
                    decimal? pivotHigh = null;
                    bool isPivotHigh = true;
                    for (int i = 0; i < values.Length; i++)
                    {
                        if (i != centerIdx && values[i] >= centerValue)
                        {
                            isPivotHigh = false;
                            break;
                        }
                    }
                    if (isPivotHigh) pivotHigh = centerValue;
                    decimal? pivotLow = null;
                    bool isPivotLow = true;
                    for (int i = 0; i < values.Length; i++)
                    {
                        if (i != centerIdx && values[i] <= centerValue)
                        {
                            isPivotLow = false;
                            break;
                        }
                    }
                    if (isPivotLow) pivotLow = centerValue;
                    if (UpperBand > 0 && low > UpperBand)
                    {
                        if (pivotHigh.HasValue)
                        {
                            if (_pivotUpRatios.Count >= MaxPivotSamples)
                                _pivotUpRatios.RemoveAt(0);
                            _pivotUpRatios.Add(pivotHigh.Value);
                        }
                    }
                    else if (LowerBand > 0 && high < LowerBand)
                    {
                        if (pivotLow.HasValue)
                        {
                            if (_pivotDownRatios.Count >= MaxPivotSamples)
                                _pivotDownRatios.RemoveAt(0);
                            _pivotDownRatios.Add(pivotLow.Value);
                        }
                    }
                }
                if (_pivotUpRatios.Count > 0)
                    PivotBandUp = FairPrice * Median(_pivotUpRatios);
                if (_pivotDownRatios.Count > 0)
                    PivotBandDown = FairPrice * Median(_pivotDownRatios);
            }
        }
        private static decimal Median(List<decimal> values)
        {
            var sorted = values.OrderBy(v => v).ToArray();
            int mid = sorted.Length / 2;
            return sorted.Length % 2 == 0
                ? (sorted[mid - 1] + sorted[mid]) / 2m
                : sorted[mid];
        }
    }
}
using System;
using System.Linq;
using System.Text;
namespace CoreAlgo.Architecture.Core.Helpers
{
    public class TradeTag
    {
        public string Strategy { get; set; } = string.Empty;
        public string Type { get; set; } = string.Empty;
        public string Details { get; set; } = string.Empty;
        public string OptionalInfo { get; set; } = string.Empty;
        public string LegInfo { get; set; } = string.Empty;
        public static string Build(string strategy, string type, string details = "", string optionalInfo = "", string legInfo = "")
        {
            var tag = new TradeTag
            {
                Strategy = strategy ?? string.Empty,
                Type = type ?? string.Empty,
                Details = details ?? string.Empty,
                OptionalInfo = optionalInfo ?? string.Empty,
                LegInfo = legInfo ?? string.Empty
            };
            return tag.ToString();
        }
        public static TradeTag Parse(string tag)
        {
            if (string.IsNullOrEmpty(tag))
            {
                return new TradeTag();
            }
            var parts = tag.Split('_');
            if (parts.Length >= 2)
            {
                var tagObj = new TradeTag
                {
                    Strategy = parts[0],
                    Type = parts[1]
                };
                if (parts.Length >= 3)
                {
                    tagObj.Details = parts[2];
                }
                if (parts.Length >= 4)
                {
                    tagObj.OptionalInfo = parts[3];
                }
                if (parts.Length == 2 && parts[1].Contains(" "))
                {
                    var typeAndDetails = parts[1].Split(new[] { ' ' }, 2);
                    tagObj.Type = typeAndDetails[0];
                    if (typeAndDetails.Length > 1)
                    {
                        tagObj.Details = typeAndDetails[1];
                    }
                }
                else if (parts.Length == 1 && tag.Contains(" "))
                {
                    var allParts = tag.Split(new[] { ' ' }, StringSplitOptions.RemoveEmptyEntries);
                    if (allParts.Length >= 1)
                    {
                        tagObj.Strategy = allParts[0];
                    }
                    if (allParts.Length >= 2)
                    {
                        tagObj.Type = allParts[1];
                    }
                    if (allParts.Length >= 3)
                    {
                        tagObj.Details = string.Join(" ", allParts.Skip(2));
                    }
                }
                return tagObj;
            }
            return new TradeTag
            {
                Details = tag
            };
        }
        public bool Matches(string strategy, string type)
        {
            return string.Equals(Strategy, strategy, StringComparison.OrdinalIgnoreCase) &&
                   string.Equals(Type, type, StringComparison.OrdinalIgnoreCase);
        }
        public bool IsEntry()
        {
            return string.Equals(Type, "Entry", StringComparison.OrdinalIgnoreCase);
        }
        public bool IsExit()
        {
            return string.Equals(Type, "Exit", StringComparison.OrdinalIgnoreCase) ||
                   string.Equals(Type, "Close", StringComparison.OrdinalIgnoreCase) ||
                   Type.Contains("Close", StringComparison.OrdinalIgnoreCase);
        }
        public bool IsStopLoss()
        {
            return string.Equals(Type, "StopLoss", StringComparison.OrdinalIgnoreCase) ||
                   Type.Contains("SL", StringComparison.OrdinalIgnoreCase);
        }
        public bool IsTakeProfit()
        {
            return string.Equals(Type, "TakeProfit", StringComparison.OrdinalIgnoreCase) ||
                   Type.Contains("TP", StringComparison.OrdinalIgnoreCase);
        }
        public bool IsScaleIn()
        {
            return string.Equals(Type, "ScaleIn", StringComparison.OrdinalIgnoreCase);
        }
        public bool IsEod()
        {
            return !string.IsNullOrEmpty(OptionalInfo) &&
                   OptionalInfo.Contains("EOD", StringComparison.OrdinalIgnoreCase);
        }
        public bool TryGetParentEid(out int parentEid)
        {
            parentEid = 0;
            if (string.IsNullOrEmpty(Details))
            {
                return false;
            }
            if (Details.StartsWith("E", StringComparison.OrdinalIgnoreCase) && Details.Length > 1)
            {
                return int.TryParse(Details.Substring(1), out parentEid);
            }
            return false;
        }
        public override string ToString()
        {
            var sb = new StringBuilder();
            if (!string.IsNullOrEmpty(Strategy))
            {
                sb.Append(Strategy);
            }
            if (!string.IsNullOrEmpty(Type))
            {
                if (sb.Length > 0) sb.Append('_');
                sb.Append(Type);
            }
            if (!string.IsNullOrEmpty(Details))
            {
                if (sb.Length > 0) sb.Append('_');
                sb.Append(Details);
            }
            if (!string.IsNullOrEmpty(OptionalInfo))
            {
                if (sb.Length > 0) sb.Append('_');
                sb.Append(OptionalInfo);
            }
            if (!string.IsNullOrEmpty(LegInfo))
            {
                if (sb.Length > 0) sb.Append('_');
                sb.Append(LegInfo);
            }
            return sb.Length > 0 ? sb.ToString() : "Unknown";
        }
        public static bool MatchesPattern(string tag, string strategy, string type)
        {
            if (string.IsNullOrEmpty(tag))
            {
                return false;
            }
            var parsed = Parse(tag);
            return parsed.Matches(strategy, type);
        }
    }
}
using System;
using System.Collections.Generic;
using System.Linq;
using QuantConnect;
using QuantConnect.Securities;
using CoreAlgo.Architecture.Core.Interfaces;
namespace CoreAlgo.Architecture.Core.Implementations
{
    public class CollateralValidationRule : IPositionOverlapRule
    {
        private readonly IAlgorithmContext _context;
        private readonly ISmartLogger _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
            {
                if (proposedSymbol.SecurityType != SecurityType.Option)
                    return ValidationResult.Success();
                if (quantity >= 0)
                    return ValidationResult.Success();
                if (IsMultiLegStrategy(strategyTag))
                {
                    _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)
            {
                _logger.Error($"[{RuleName}] Error validating collateral: {ex.Message}");
                return ValidationResult.Error($"Collateral validation error: {ex.Message}");
            }
        }
        private ValidationResult ValidateCallCollateral(
            Symbol underlying,
            decimal proposedQuantity,
            IEnumerable<KeyValuePair<Symbol, SecurityHolding>> existingPositions,
            int contractMultiplier)
        {
            try
            {
                var stockHolding = _context.Algorithm.Portfolio[underlying];
                var availableShares = stockHolding?.Quantity ?? 0;
                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;
                _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)
            {
                _logger.Warning($"[{RuleName}] Error in call validation: {ex.Message}");
                return ValidationResult.Success();
            }
        }
        private ValidationResult ValidatePutCollateral(
            Symbol proposedSymbol,
            decimal proposedQuantity,
            IEnumerable<KeyValuePair<Symbol, SecurityHolding>> existingPositions,
            int contractMultiplier)
        {
            try
            {
                var underlying = proposedSymbol.Underlying;
                var strike = proposedSymbol.ID.StrikePrice;
                var availableCash = _context.Algorithm.Portfolio.Cash;
                var cashRequiredForNewPut = Math.Abs(proposedQuantity) * strike * contractMultiplier;
                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;
                _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)
            {
                _logger.Warning($"[{RuleName}] Error in put validation: {ex.Message}");
                return ValidationResult.Success();
            }
        }
        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;
            }
        }
        private Symbol GetPositionUnderlying(Symbol symbol)
        {
            return symbol.SecurityType == SecurityType.Option ? symbol.Underlying : symbol;
        }
        private bool IsMultiLegStrategy(string strategyTag)
        {
            if (!string.IsNullOrEmpty(strategyTag))
            {
                var tag = strategyTag.ToUpperInvariant();
                if (tag.Contains("COMBO") || tag.Contains("MULTI") || tag.Contains("SPREAD"))
                    return true;
            }
            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)
            {
                _logger.Debug($"[MULTI-LEG DETECTION] Could not read ComboOrderLegCount: {ex.Message}");
            }
            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, TRACE=4
    /// </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
        /// </summary>
        Debug = 3,
        
        /// <summary>
        /// Trace messages - highest granularity tracing, lowest priority
        /// </summary>
        Trace = 4
    }
}
    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;
    using CoreAlgo.Architecture.Core.Services;
namespace CoreAlgo.Architecture.Core.Implementations
{
    public enum HighlightKind
    {
        Entry,
        TakeProfit,
        StopLoss,
        TrailingStop,
        PartialFill,
        Cancel
    }
    public static class SmartLoggerStore
    {
        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();
        public static readonly ConcurrentDictionary<string, Dictionary<string, string>> HighlightContexts =
            new ConcurrentDictionary<string, Dictionary<string, string>>();
        public static readonly ConcurrentQueue<SmartLogMessage> Highlights = new ConcurrentQueue<SmartLogMessage>();
        private static int _immediateHighlightCount = 0;
        private static DateTime _lastImmediateResetDay = DateTime.MinValue;
        private static readonly object _immediateCounterLock = new object();
        private const int MaxMetricsPerScope = 12;
        private const int MaxMetricChars = 80;
        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;
        public static (int dailyMessages, int messageGroups) GetCollectionCounts()
        {
            return (DailyMessages.Count, MessageGroups.Count);
        }
        public static bool ShouldThrottleMessage(string baseHash, DateTime currentTime)
        {
            var throttleKey = $"throttle_{baseHash}";
            var info = MessageThrottle.GetOrAdd(throttleKey, _ => (currentTime, 0));
            if (currentTime - info.lastSeen > ThrottleWindow)
            {
                MessageThrottle.TryUpdate(throttleKey, (currentTime, 1), info);
                return false;
            }
            var newCount = info.count + 1;
            MessageThrottle.TryUpdate(throttleKey, (currentTime, newCount), info);
            return newCount > MaxMessagesPerWindow;
        }
        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 _);
            }
        }
        public static void MergeMetrics(string scopeKey, Dictionary<string, string> metrics)
        {
            if (string.IsNullOrEmpty(scopeKey) || metrics == null || metrics.Count == 0)
                return;
            var context = HighlightContexts.GetOrAdd(scopeKey, _ => new Dictionary<string, string>());
            lock (context)
            {
                foreach (var kvp in metrics)
                {
                    if (context.Count >= MaxMetricsPerScope)
                        break;
                    var value = kvp.Value;
                    if (value.Length > MaxMetricChars)
                        value = value.Substring(0, MaxMetricChars - 3) + "...";
                    context[kvp.Key] = value;
                }
            }
        }
        public static bool TryGetAndClearMetrics(string scopeKey, out Dictionary<string, string> metrics)
        {
            metrics = null;
            if (string.IsNullOrEmpty(scopeKey))
                return false;
            if (HighlightContexts.TryRemove(scopeKey, out var context))
            {
                lock (context)
                {
                    metrics = new Dictionary<string, string>(context);
                }
                return metrics.Count > 0;
            }
            return false;
        }
        public static void ClearHighlightMetrics(string scopeKey)
        {
            if (!string.IsNullOrEmpty(scopeKey))
            {
                HighlightContexts.TryRemove(scopeKey, out _);
            }
        }
        public static void AddHighlight(SmartLogMessage message)
        {
            Highlights.Enqueue(message);
        }
        public static bool ShouldPrintImmediateHighlight(DateTime currentTime, int maxImmediate)
        {
            lock (_immediateCounterLock)
            {
                if (currentTime.Date > _lastImmediateResetDay.Date)
                {
                    _immediateHighlightCount = 0;
                    _lastImmediateResetDay = currentTime.Date;
                }
                if (_immediateHighlightCount < maxImmediate)
                {
                    _immediateHighlightCount++;
                    return true;
                }
                return false;
            }
        }
        public static List<SmartLogMessage> TryDequeueAllHighlights()
        {
            var result = new List<SmartLogMessage>();
            while (Highlights.TryDequeue(out var msg))
            {
                result.Add(msg);
            }
            return result;
        }
        public static void ProcessDailyLogs(QCAlgorithm algorithm, TradeSummary summary = null, int? maxHighlightsPerDayOverride = null)
        {
            if (algorithm.LiveMode)
                return;
            lock (ProcessLock)
            {
                var currentDay = algorithm.Time.Date;
                if (LastProcessedDay.HasValue && LastProcessedDay.Value == currentDay)
                {
                    return; // First OnEndOfDay() call processes, subsequent calls are ignored
                }
                var hasHighlights = Highlights.Count > 0;
                var hasMessages = DailyMessages.Count > 0 || MessageGroups.Count > 0;
                if (!hasHighlights && !hasMessages)
                {
                    LastProcessedDay = currentDay;
                    return;
                }
                algorithm.Log($"--- EOD {currentDay:yyyy-MM-dd} ---");
                var maxHighlightsPerDay = maxHighlightsPerDayOverride ??
                    (int.TryParse(algorithm.GetParameter("MaxHighlightsPerDay", "200"), out var parsedMax) ? parsedMax : 200);
                var highlights = TryDequeueAllHighlights();
                if (highlights.Any())
                {
                    var sortedHighlights = highlights.OrderBy(h => h.Timestamp).ToList();
                    var displayCount = Math.Min(sortedHighlights.Count, maxHighlightsPerDay);
                    for (int i = 0; i < displayCount; i++)
                        algorithm.Log(sortedHighlights[i].Message);
                    if (sortedHighlights.Count > maxHighlightsPerDay)
                        algorithm.Log($"... and {sortedHighlights.Count - maxHighlightsPerDay} more");
                }
                if (hasMessages)
                {
                    foreach (var kvp in DailyMessages)
                    {
                        var messages = kvp.Value;
                        if (messages.Count == 0) continue;
                        lock (messages)
                        {
                            var firstMsg = messages[0];
                            var repeat = messages.Count > 1 ? $" (x{messages.Count})" : "";
                            algorithm.Log($"{firstMsg.Message}{repeat}");
                        }
                    }
                    foreach (var kvp in MessageGroups)
                        algorithm.Log(kvp.Value.GetSummary());
                }
                DailyMessages.Clear();
                MessageGroups.Clear();
                CleanOldThrottleEntries(currentDay);
                HighlightContexts.Clear();
                LastProcessedDay = currentDay;
            }
        }
    }
    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;
        }
        public string Hash
        {
            get
            {
                if (_hash == null)
                {
                    var content = $"{Level}|{ClassName}|{FunctionName}|{Message}";
                    _hash = ComputeHash(content);
                }
                return _hash;
            }
        }
        public string BaseHash
        {
            get
            {
                if (_baseHash == null)
                {
                    try
                    {
                        var template = Regex.Replace(Message, @"[-+]?[0-9,]*\.?[0-9]+(?:[eE][-+]?[0-9]+)?", "NUM");
                        template = Regex.Replace(template, @"\s+", " ");
                        template = template.ToLowerInvariant();
                        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
                    {
                        var content = $"{Level}|{ClassName}|{FunctionName}";
                        _baseHash = ComputeHash(content);
                    }
                }
                return _baseHash;
            }
        }
        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
            {
            }
            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();
            }
        }
    }
    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;
        }
        public string GetSummary()
        {
            if (Messages.Count == 1)
                return FormatMessage(Messages[0]);
            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]);
            var mean = values.Average();
            var min = values.Min();
            var max = values.Max();
            var count = values.Count;
            var timeRange = $"{FirstTime:HH:mm:ss}-{LastTime:HH:mm:ss}";
            var sections = new List<string>
            {
                $"{timeRange} {Level} -> {ClassName}.{FunctionName}: {BaseMessage}",
                $"    Stats: mean={mean:F2}, min={min:F2}, max={max:F2}"
            };
            if (count >= 2)
            {
                var trend = GetTrendAnalysis(values, timestamps);
                if (!string.IsNullOrEmpty(trend))
                    sections.Add($"    Trend:{trend}");
            }
            if (count >= 3)
            {
                var distribution = GetDistributionAnalysis(values);
                if (!string.IsNullOrEmpty(distribution))
                    sections.Add($"    {distribution}");
            }
            sections.Add($"    Samples: {count}");
            return string.Join("\n", sections);
        }
        private string GetTrendAnalysis(List<double> values, List<DateTime> timestamps)
        {
            if (values.Count < 2) return "";
            try
            {
                var keyChanges = new List<(DateTime time, double value)>();
                for (int i = 1; i < values.Count - 1; i++)
                {
                    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]));
                    }
                }
                if (keyChanges.Count > 4)
                {
                    keyChanges = keyChanges.OrderByDescending(x => Math.Abs(x.value - values[0])).Take(4).ToList();
                    keyChanges = keyChanges.OrderBy(x => x.time).ToList();
                }
                if (!keyChanges.Any()) return "";
                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 (Math.Abs(minVal - maxVal) < 0.001)
                    return $"Distribution: [constant={minVal:F2}]";
                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]++;
                }
                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}";
        }
    }
    public class QCLogger<T> : ILogger<T>, ISmartLogger
    {
        private readonly QCAlgorithm _algorithm;
        private readonly string _categoryName;
        private readonly int _currentLogLevel;
        private readonly bool _verboseMode;
        public QCLogger(QCAlgorithm algorithm, int logLevel = 3, bool verboseMode = false)
        {
            _algorithm = algorithm ?? throw new ArgumentNullException(nameof(algorithm));
            _categoryName = typeof(T).Name;
            _currentLogLevel = logLevel;
            _verboseMode = algorithm.LiveMode || verboseMode;
        }
        public void LogInformation(string message, params object[] args)
        {
            var formattedMessage = FormatMessage(message, args);
            if (_verboseMode)
            {
                var realTimeMessage = FormatRealTimeMessage(formattedMessage, _categoryName, GetCallingFunctionName(), LogLevel.Info);
                _algorithm.Log(realTimeMessage);
                return;
            }
            StoreSmartMessage("INFO", formattedMessage);
        }
        public void LogDebug(string message, params object[] args)
        {
            var formattedMessage = FormatMessage(message, args);
            if (_verboseMode)
            {
                var realTimeMessage = FormatRealTimeMessage(formattedMessage, _categoryName, GetCallingFunctionName(), LogLevel.Debug);
                _algorithm.Log(realTimeMessage);
                return;
            }
            StoreSmartMessage("DEBUG", formattedMessage);
        }
        public void LogTrace(string message, params object[] args)
        {
            var formattedMessage = FormatMessage(message, args);
            if (_verboseMode)
            {
                var realTimeMessage = FormatRealTimeMessage(formattedMessage, _categoryName, GetCallingFunctionName(), LogLevel.Trace);
                _algorithm.Log(realTimeMessage);
                return;
            }
            StoreSmartMessage("TRACE", formattedMessage);
        }
        public void LogWarning(string message, params object[] args)
        {
            var formattedMessage = FormatMessage(message, args);
            if (_verboseMode)
            {
                var realTimeMessage = FormatRealTimeMessage(formattedMessage, _categoryName, GetCallingFunctionName(), LogLevel.Warning);
                _algorithm.Log(realTimeMessage);
                return;
            }
            StoreSmartMessage("WARN", formattedMessage);
        }
        public void LogError(string message, params object[] args)
        {
            var formattedMessage = FormatMessage(message, args);
            if (_verboseMode)
            {
                var realTimeMessage = FormatRealTimeMessage(formattedMessage, _categoryName, GetCallingFunctionName(), LogLevel.Error);
                _algorithm.Log(realTimeMessage);
                return;
            }
            StoreSmartMessage("ERROR", formattedMessage);
        }
        public void LogError(Exception exception, string message, params object[] args)
        {
            var formattedMessage = FormatMessage(message, args);
            var fullMessage = $"{formattedMessage} - Exception: {exception.Message}";
            if (_verboseMode)
            {
                var realTimeMessage = FormatRealTimeMessage(fullMessage, _categoryName, GetCallingFunctionName(), LogLevel.Error);
                _algorithm.Log(realTimeMessage);
                var stackTraceMessage = FormatRealTimeMessage($"StackTrace: {exception.StackTrace}", _categoryName, GetCallingFunctionName(), LogLevel.Error);
                _algorithm.Log(stackTraceMessage);
                return;
            }
            StoreSmartMessage("ERROR", fullMessage);
            StoreSmartMessage("ERROR", $"StackTrace: {exception.StackTrace}");
        }
        public void LogMessage(string message, LogLevel level = LogLevel.Debug)
        {
            if ((int)level > _currentLogLevel)
                return;
            if (_verboseMode)
            {
                var realTimeMessage = FormatRealTimeMessage(message, _categoryName, GetCallingFunctionName(), level);
                _algorithm.Log(realTimeMessage);
                return;
            }
            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;
                case LogLevel.Trace:
                    LogTrace(message);
                    break;
                default:
                    LogDebug(message);
                    break;
            }
        }
        public void Error(string message) => LogMessage(message, LogLevel.Error);
        public void Warning(string message) => LogMessage(message, LogLevel.Warning);
        public void Info(string message) => LogMessage(message, LogLevel.Info);
        public void Debug(string message) => LogMessage(message, LogLevel.Debug);
        public void Trace(string message) => LogMessage(message, LogLevel.Trace);
        public string BuildScope(Symbol symbol, int? tradeId = null)
        {
            return $"{symbol.Value}:{tradeId?.ToString() ?? "_"}";
        }
        public void AddHighlightMetric(string scopeKey, string key, object value, int? decimalPlaces = null)
        {
            if (string.IsNullOrEmpty(scopeKey) || string.IsNullOrEmpty(key))
                return;
            var formattedValue = FormatMetricValue(value, decimalPlaces);
            var metrics = new Dictionary<string, string> { { key, formattedValue } };
            SmartLoggerStore.MergeMetrics(scopeKey, metrics);
        }
        public void AddHighlightMetrics(string scopeKey, IDictionary<string, object> metrics, int? decimalPlaces = null)
        {
            if (string.IsNullOrEmpty(scopeKey) || metrics == null || metrics.Count == 0)
                return;
            var formatted = new Dictionary<string, string>();
            foreach (var kvp in metrics)
            {
                formatted[kvp.Key] = FormatMetricValue(kvp.Value, decimalPlaces);
            }
            SmartLoggerStore.MergeMetrics(scopeKey, formatted);
        }
        public void ClearHighlightMetrics(string scopeKey)
        {
            SmartLoggerStore.ClearHighlightMetrics(scopeKey);
        }
        public void HighlightTradeEvent(string className, string methodName, HighlightKind kind, string oneLine, string scopeKey = null)
        {
            var highlightEnabled = !bool.TryParse(_algorithm.GetParameter("HighlightTrades", "true"), out var hlEnabled) || hlEnabled;
            if (!highlightEnabled)
                return;
            var immediateEnabled = !bool.TryParse(_algorithm.GetParameter("HighlightImmediate", "true"), out var immEnabled) || immEnabled;
            var maxImmediate = int.TryParse(_algorithm.GetParameter("MaxHighlightImmediate", "80"), out var parsedImm) ? parsedImm : 80;
            var includeMetrics = !bool.TryParse(_algorithm.GetParameter("HighlightIncludeStrategyMetrics", "true"), out var inclMetrics) || inclMetrics;
            var finalLine = oneLine;
            if (includeMetrics && !string.IsNullOrEmpty(scopeKey) &&
                SmartLoggerStore.TryGetAndClearMetrics(scopeKey, out var metrics))
            {
                var suffix = CoreAlgo.Architecture.Core.Services.TradeHighlightFormatter.FormatMetricsSuffix(metrics);
                if (!string.IsNullOrEmpty(suffix))
                {
                    finalLine = $"{oneLine} {suffix}";
                }
            }
            var msg = new SmartLogMessage("INFO", className, methodName, finalLine, _algorithm.Time);
            if (immediateEnabled && !_verboseMode)
            {
                var shouldPrintImmediate = SmartLoggerStore.ShouldPrintImmediateHighlight(_algorithm.Time, maxImmediate);
                if (shouldPrintImmediate)
                {
                    var realTimeMessage = FormatRealTimeMessage(finalLine, className, methodName, LogLevel.Info);
                    _algorithm.Log(realTimeMessage);
                }
            }
            SmartLoggerStore.AddHighlight(msg);
        }
        private string FormatMetricValue(object value, int? decimalPlaces)
        {
            if (value == null)
                return "null";
            if (value is decimal d)
                return d.ToString(decimalPlaces.HasValue ? $"F{decimalPlaces.Value}" : "F2");
            if (value is double dbl)
                return dbl.ToString(decimalPlaces.HasValue ? $"F{decimalPlaces.Value}" : "F2");
            if (value is float f)
                return f.ToString(decimalPlaces.HasValue ? $"F{decimalPlaces.Value}" : "F2");
            if (value is int || value is long || value is short)
                return value.ToString();
            if (value is bool b)
                return b ? "T" : "F";
            var str = value.ToString();
            if (str.Length > 20)
                str = str.Substring(0, 17) + "...";
            return str;
        }
        public void LogWithContext(string message, string className, string methodName, LogLevel level = LogLevel.Info)
        {
            if ((int)level > _currentLogLevel)
                return;
            if (string.IsNullOrEmpty(className) || className == "SimpleBaseStrategy")
            {
                var (dynClass, dynMethod) = GetCallerContextDynamic();
                if (dynClass != "Unknown")
                {
                    className = dynClass;
                    methodName = dynMethod;
                }
            }
            if (_verboseMode)
            {
                var formattedMessage = FormatRealTimeMessage(message, className, methodName, level);
                _algorithm.Log(formattedMessage);
                return;
            }
            bool shouldOutputImmediate = level <= LogLevel.Warning || // ERROR/WARNING always immediate
                                        (_verboseMode && level == LogLevel.Info);
            if (shouldOutputImmediate)
            {
                var formattedMessage = FormatRealTimeMessage(message, className, methodName, level);
                _algorithm.Log(formattedMessage);
            }
            if (!shouldOutputImmediate || level == LogLevel.Debug || level == LogLevel.Trace)
            {
                StoreSmartMessageWithContext(level.ToString().ToUpper(), message, className, methodName);
            }
        }
        private void StoreSmartMessage(string level, string message)
        {
            if (_verboseMode)
                return;
            var functionName = GetCallingFunctionName();
            var smartMessage = new SmartLogMessage(level, _categoryName, functionName, message, _algorithm.Time);
            if ((level == "DEBUG" || level == "TRACE") && SmartLoggerStore.ShouldThrottleMessage(smartMessage.BaseHash, _algorithm.Time))
            {
                return; // Skip this message due to throttling
            }
            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
                    }
                }
            }
            var messageList = SmartLoggerStore.DailyMessages.GetOrAdd(smartMessage.Hash, _ => new List<SmartLogMessage>());
            lock (messageList)
            {
                messageList.Add(smartMessage);
            }
        }
        private void StoreSmartMessageWithContext(string level, string message, string className, string methodName)
        {
            if (_verboseMode)
                return;
            var smartMessage = new SmartLogMessage(level, className, methodName, message, _algorithm.Time);
            if ((level == "DEBUG" || level == "TRACE") && SmartLoggerStore.ShouldThrottleMessage(smartMessage.BaseHash, _algorithm.Time))
            {
                return; // Skip this message due to throttling
            }
            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
                    }
                }
            }
            var messageList = SmartLoggerStore.DailyMessages.GetOrAdd(smartMessage.Hash, _ => new List<SmartLogMessage>());
            lock (messageList)
            {
                messageList.Add(smartMessage);
            }
        }
        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");
            if (_verboseMode)
            {
                return $"{timestamp} [{className}.{methodName}] {level.ToString().ToUpper()}: {message}";
            }
            else
            {
                return $"{timestamp} (Real: {realTime}) [{className}.{methodName}] {level.ToString().ToUpper()}: {message}";
            }
        }
        private static string GetCallingFunctionName()
        {
            try
            {
                var stackTrace = new System.Diagnostics.StackTrace();
                var frame = stackTrace.GetFrame(3);
                return frame?.GetMethod()?.Name ?? "Unknown";
            }
            catch
            {
                return "Unknown";
            }
        }
        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;
                    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 (typeName == "SimpleBaseStrategy")
                    {
                        continue;
                    }
                    return (typeName, methodName);
                }
                return ("Unknown", "Unknown");
            }
            catch
            {
                return ("Unknown", "Unknown");
            }
        }
        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;
                    if (IsWrapperMethod(typeName, methodName))
                        continue;
                    if (typeName.EndsWith("Template") && !typeName.Contains("<"))
                        return (typeName, methodName);
                    if (typeName != "SimpleBaseStrategy" &&
                        !typeName.Contains("Logger") &&
                        !typeName.Contains("Algorithm") &&
                        !typeName.Contains("System"))
                    {
                        return (typeName, methodName);
                    }
                }
                return ("Unknown", "Unknown");
            }
            catch
            {
                return ("Unknown", "Unknown");
            }
        }
        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
            {
                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
            {
                formatted = message;
                if (args != null && args.Length > 0)
                {
                    formatted += " [" + string.Join(", ", args) + "]";
                }
            }
            return formatted;
        }
    }
}
using System;
using System.Collections.Concurrent;
using System.Collections.Generic;
using System.Linq;
using System.Runtime.CompilerServices;
using QuantConnect;
using QuantConnect.Algorithm;
using QuantConnect.Data;
using QuantConnect.Data.UniverseSelection;
using QuantConnect.Securities;
using QuantConnect.Securities.Option;
using CoreAlgo.Architecture.Core.Models;
using CoreAlgo.Architecture.Core.Services;
using CoreAlgo.Architecture.Core.Helpers;
using CoreAlgo.Architecture.Core.Configuration;
using CoreAlgo.Architecture.QC.Helpers;
namespace CoreAlgo.Architecture.Core.Implementations
{
    public partial class SimpleBaseStrategy
    {
        protected void Log(string message) => Logger.Info(message);
        protected void Error(string message) => Logger.Error(message);
        protected Security AddEquity(string ticker, Resolution resolution = Resolution.Minute) => Algorithm.AddEquity(ticker, resolution);
        protected Security AddOption(string underlying, Resolution resolution = Resolution.Minute) => Algorithm.AddOption(underlying, resolution);
        protected void SetStartDate(DateTime date) => Algorithm.SetStartDate(date);
        protected void SetEndDate(DateTime date) => Algorithm.SetEndDate(date);
        protected void SetCash(decimal cash) => Algorithm.SetCash(cash);
        protected void Debug(string message) => Logger.Debug(message);
        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 (Exception)
            {
                return defaultValue;
            }
        }
        protected void SmartLog(string message,
            [CallerMemberName] string memberName = "",
            [CallerFilePath] string sourceFilePath = "")
        {
            var className = ExtractClassNameFromPath(sourceFilePath);
            if (string.IsNullOrEmpty(className))
                className = this.GetType().Name;
            Logger.LogWithContext(message, className, memberName, LogLevel.Info);
        }
        protected void SmartWarn(string message,
            [CallerMemberName] string memberName = "",
            [CallerFilePath] string sourceFilePath = "")
        {
            var className = ExtractClassNameFromPath(sourceFilePath);
            if (string.IsNullOrEmpty(className))
                className = this.GetType().Name;
            Logger.LogWithContext(message, className, memberName, LogLevel.Warning);
        }
        protected void SmartDebug(string message,
            [CallerMemberName] string memberName = "",
            [CallerFilePath] string sourceFilePath = "")
        {
            var className = ExtractClassNameFromPath(sourceFilePath);
            if (string.IsNullOrEmpty(className))
                className = this.GetType().Name;
            Logger.LogWithContext(message, className, memberName, LogLevel.Debug);
        }
        protected void SmartTrace(string message,
            [CallerMemberName] string memberName = "",
            [CallerFilePath] string sourceFilePath = "")
        {
            var className = ExtractClassNameFromPath(sourceFilePath);
            if (string.IsNullOrEmpty(className))
                className = this.GetType().Name;
            Logger.LogWithContext(message, className, memberName, LogLevel.Trace);
        }
        protected void SmartError(string message,
            [CallerMemberName] string memberName = "",
            [CallerFilePath] string sourceFilePath = "")
        {
            var className = ExtractClassNameFromPath(sourceFilePath);
            if (string.IsNullOrEmpty(className))
                className = this.GetType().Name;
            Logger.LogWithContext(message, className, memberName, LogLevel.Error);
        }
        private string ExtractClassNameFromPath(string filePath)
        {
            if (string.IsNullOrEmpty(filePath))
                return "";
            try
            {
                var lastSlash = Math.Max(filePath.LastIndexOf('/'), filePath.LastIndexOf('\\'));
                var fileName = lastSlash >= 0 ? filePath.Substring(lastSlash + 1) : filePath;
                if (fileName.EndsWith(".cs"))
                    fileName = fileName.Substring(0, fileName.Length - 3);
                return fileName;
            }
            catch (Exception)
            {
                return this.GetType().Name;
            }
        }
        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);
        }
        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}");
            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()}");
            var distinctResolutions = option.Subscriptions
                .Select(s => s.Resolution)
                .Distinct()
                .OrderBy(r => r)
                .ToList();
            SmartLog($"   All subscription resolutions: {string.Join(", ", distinctResolutions)}");
            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}");
            }
        }
        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));
        }
        protected virtual void OnNewTradingDay(Slice slice) { }
        protected virtual void OnPreExecuteAlways(Slice slice)
        {
        }
        protected Dictionary<Symbol, List<decimal>> BatchFetchHistory(IEnumerable<Symbol> symbols,
            int days, Resolution resolution = Resolution.Daily, int batchSize = 500)
        {
            return UniverseOptimizer.BatchFetchHistory(Algorithm, symbols, days, resolution, batchSize);
        }
        protected Dictionary<Symbol, decimal> CalculateBatchedADV(IEnumerable<Symbol> symbols, int days = 21)
        {
            return UniverseOptimizer.CalculateBatchedADV(Algorithm, symbols, days);
        }
        protected Dictionary<Symbol, decimal> CalculateVolumeShock(ConcurrentDictionary<Symbol, long> intradayVolumes,
            IEnumerable<Symbol> symbols, int advDays = 21)
        {
            return UniverseOptimizer.CalculateVolumeShock(Algorithm, intradayVolumes, symbols, advDays);
        }
        protected void CleanupRemovedSecurities(SecurityChanges changes, params object[] trackingDictionaries)
        {
            UniverseOptimizer.CleanupRemovedSecurities(Algorithm, changes, trackingDictionaries);
        }
    }
}
using System;
using System.Collections.Generic;
using System.Linq;
using QuantConnect;
using QuantConnect.Orders;
using CoreAlgo.Architecture.Core.Models;
using CoreAlgo.Architecture.QC.Helpers;
using CoreAlgo.Architecture.Core.Configuration;
using CoreAlgo.Architecture.Core.Helpers;
namespace CoreAlgo.Architecture.Core.Implementations
{
    public partial class SimpleBaseStrategy
    {
        protected void TrackWorkingOrder(OrderTicket ticket, string strategy = "")
        {
            if (ticket?.OrderId != null)
            {
                if (!string.IsNullOrEmpty(ticket.Tag))
                {
                    var parsedTag = TradeTag.Parse(ticket.Tag);
                    if (parsedTag.IsExit() || parsedTag.IsStopLoss() || parsedTag.IsTakeProfit() || parsedTag.IsScaleIn())
                    {
                        Debug($"Skipping tracking for child order: {ticket.OrderId} (Tag: {ticket.Tag})");
                        return;
                    }
                }
                var orderId = ticket.OrderId.ToString();
                var symbol = ticket.Symbol?.Value ?? "Unknown";
                var strategyName = !string.IsNullOrEmpty(strategy) ? strategy : Name;
                var trade = new TradeRecord(orderId, symbol, strategyName, ticket.Tag);
                if (ticket.Symbol != null)
                {
                    trade.SymbolIds.Add(ticket.Symbol.ID.ToString());
                    if (ticket.Symbol.SecurityType == SecurityType.Option && ticket.Symbol.Underlying != null)
                    {
                        trade.Underlying = ticket.Symbol.Underlying.Value;
                    }
                }
                TradeTracker.AllTrades.Add(trade);
                TradeTracker.WorkingTrades.Add(trade);
                Debug($"Tracking working order: {orderId} for {symbol}");
                TradePersistence.SaveTrades(TradeTracker);
            }
        }
        protected TradeRecord TrackComboOrder(List<OrderTicket> tickets, List<string> symbolIds, string strategy = "", string tag = "")
        {
            if (tickets == null || tickets.Count == 0)
            {
                return null;
            }
            var sortedTickets = tickets.OrderBy(t => t.OrderId).ToList();
            var primaryTicket = sortedTickets.FirstOrDefault();
            if (primaryTicket?.OrderId == null)
            {
                return null;
            }
            var orderId = primaryTicket.OrderId.ToString();
            var symbol = primaryTicket.Symbol?.Value ?? "Unknown";
            var strategyName = !string.IsNullOrEmpty(strategy) ? strategy : Name;
            var algorithmTime = Algorithm?.Time ?? DateTime.MinValue;
            TradeRecord trade = null;
            foreach (var ticket in sortedTickets)
            {
                if (ticket?.OrderId != null)
                {
                    var existingTrade = TradeTracker.FindTradeByAnyOrderId(ticket.OrderId);
                    if (existingTrade != null)
                    {
                        trade = existingTrade;
                        SmartLog($"[TRACK COMBO] Race condition resolved: Using existing trade {trade.OrderId} bootstrapped from OnOrderFilled");
                        break;
                    }
                }
            }
            if (trade == null)
            {
                var workingTradesBefore = TradeTracker.WorkingTrades.Count;
                trade = new TradeRecord(orderId, symbol, strategyName, tag);
                TradeTracker.AllTrades.Add(trade);
                TradeTracker.WorkingTrades.Add(trade);
                var workingTradesAfter = TradeTracker.WorkingTrades.Count;
                SmartLog($"[TRACK COMBO] @ {algorithmTime:HH:mm:ss.fff} | Created new trade | Primary={orderId}, Strategy={strategyName}, Tag='{tag}' | " +
                        $"WorkingTrades: {workingTradesBefore}→{workingTradesAfter}");
            }
            else
            {
                if (string.IsNullOrEmpty(trade.Strategy) || trade.Strategy == Name) // Only update if generic or empty
                {
                    trade.Strategy = strategyName;
                }
                if (!string.IsNullOrEmpty(tag))
                {
                    trade.OrderTag = tag;
                }
            }
            foreach (var ticket in tickets)
            {
                if (ticket?.OrderId != null && !trade.OrderIds.Contains(ticket.OrderId))
                {
                    trade.OrderIds.Add(ticket.OrderId);
                }
            }
            if (symbolIds != null)
            {
                trade.SymbolIds.Clear(); // Clear potential partial/unordered symbols from bootstrap
                foreach (var symbolId in symbolIds)
                {
                    if (!string.IsNullOrEmpty(symbolId))
                    {
                        trade.SymbolIds.Add(symbolId);
                    }
                }
            }
            if (string.IsNullOrEmpty(trade.Underlying))
            {
                foreach (var ticket in tickets)
                {
                    if (ticket?.Symbol?.SecurityType == SecurityType.Option && ticket.Symbol.Underlying != null)
                    {
                        trade.Underlying = ticket.Symbol.Underlying.Value;
                        break;
                    }
                }
            }
            SmartLog($"[TRACK COMBO] @ {algorithmTime:HH:mm:ss.fff} | Trade={trade.OrderId} Fully Configured | " +
                    $"All OrderIDs=[{string.Join(", ", trade.OrderIds.OrderBy(id => id))}], Legs={trade.OrderIds.Count}, Symbols={trade.SymbolIds.Count}");
            TradePersistence.SaveTrades(TradeTracker);
            return trade;
        }
        public void TrackOrderFilled(OrderEvent orderEvent)
        {
            if (orderEvent?.OrderId != null)
            {
                var orderId = orderEvent.OrderId;
                var algorithmTime = Algorithm?.Time ?? DateTime.MinValue;
                var trade = TradeTracker.FindTradeByAnyOrderId(orderId);
                if (trade == null)
                {
                    var orderTicket = Algorithm?.Transactions?.GetOrderTicket(orderId);
                    var orderTag = orderTicket?.Tag ?? string.Empty;
                    if (string.IsNullOrEmpty(orderTag))
                    {
                        OnOrderFilled(orderEvent);
                        NotifyTradeAnalyzer(orderEvent);
                        return;
                    }
                    var symbolValue = orderEvent.Symbol?.Value ?? "Unknown";
                    trade = TryBootstrapComboTrade(orderEvent, orderTicket);
                    if (trade != null)
                    {
                        SmartLog($"[TRADE TRACKING BOOTSTRAP] Created trade {trade.OrderId} from OrderEvent {orderId} (Tag='{orderTag}')");
                    }
                    else
                    {
                        SmartWarn(
                            $"[TRADE TRACKING] @ {algorithmTime:HH:mm:ss.fff} | Filled order {orderId} ({symbolValue}) Tag='{orderTag}' " +
                            $"could not be mapped. AllTrades={TradeTracker.AllTrades.Count}, Open={TradeTracker.OpenTrades.Count}");
                    }
                    if (trade == null && orderTicket?.Tag != null)
                    {
                        var parsedTag = TradeTag.Parse(orderTicket.Tag);
                        if ((parsedTag.IsStopLoss() || parsedTag.IsExit()) && parsedTag.TryGetParentEid(out int parentEid))
                        {
                            var parentTrade = TradeTracker.AllTrades.FirstOrDefault(t => t.OrderId == parentEid.ToString());
                            if (parentTrade != null && parentTrade.Status == "Open")
                            {
                                var closePrice = orderEvent.FillPrice;
                                var pnl = (closePrice - parentTrade.OpenPrice) * parentTrade.Quantity;
                                var closeTime = Algorithm?.UtcTime ?? DateTime.UtcNow;
                                TradeTracker.MarkTradeAsClosed(parentTrade.OrderId, closePrice, pnl, closeTime);
                                TradePersistence.SaveTrades(TradeTracker);
                                var exitType = parsedTag.IsEod() ? "EOD" : "Fallback";
                                SmartLog($"[{exitType} CLOSE] Force-closed parent E{parentEid} via unmapped fill {orderId} | " +
                                        $"Tag='{orderTicket.Tag}' | FillPrice: {closePrice:F2} | PnL: {pnl:F2}");
                                trade = parentTrade;  // Set for downstream processing
                            }
                            else if (parentTrade != null)
                            {
                                SmartDebug($"[PARENT EID] Found parent E{parentEid} but status is '{parentTrade.Status}' (not Open), skipping close");
                            }
                            else
                            {
                                SmartWarn($"[PARENT EID] No trade found for parent E{parentEid} parsed from tag '{orderTicket.Tag}'");
                            }
                        }
                    }
                }
                else
                {
                    SmartDebug($"[TRADE TRACKING] @ {algorithmTime:HH:mm:ss.fff} | Filled order {orderId} mapped to trade {trade.OrderId} (registered IDs: [{string.Join(", ", trade.OrderIds)}])");
                }
                if (trade != null && orderEvent.Symbol != null)
                {
                    var symbolId = orderEvent.Symbol.ID.ToString();
                    if (!trade.SymbolIds.Contains(symbolId))
                    {
                        trade.SymbolIds.Add(symbolId);
                    }
                    if (string.IsNullOrEmpty(trade.Symbol) || trade.Symbol == "Unknown")
                    {
                        trade.Symbol = orderEvent.Symbol.Value;
                    }
                    if (string.IsNullOrEmpty(trade.Underlying) &&
                        orderEvent.Symbol.SecurityType == SecurityType.Option &&
                        orderEvent.Symbol.Underlying != null)
                    {
                        trade.Underlying = orderEvent.Symbol.Underlying.Value;
                    }
                }
                if (trade != null && trade.Status != "Open")
                {
                    TradeTracker.MarkTradeAsOpen(trade.OrderId, orderEvent.FillPrice, (int)orderEvent.FillQuantity, Algorithm.UtcTime);
                    Debug($"Order filled: {orderId} at {orderEvent.FillPrice}, trade {trade.OrderId} marked as Open");
                }
                else if (trade == null)
                {
                    Debug($"Order filled: {orderId} at {orderEvent.FillPrice}, but no trade found (likely child order)");
                }
                OnOrderFilled(orderEvent);
                NotifyTradeAnalyzer(orderEvent);
                TradePersistence.SaveTrades(TradeTracker);
            }
        }
        private TradeRecord TryBootstrapComboTrade(OrderEvent orderEvent, OrderTicket orderTicket)
        {
            if (orderTicket == null || string.IsNullOrEmpty(orderTicket.Tag))
            {
                return null;
            }
            var tag = orderTicket.Tag;
            var orderId = orderEvent.OrderId;
            var parsedTag = TradeTag.Parse(tag);
            var isCloseTag = parsedTag.IsExit();
            if (isCloseTag)
            {
                var today = Algorithm.Time.Date;
                var closeTickets = Algorithm.Transactions.GetOrderTickets()
                    .Where(t => t != null &&
                               t.Tag == tag &&
                               t.Time.Date == today)
                    .ToList();
                var closeSymbolIds = new HashSet<string>();
                foreach (var ticket in closeTickets)
                {
                    if (ticket?.Symbol != null)
                    {
                        closeSymbolIds.Add(ticket.Symbol.ID.ToString());
                    }
                }
                if (closeSymbolIds.Count > 0)
                {
                    var matchingTrade = TradeTracker.OpenTrades
                        .FirstOrDefault(t => t.SymbolIds.Any(sid => closeSymbolIds.Contains(sid)));
                    if (matchingTrade == null)
                    {
                        matchingTrade = TradeTracker.WorkingTrades
                            .FirstOrDefault(t => t.SymbolIds.Any(sid => closeSymbolIds.Contains(sid)));
                    }
                    if (matchingTrade != null)
                    {
                        foreach (var ticket in closeTickets)
                        {
                            if (ticket?.OrderId != null && !matchingTrade.OrderIds.Contains(ticket.OrderId))
                            {
                                matchingTrade.OrderIds.Add(ticket.OrderId);
                            }
                        }
                        SmartLog($"[BOOTSTRAP CLOSE] Mapped exit OrderIDs [{string.Join(", ", closeTickets.Where(t => t != null).Select(t => t.OrderId))}] " +
                                $"to existing {matchingTrade.Status} trade {matchingTrade.OrderId} (Tag='{tag}')");
                        return matchingTrade;
                    }
                }
                SmartLog($"[BOOTSTRAP CLOSE] WARNING: No matching Open trade found for close tag '{tag}', creating fallback trade");
            }
            var isComboEntryTag = parsedTag.IsEntry() || (parsedTag.Strategy.Equals(Name, StringComparison.OrdinalIgnoreCase) && !isCloseTag);
            if (!isComboEntryTag)
            {
                return null;
            }
            var today2 = Algorithm.Time.Date;
            var siblingTickets = Algorithm.Transactions.GetOrderTickets()
                .Where(t => t != null &&
                           t.Tag == tag &&
                           t.Time.Date == today2 &&
                           t.OrderId != orderId) // Exclude current order
                .OrderBy(t => t.OrderId)
                .ToList();
            var allLegTickets = new List<OrderTicket> { orderTicket };
            allLegTickets.AddRange(siblingTickets);
            foreach (var ticket in allLegTickets)
            {
                var existingTrade = TradeTracker.FindTradeByAnyOrderId(ticket.OrderId);
                if (existingTrade != null)
                {
                    if (!existingTrade.OrderIds.Contains(orderId))
                    {
                        existingTrade.OrderIds.Add(orderId);
                    }
                    return existingTrade;
                }
            }
            var primaryTicket = allLegTickets.OrderBy(t => t.OrderId).First();
            var primaryOrderId = primaryTicket.OrderId.ToString();
            var symbol = orderEvent.Symbol?.Value ?? primaryTicket.Symbol?.Value ?? "Unknown";
            var strategyName = Name;
            var trade = new TradeRecord(primaryOrderId, symbol, strategyName, tag);
            foreach (var ticket in allLegTickets)
            {
                if (ticket?.OrderId != null && !trade.OrderIds.Contains(ticket.OrderId))
                {
                    trade.OrderIds.Add(ticket.OrderId);
                }
                if (ticket?.Symbol != null)
                {
                    var symbolId = ticket.Symbol.ID.ToString();
                    if (!trade.SymbolIds.Contains(symbolId))
                    {
                        trade.SymbolIds.Add(symbolId);
                    }
                    if (string.IsNullOrEmpty(trade.Underlying) &&
                        ticket.Symbol.SecurityType == SecurityType.Option &&
                        ticket.Symbol.Underlying != null)
                    {
                        trade.Underlying = ticket.Symbol.Underlying.Value;
                    }
                }
            }
            TradeTracker.AllTrades.Add(trade);
            TradeTracker.WorkingTrades.Add(trade);
            SmartLog($"[BOOTSTRAP COMBO] Created trade {trade.OrderId} from {allLegTickets.Count} legs " +
                    $"(OrderIDs=[{string.Join(", ", trade.OrderIds.OrderBy(id => id))}], " +
                    $"SymbolIDs={trade.SymbolIds.Count}, Tag='{tag}')");
            return trade;
        }
        protected void RegisterChildOrder(int parentEntryOrderId, int childOrderId)
        {
            var trade = TradeTracker.FindTradeByAnyOrderId(parentEntryOrderId);
            if (trade != null && !trade.OrderIds.Contains(childOrderId))
            {
                trade.OrderIds.Add(childOrderId);
            }
        }
        protected virtual void OnOrderFilled(OrderEvent orderEvent) { }
        protected void NotifyTradeAnalyzer(OrderEvent orderEvent)
        {
            if (!Algorithm.LiveMode) return;
            var analyzerUrl = Algorithm.GetParameter("TradeAnalyzerWebhookUrl", "");
            if (string.IsNullOrEmpty(analyzerUrl)) return;
            try
            {
                var orderTicket = Algorithm.Transactions.GetOrderTicket(orderEvent.OrderId);
                var tag = orderTicket?.Tag ?? "";
                var payload = new System.Collections.Generic.Dictionary<string, object>
                {
                    { "type", "order_filled" },
                    { "strategy", Name },
                    { "orderId", orderEvent.OrderId },
                    { "symbol", orderEvent.Symbol?.Value ?? "UNKNOWN" },
                    { "quantity", orderEvent.FillQuantity },
                    { "price", orderEvent.FillPrice },
                    { "time", Algorithm.Time.ToString("o") },
                    { "tag", tag }
                };
                var authUser = Algorithm.GetParameter("TradeAnalyzerAuthUser", "");
                var authPass = Algorithm.GetParameter("TradeAnalyzerAuthPass", "");
                var headers = new System.Collections.Generic.Dictionary<string, string>();
                if (!string.IsNullOrEmpty(authUser) && !string.IsNullOrEmpty(authPass))
                {
                    var authString = $"{authUser}:{authPass}";
                    var authBytes = System.Text.Encoding.UTF8.GetBytes(authString);
                    var authBase64 = System.Convert.ToBase64String(authBytes);
                    headers["Authorization"] = $"Basic {authBase64}";
                }
                var jsonPayload = System.Text.Json.JsonSerializer.Serialize(payload);
                Algorithm.Notify.Web(analyzerUrl, jsonPayload, headers);
                SmartDebug($"[TRADE ANALYZER] Sent notification for order {orderEvent.OrderId}");
            }
            catch (Exception ex)
            {
                SmartWarn($"[TRADE ANALYZER] Failed to send notification: {ex.Message}");
            }
        }
        public virtual void OnOrderEventRouted(OrderEvent orderEvent) { }
        protected void TrackPositionClosed(string orderId, decimal closePrice, decimal pnl)
        {
            var closeTime = Algorithm?.UtcTime ?? DateTime.UtcNow;
            TradeTracker.MarkTradeAsClosed(orderId, closePrice, pnl, closeTime);
            var tag = "";
            if (int.TryParse(orderId, out var parsedOrderId))
            {
                var trade = TradeTracker.FindTradeByAnyOrderId(parsedOrderId);
                tag = trade != null && !string.IsNullOrEmpty(trade.OrderTag) ? $" (Entry: {trade.OrderTag})" : "";
            }
            Debug($"Position closed: {orderId}{tag}, P&L: {pnl}");
            TradePersistence.SaveTrades(TradeTracker);
        }
        public void TrackOrderCancelled(string orderId)
        {
            TradeTracker.CancelWorkingTrade(orderId);
            Debug($"Order cancelled: {orderId}");
            TradePersistence.SaveTrades(TradeTracker);
        }
        public virtual void OnAssignmentOrderEvent(OrderEvent assignmentEvent)
        {
            try
            {
                SmartLog($"Assignment event received: {assignmentEvent.Symbol} at {Algorithm.Time}");
                AssignmentHandler.HandleAssignment(Algorithm, assignmentEvent);
                OnAssignmentHandled(assignmentEvent);
            }
            catch (Exception ex)
            {
                SmartError($"Error in assignment handling: {ex.Message}");
                OnError(ex, ErrorSeverity.Error, true, "Assignment handling error");
            }
        }
        protected virtual void OnAssignmentHandled(OrderEvent assignmentEvent)
        {
            SmartLog($"Assignment handling completed for {assignmentEvent.Symbol}");
        }
    }
}
using System;
using System.Collections.Generic;
using System.Linq;
using QuantConnect;
using QuantConnect.Algorithm;
using QuantConnect.Algorithm.Framework.Portfolio;
using QuantConnect.Orders;
using QuantConnect.Securities;
using CoreAlgo.Architecture.QC.Helpers;
using CoreAlgo.Architecture.Core.Configuration;
using CoreAlgo.Architecture.Core.Services;
using CoreAlgo.Architecture.Core.Models;
using CoreAlgo.Architecture.Core.Execution;
namespace CoreAlgo.Architecture.Core.Implementations
{
    public partial class SimpleBaseStrategy
    {
        protected OrderTicket MarketOrder(Symbol symbol, decimal quantity, string tag = "")
        {
            OrderTicket ticket;
            if (ShouldUseSmartOrderManager())
            {
                ticket = SmartOrderManager.SmartMarketOrder(symbol, quantity, tag);
            }
            else
            {
                ticket = Algorithm.MarketOrder(symbol, quantity, tag: tag, asynchronous: true);
            }
            if (ticket != null && ticket.Status != OrderStatus.Invalid)
            {
                TrackWorkingOrder(ticket, Name);
            }
            return ticket;
        }
        protected OrderTicket LimitOrder(Symbol symbol, decimal quantity, decimal limitPrice, string tag = "")
        {
            var rounded = PriceRounding.RoundLimitPrice(Algorithm.Securities, symbol, quantity, limitPrice);
            var ticket = Algorithm.LimitOrder(symbol, quantity, rounded, tag: tag, asynchronous: true);
            if (ticket != null && ticket.Status != OrderStatus.Invalid)
            {
                TrackWorkingOrder(ticket, Name);
            }
            return ticket;
        }
        protected OrderTicket StopMarketOrder(Symbol symbol, decimal quantity, decimal stopPrice, string tag = "")
        {
            var rounded = PriceRounding.RoundStopPrice(Algorithm.Securities, symbol, quantity, stopPrice);
            var ticket = Algorithm.StopMarketOrder(symbol, quantity, rounded, tag: tag, asynchronous: true);
            if (ticket != null && ticket.Status != OrderStatus.Invalid)
            {
                TrackWorkingOrder(ticket, Name);
            }
            return ticket;
        }
        protected OrderTicket StopLimitOrder(Symbol symbol, decimal quantity, decimal stopPrice, decimal limitPrice, string tag = "")
        {
            OrderTicket ticket;
            try
            {
                var (roundedStop, roundedLimit) = PriceRounding.RoundStopLimitPrices(Algorithm.Securities, symbol, quantity, stopPrice, limitPrice);
                if (quantity < 0) // Sell stop-limit
                {
                    if (roundedLimit >= roundedStop)
                    {
                        SmartWarn($"[STOP-LIMIT INVALID] Sell: limit {roundedLimit} >= stop {roundedStop}. Falling back to StopMarketOrder.");
                        ticket = Algorithm.StopMarketOrder(symbol, quantity, roundedStop, tag: tag, asynchronous: true);
                    }
                    else
                    {
                        ticket = Algorithm.StopLimitOrder(symbol, quantity, roundedStop, roundedLimit, tag: tag, asynchronous: true);
                    }
                }
                else // Buy stop-limit
                {
                    if (roundedLimit <= roundedStop)
                    {
                        SmartWarn($"[STOP-LIMIT INVALID] Buy: limit {roundedLimit} <= stop {roundedStop}. Falling back to StopMarketOrder.");
                        ticket = Algorithm.StopMarketOrder(symbol, quantity, roundedStop, tag: tag, asynchronous: true);
                    }
                    else
                    {
                        ticket = Algorithm.StopLimitOrder(symbol, quantity, roundedStop, roundedLimit, tag: tag, asynchronous: true);
                    }
                }
            }
            catch (ArgumentException ex)
            {
                SmartError($"[STOP-LIMIT ERROR] {ex.Message}. Falling back to StopMarketOrder at {stopPrice:F4}.");
                ticket = Algorithm.StopMarketOrder(symbol, quantity, stopPrice, tag: tag, asynchronous: true);
            }
            if (ticket != null && ticket.Status != OrderStatus.Invalid)
            {
                TrackWorkingOrder(ticket, Name);
            }
            return ticket;
        }
        protected OrderTicket TrailingStopOrder(Symbol symbol, decimal quantity, decimal trailingAmount, bool isDollarTrailing, string tag = "")
        {
            var rounded = PriceRounding.RoundTrailingStopPrice(Algorithm.Securities, symbol, quantity, trailingAmount, isDollarTrailing);
            var ticket = Algorithm.TrailingStopOrder(symbol, quantity, rounded, isDollarTrailing, tag: tag, asynchronous: true);
            if (ticket != null && ticket.Status != OrderStatus.Invalid)
            {
                TrackWorkingOrder(ticket, Name);
            }
            return ticket;
        }
        protected List<OrderTicket> ComboMarketOrder(List<Leg> legs, int quantity, string tag = "")
		{
			List<OrderTicket> tickets;
			if (ShouldUseSmartOrderManager())
			{
				tickets = SmartOrderManager.SmartComboMarketOrder(legs, quantity, tag);
			}
			else
			{
				tickets = Algorithm.ComboMarketOrder(legs, quantity, tag: tag, asynchronous: true);
			}
			if (tickets != null && tickets.Count > 0 && tickets.Any(t => t != null && t.Status != OrderStatus.Invalid))
			{
				var symbolIds = legs?.Select(leg => leg.Symbol?.ID.ToString())
					.Where(id => !string.IsNullOrEmpty(id))
					.ToList() ?? new List<string>();
				TrackComboOrder(tickets, symbolIds, Name, tag);
			}
			return tickets;
		}
        protected OrderTicket MarketOnOpenOrder(Symbol symbol, int quantity, string tag = "")
        {
            var ticket = Algorithm.MarketOnOpenOrder(symbol, quantity, tag: tag, asynchronous: true);
            if (ticket != null && ticket.Status != OrderStatus.Invalid)
            {
                TrackWorkingOrder(ticket, Name);
            }
            return ticket;
        }
        protected OrderTicket MarketOnCloseOrder(Symbol symbol, int quantity, string tag = "")
        {
            var ticket = Algorithm.MarketOnCloseOrder(symbol, quantity, tag: tag, asynchronous: true);
            if (ticket != null && ticket.Status != OrderStatus.Invalid)
            {
                TrackWorkingOrder(ticket, Name);
            }
            return ticket;
        }
        protected List<OrderTicket> ComboLimitOrder(List<Leg> legs, int quantity, decimal limitPrice, string tag = "")
        {
            try
            {
                return Algorithm.ComboLimitOrder(legs, quantity, limitPrice, tag: tag, asynchronous: true);
            }
            catch (Exception)
            {
                return Algorithm.ComboLimitOrder(legs, quantity, limitPrice, tag: tag);
            }
        }
        protected OrderTicket Liquidate(Symbol symbol, string tag = "")
        {
            dynamic result = Algorithm.Liquidate(symbol: symbol, tag: tag, asynchronous: true);
            if (result is List<OrderTicket> tickets && tickets.Count > 0)
            {
                return tickets[0];
            }
            return result as OrderTicket ?? null;
        }
        protected void LiquidateAll(string tag = "")
        {
            Algorithm.Liquidate(asynchronous: true);
        }
        protected void SetHoldingsTargets(List<PortfolioTarget> targets, bool liquidateExistingHoldings = false)
        {
            Algorithm.SetHoldings(targets, liquidateExistingHoldings: liquidateExistingHoldings, asynchronous: true);
        }
        private bool ShouldUseSmartOrderManager()
        {
            return SmartOrderManager != null;
        }
        private void SetupSmartPricing()
        {
            if (SmartOrderManager == null)
            {
                Logger.Warning("SetupSmartPricing called but SmartOrderManager is null");
                return;
            }
            var smartPricingMode = "Off";
            if (Config is StrategyConfig strategyConfig)
            {
                smartPricingMode = strategyConfig.GetParameterValue("SmartPricingMode", "Off")?.ToString() ?? "Off";
                Logger.Info($"SmartPricing mode from Config: {smartPricingMode}");
            }
            else
            {
                smartPricingMode = Algorithm.GetParameter("SmartPricingMode", "Off");
                Logger.Info($"SmartPricing mode from Algorithm.GetParameter: {smartPricingMode}");
            }
            if (!string.Equals(smartPricingMode, "Off", StringComparison.OrdinalIgnoreCase))
            {
                try
                {
                    Logger.Info($"Creating SmartPricing engine for mode: {smartPricingMode}");
                    var pricingEngine = SmartPricingEngineFactory.Create(smartPricingMode);
                    SmartOrderManager.SetPricingEngine(pricingEngine);
                    Logger.Info($"SmartPricing enabled with mode: {smartPricingMode}");
                }
                catch (Exception ex)
                {
                    Logger.Error($"Failed to setup SmartPricing: {ex.Message}");
                }
            }
            else
            {
                Logger.Info("SmartPricing disabled (mode is Off)");
            }
        }
    }
}
using System;
using System.Collections.Generic;
using System.Linq;
using System.Runtime.CompilerServices;
using QuantConnect;
using QuantConnect.Algorithm;
using QuantConnect.Data;
using QuantConnect.Orders;
using QuantConnect.Securities;
using QuantConnect.Data.UniverseSelection;
using QuantConnect.Securities.Option;
using CoreAlgo.Architecture.Core.Interfaces;
using CoreAlgo.Architecture.Core.Models;
using CoreAlgo.Architecture.Core.Configuration;
using CoreAlgo.Architecture.Core.Helpers;
using CoreAlgo.Architecture.Core.Services;
using CoreAlgo.Architecture.Core.Execution;
using CoreAlgo.Architecture.QC.Helpers;
using System.Collections.Concurrent;
namespace CoreAlgo.Architecture.Core.Implementations
{
    public abstract partial class SimpleBaseStrategy : IStrategy, IAlgorithmContext
    {
        public StrategyConfig Config { get; private set; }
        protected EntryRestrictions EntryRestrictions { get; private set; }
        protected ExitRestrictions ExitRestrictions { get; private set; }
        protected StrikeRangeCalculator StrikeRangeCalculator { get; private set; }
        private StrategyState _state = StrategyState.NotInitialized;
        private Dictionary<string, object> _parameters = new Dictionary<string, object>();
        private DateTime _lastTradingDay = DateTime.MinValue;
        public QCAlgorithm Algorithm { get; private set; }
        public ISmartLogger Logger { get; private set; }
        public SmartOrderManager SmartOrderManager { get; private set; }
        protected PositionOverlapManager OverlapManager { get; private set; }
        public TradeTracker TradeTracker { get; private set; } = new TradeTracker();
        public TradePersistenceService TradePersistence { get; private set; }
        public PortfolioReporter Reporter { get; private set; }
        public abstract string Name { get; }
        public abstract string Description { get; }
        public virtual string Version => "1.0.0";
        public StrategyState State
        {
            get => _state;
            private set
            {
                if (_state != value)
                {
                    var previousState = _state;
                    _state = value;
                    StateChanged?.Invoke(this, new StrategyStateChangedEventArgs(previousState, value));
                }
            }
        }
        public Dictionary<string, object> Parameters => _parameters;
        public event EventHandler<StrategyStateChangedEventArgs> StateChanged;
        public event EventHandler<StrategyErrorEventArgs> ErrorOccurred;
        public virtual void Initialize(QCAlgorithm algorithm)
        {
            Algorithm = algorithm;
            StrikeRangeCalculator = new StrikeRangeCalculator(algorithm);
            if (Logger != null)
            {
                var context = new SimpleAlgorithmContext(algorithm, Logger);
                SmartOrderManager = new SmartOrderManager(algorithm, context);
                TradePersistence = new TradePersistenceService(context);
                var riskManager = new CoreAlgoRiskManager(context);
                Reporter = new PortfolioReporter(context, riskManager);
                TradePersistence.ClearPositionsIfBacktest();
                var loadedTracker = TradePersistence.LoadTrades();
                if (loadedTracker != null)
                {
                    TradeTracker = loadedTracker;
                    Logger.Info($"[{Name}] Restored {TradeTracker.AllTrades.Count} trades from ObjectStore");
                }
                SetupSmartPricing();
            }
            State = StrategyState.Initializing;
            OnInitialize();
            State = StrategyState.Ready;
        }
        public virtual void Initialize(QCAlgorithm algorithm, Dictionary<string, object> parameters)
        {
            Algorithm = algorithm;
            _parameters = parameters ?? new Dictionary<string, object>();
            StrikeRangeCalculator = new StrikeRangeCalculator(algorithm);
            State = StrategyState.Initializing;
            OnInitialize();
            State = StrategyState.Ready;
        }
        public void SetContext(ISmartLogger logger)
        {
            Logger = logger ?? throw new ArgumentNullException(nameof(logger));
        }
        public virtual void Configure<T>() where T : StrategyConfig, new()
        {
            Config = new T();
            Config.LoadFromParameters(this); // Pass context instead of separate parameters
            var validationErrors = Config.Validate();
            if (validationErrors.Length > 0)
            {
                var errorMessage = $"Configuration validation failed:\n{string.Join("\n", validationErrors)}";
                Logger.Error(errorMessage);
                throw new InvalidOperationException(errorMessage);
            }
            Logger.Debug($"Configuration validation passed for {typeof(T).Name}");
            EntryRestrictions = new EntryRestrictions(Config, Algorithm);
            ExitRestrictions = new ExitRestrictions(Config, Algorithm);
            OnConfigured();
        }
        public abstract void OnInitialize();
        protected SecurityPortfolioManager Portfolio => Algorithm.Portfolio;
        protected SecurityManager Securities => Algorithm.Securities;
        protected DateTime Time => Algorithm.Time;
        protected virtual void OnConfigured()
        {
            if (Config != null && Config.EnableOverlapPrevention && SmartOrderManager != null && Logger != null)
            {
                var context = new SimpleAlgorithmContext(Algorithm, Logger);
                OverlapManager = new PositionOverlapManager(context);
                SmartOrderManager.SetOverlapManager(OverlapManager);
                Logger.Info($"[{Name}] Position overlap prevention enabled (Mode: {Config.OverlapPreventionMode})");
            }
            if (Reporter != null && Config != null)
            {
                Reporter.Initialize(Config.AccountSize);
                Logger.Info($"[{Name}] Portfolio reporter initialized with ${Config.AccountSize:N0} starting capital");
            }
        }
        public void EnsureSmartPricingInitialized()
        {
            Logger.Info($"EnsureSmartPricingInitialized called. SmartOrderManager null: {SmartOrderManager == null}, Logger null: {Logger == null}, Algorithm null: {Algorithm == null}");
            if (SmartOrderManager == null && Logger != null && Algorithm != null)
            {
                Logger.Info("Creating SmartOrderManager...");
                var context = new SimpleAlgorithmContext(Algorithm, Logger);
                SmartOrderManager = new SmartOrderManager(Algorithm, context);
                SetupSmartPricing();
                Logger.Info("SmartOrderManager initialized after strategy setup");
            }
            else
            {
                Logger.Warning($"SmartOrderManager initialization skipped - already exists: {SmartOrderManager != null}");
            }
        }
        protected virtual bool ShouldExecuteTrade(Slice slice, out string blockReason)
        {
            blockReason = "";
            if (Config == null) return true;
            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;
                }
            }
            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;
                }
            }
            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;
                }
            }
            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;
                }
            }
            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;
                }
            }
            if (!OnShouldExecuteTrade(slice, out var customReason))
            {
                blockReason = customReason;
                return false;
            }
            return true;
        }
        protected virtual bool OnShouldExecuteTrade(Slice slice, out string blockReason)
        {
            blockReason = "";
            return true;
        }
        protected virtual void CheckExitConditions(Slice slice)
        {
            if (ExitRestrictions == null) return;
            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}");
                    OnExitSignal(position.Key, reason);
                }
            }
        }
        protected virtual void OnExitSignal(Symbol symbol, string reason)
        {
            try
            {
                var ticket = Liquidate(symbol, 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}");
            }
        }
        public virtual void Execute(Slice slice)
        {
            if (State == StrategyState.Ready)
                State = StrategyState.Running;
            if (State != StrategyState.Running)
            {
                return;
            }
            try
            {
                if (slice.Time.Date > _lastTradingDay)
                {
                    _lastTradingDay = slice.Time.Date;
                    OnNewTradingDay(slice);
                }
                OnPreExecuteAlways(slice);
                CheckExitConditions(slice);
                if (!ShouldExecuteTrade(slice, out var blockReason))
                {
                    if (slice.Time.Minute == 0 && slice.Time.Second == 0)
                    {
                        SmartLog($"[TRADING BLOCKED] {blockReason}");
                    }
                    return;
                }
                OnExecute(slice);
            }
            catch (Exception ex)
            {
                Logger.Error($"Error during strategy execution: {ex.Message}");
                Logger.Error($"Stack trace: {ex.StackTrace}");
                OnError(ex, ErrorSeverity.Error, true, "Error during strategy execution");
                throw;
            }
        }
        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;
            }
        }
        public virtual bool Validate()
        {
            try
            {
                return OnValidate();
            }
            catch (Exception ex)
            {
                OnError(ex, ErrorSeverity.Warning, true, "Error during strategy validation");
                return false;
            }
        }
        public virtual Dictionary<string, double> GetPerformanceMetrics()
        {
            var metrics = new Dictionary<string, double>();
            try
            {
                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;
                OnGetPerformanceMetrics(metrics);
            }
            catch (Exception ex)
            {
                OnError(ex, ErrorSeverity.Warning, true, "Error getting performance metrics");
            }
            return metrics;
        }
        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;
            }
        }
        protected abstract void OnExecute(Slice slice);
        protected virtual void OnShutdown()
        {
            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}");
            }
        }
        protected virtual bool OnValidate() => true;
        protected virtual void OnGetPerformanceMetrics(Dictionary<string, double> metrics) { }
        protected virtual void OnReset() { }
        public virtual void OnSecuritiesChanged(SecurityChanges changes)
        {
        }
    }
}
    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;
namespace CoreAlgo.Architecture.Core.Implementations
{
    public class StrategyStateChangedEventArgs : EventArgs
    {
        public StrategyState PreviousState { get; }
        public StrategyState NewState { get; }
        public DateTime Timestamp { get; }
        public string Message { get; }
        public StrategyStateChangedEventArgs(StrategyState previousState, StrategyState newState, string message = null)
        {
            PreviousState = previousState;
            NewState = newState;
            Timestamp = DateTime.UtcNow;
            Message = message;
        }
    }
    public class StrategyErrorEventArgs : EventArgs
    {
        public Exception Error { get; }
        public ErrorSeverity Severity { get; }
        public DateTime Timestamp { get; }
        public bool CanContinue { get; }
        public string Context { get; }
        public StrategyErrorEventArgs(Exception error, ErrorSeverity severity, bool canContinue, string context = null)
        {
            Error = error;
            Severity = severity;
            CanContinue = canContinue;
            Context = context;
            Timestamp = DateTime.UtcNow;
        }
    }
    public enum ErrorSeverity
    {
        Info,
        Warning,
        Error,
        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
{
    public class StrikeOverlapRule : IPositionOverlapRule
    {
        private readonly IAlgorithmContext _context;
        private readonly ISmartLogger _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
            {
                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;
                var existingOptions = existingPositions
                    .Where(p => p.Value.Invested &&
                               p.Key.SecurityType == SecurityType.Option &&
                               p.Key.Underlying == underlying)
                    .ToList();
                if (!existingOptions.Any())
                    return ValidationResult.Success();
                return ValidateExactDuplicates(
                    proposedStrike,
                    proposedExpiry,
                    proposedRight,
                    quantity,
                    existingOptions);
            }
            catch (System.Exception ex)
            {
                _logger.Error($"[{RuleName}] Error validating strike overlaps: {ex.Message}");
                return ValidationResult.Error($"Strike validation error: {ex.Message}");
            }
        }
        private ValidationResult ValidateExactDuplicates(
            decimal proposedStrike,
            System.DateTime proposedExpiry,
            OptionRight proposedRight,
            decimal quantity,
            List<KeyValuePair<Symbol, SecurityHolding>> existingOptions)
        {
            var exactMatch = existingOptions.FirstOrDefault(p =>
                p.Key.ID.StrikePrice == proposedStrike &&
                p.Key.ID.Date == proposedExpiry &&
                p.Key.ID.OptionRight == proposedRight);
            if (exactMatch.Key != null)
            {
                if (IsPositionModification(quantity, exactMatch.Value.Quantity))
                {
                    _logger.Debug($"[{RuleName}] Allowing position modification for {proposedStrike} {proposedRight} {proposedExpiry:yyyy-MM-dd}");
                    return ValidationResult.Success();
                }
                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();
        }
        private bool IsPositionModification(decimal proposedQuantity, decimal existingQuantity)
        {
            return Math.Abs(proposedQuantity) != Math.Abs(existingQuantity);
        }
    }
}
using System;
using System.Collections.Generic;
using System.Globalization;
namespace CoreAlgo.Architecture.Core.Implementations
{
    public class TradeRecord
    {
        public string OrderId { get; set; } = string.Empty;
        public string Symbol { get; set; } = string.Empty;
        public string Strategy { get; set; } = string.Empty;
        public DateTime OpenTime { get; set; }
        public DateTime? CloseTime { get; set; }
        public decimal OpenPrice { get; set; }
        public decimal? ClosePrice { get; set; }
        public int Quantity { get; set; }
        public string Status { get; set; } = "Working"; // Working, Open, Closed, Cancelled, PartialFill
        public decimal PnL { get; set; }
        public string OrderTag { get; set; } = string.Empty;
        public string Underlying { get; set; } = string.Empty;
        public List<int> OrderIds { get; set; } = new List<int>();
        public List<string> SymbolIds { get; set; } = new List<string>();
        public int FilledQuantity { get; set; }
        public DateTime LastUpdateUtc { get; set; }
        public Dictionary<string, string> StrategyDetails { get; set; } = new Dictionary<string, string>();
        public TradeRecord(string orderId, string symbol, string strategy = "", string orderTag = "")
        {
            OrderId = orderId;
            Symbol = symbol;
            Strategy = strategy;
            OrderTag = orderTag;
            OpenTime = DateTime.UtcNow;
            LastUpdateUtc = DateTime.UtcNow;
            if (int.TryParse(orderId, out var parsedOrderId))
            {
                OrderIds.Add(parsedOrderId);
            }
        }
        public TradeRecord()
        {
            OpenTime = DateTime.UtcNow;
            LastUpdateUtc = DateTime.UtcNow;
        }
        public void MarkAsOpen(decimal fillPrice, int fillQuantity)
        {
            MarkAsOpen(fillPrice, fillQuantity, DateTime.UtcNow);
        }
        public void MarkAsOpen(decimal fillPrice, int fillQuantity, DateTime openTimeUtc)
        {
            Status = "Open";
            OpenPrice = fillPrice;
            Quantity = fillQuantity;
            FilledQuantity = fillQuantity;
            OpenTime = openTimeUtc;
            LastUpdateUtc = openTimeUtc;
        }
        public void MarkAsPartialFill(int filledQuantity, decimal averagePrice)
        {
            MarkAsPartialFill(filledQuantity, averagePrice, DateTime.UtcNow);
        }
        public void MarkAsPartialFill(int filledQuantity, decimal averagePrice, DateTime timeUtc)
        {
            Status = "PartialFill";
            FilledQuantity = filledQuantity;
            OpenPrice = averagePrice;
            LastUpdateUtc = timeUtc;
        }
        public void MarkAsClosed(decimal closePrice, decimal pnl)
        {
            MarkAsClosed(closePrice, pnl, DateTime.UtcNow);
        }
        public void MarkAsClosed(decimal closePrice, decimal pnl, DateTime closeTimeUtc)
        {
            Status = "Closed";
            CloseTime = closeTimeUtc;
            ClosePrice = closePrice;
            PnL = pnl;
            LastUpdateUtc = closeTimeUtc;
        }
        public void MarkAsCancelled()
        {
            MarkAsCancelled(DateTime.UtcNow);
        }
        public void MarkAsCancelled(DateTime timeUtc)
        {
            Status = "Cancelled";
            LastUpdateUtc = timeUtc;
        }
        public void SetDetail(string key, string value)
        {
            StrategyDetails[key] = value;
            LastUpdateUtc = DateTime.UtcNow;
        }
        public void SetDetail(string key, decimal value)
        {
            StrategyDetails[key] = value.ToString(CultureInfo.InvariantCulture);
            LastUpdateUtc = DateTime.UtcNow;
        }
        public void SetDetail(string key, int value)
        {
            StrategyDetails[key] = value.ToString(CultureInfo.InvariantCulture);
            LastUpdateUtc = DateTime.UtcNow;
        }
        public void SetDetail(string key, bool value)
        {
            StrategyDetails[key] = value ? "true" : "false";
            LastUpdateUtc = DateTime.UtcNow;
        }
        public void SetDetail(string key, DateTime valueUtc)
        {
            StrategyDetails[key] = valueUtc.ToUniversalTime().ToString("o", CultureInfo.InvariantCulture);
            LastUpdateUtc = DateTime.UtcNow;
        }
        public bool TryGetDetail(string key, out string value) => StrategyDetails.TryGetValue(key, out value);
        public bool TryGetDecimal(string key, out decimal value)
        {
            value = 0m;
            return StrategyDetails.TryGetValue(key, out var s)
                && decimal.TryParse(s, NumberStyles.Number, CultureInfo.InvariantCulture, out value);
        }
        public bool TryGetInt(string key, out int value)
        {
            value = 0;
            return StrategyDetails.TryGetValue(key, out var s)
                && int.TryParse(s, NumberStyles.Integer, CultureInfo.InvariantCulture, out value);
        }
        public bool TryGetBool(string key, out bool value)
        {
            value = false;
            return StrategyDetails.TryGetValue(key, out var s)
                && bool.TryParse(s, out value);
        }
        public bool TryGetDateTime(string key, out DateTime valueUtc)
        {
            valueUtc = default;
            return StrategyDetails.TryGetValue(key, out var s)
                && DateTime.TryParse(s, CultureInfo.InvariantCulture, DateTimeStyles.AdjustToUniversal | DateTimeStyles.AssumeUniversal, out valueUtc);
        }
        public static string CsvHeader()
        {
            return "OrderId,Symbol,Underlying,Strategy,OrderTag,Status,OpenTime,CloseTime,OpenPrice,ClosePrice,Quantity,PnL";
        }
        public string ToCsv()
        {
            return $"{OrderId},{Symbol},{Underlying},{Strategy},{OrderTag},{Status},{OpenTime:yyyy-MM-dd HH:mm:ss}," +
                   $"{CloseTime?.ToString("yyyy-MM-dd HH:mm:ss") ?? ""}," +
                   $"{OpenPrice},{ClosePrice?.ToString() ?? ""},{Quantity},{PnL}";
        }
    }
}
using System;
using System.Collections.Generic;
using System.Linq;
namespace CoreAlgo.Architecture.Core.Implementations
{
    public class TradeTracker
    {
        public List<TradeRecord> AllTrades { get; set; } = new List<TradeRecord>();
        public List<TradeRecord> OpenTrades { get; set; } = new List<TradeRecord>();
        public List<TradeRecord> WorkingTrades { get; set; } = new List<TradeRecord>();
        public List<TradeRecord> ClosedTrades { get; set; } = new List<TradeRecord>();
        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);
        }
        public TradeRecord FindTradeByAnyOrderId(int orderId)
        {
            var trade = AllTrades.FirstOrDefault(t => t.OrderId == orderId.ToString());
            if (trade != null)
            {
                return trade;
            }
            return AllTrades.FirstOrDefault(t => t.OrderIds.Contains(orderId));
        }
        public void MarkTradeAsOpen(string orderId, decimal fillPrice, int fillQuantity, DateTime fillTimeUtc)
        {
            var trade = WorkingTrades.FirstOrDefault(t => t.OrderId == orderId);
            if (trade != null)
            {
                trade.MarkAsOpen(fillPrice, fillQuantity, fillTimeUtc);
                WorkingTrades.RemoveAll(t => t.OrderId == orderId);
                if (!OpenTrades.Any(t => t.OrderId == orderId))
                {
                    OpenTrades.Add(trade);
                }
                return;
            }
            trade = AllTrades.FirstOrDefault(t => t.OrderId == orderId);
            if (trade != null && trade.Status == "Working")
            {
                trade.MarkAsOpen(fillPrice, fillQuantity, fillTimeUtc);
                if (!OpenTrades.Any(t => t.OrderId == orderId))
                {
                    OpenTrades.Add(trade);
                }
                return;
            }
        }
        public void MarkTradeAsClosed(string orderId, decimal closePrice, decimal pnl)
        {
            MarkTradeAsClosed(orderId, closePrice, pnl, DateTime.UtcNow);
        }
        public void MarkTradeAsClosed(string orderId, decimal closePrice, decimal pnl, DateTime closeTimeUtc)
        {
            var trade = OpenTrades.FirstOrDefault(t => t.OrderId == orderId);
            if (trade != null)
            {
                trade.MarkAsClosed(closePrice, pnl, closeTimeUtc);
                var removed = OpenTrades.RemoveAll(t => t.OrderId == orderId);
                if (removed == 0)
                {
                    System.Console.WriteLine($"[WARN TradeTracker] MarkTradeAsClosed: Found trade {orderId} in OpenTrades but RemoveAll returned 0");
                }
                if (!ClosedTrades.Any(t => t.OrderId == orderId))
                {
                    ClosedTrades.Add(trade);
                }
                return;
            }
            trade = AllTrades.FirstOrDefault(t => t.OrderId == orderId);
            if (trade != null && trade.Status == "Open")
            {
                trade.MarkAsClosed(closePrice, pnl, closeTimeUtc);
                OpenTrades.RemoveAll(t => t.OrderId == orderId);
                if (!ClosedTrades.Any(t => t.OrderId == orderId))
                {
                    ClosedTrades.Add(trade);
                }
            }
        }
        public void CancelWorkingTrade(string orderId)
        {
            var trade = WorkingTrades.FirstOrDefault(t => t.OrderId == orderId);
            if (trade != null)
            {
                WorkingTrades.RemoveAll(t => t.OrderId == orderId);
                trade.Status = "Cancelled";
            }
        }
        public void ExportToLogs(Action<string> logAction)
        {
            try
            {
                logAction?.Invoke($"=== TRADE EXPORT === {TradeRecord.CsvHeader()}");
                foreach (var trade in AllTrades.OrderBy(t => t.OpenTime))
                {
                    logAction?.Invoke($"TRADE_DATA: {trade.ToCsv()}");
                }
                logAction?.Invoke("=== END TRADE EXPORT ===");
            }
            catch (Exception ex)
            {
                logAction?.Invoke($"Failed to export trades to logs: {ex.Message}");
            }
        }
        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}";
        }
        public void Clear()
        {
            AllTrades.Clear();
            WorkingTrades.Clear();
            OpenTrades.Clear();
            ClosedTrades.Clear();
        }
    }
}
using System;
using System.Collections.Generic;
using System.Linq;
using QuantConnect;
using QuantConnect.Securities;
using CoreAlgo.Architecture.Core.Interfaces;

namespace CoreAlgo.Architecture.Core.Implementations
{
    /// <summary>
    /// Prevents cross-strategy conflicts on the same underlying asset
    /// Example: Blocks Iron Condor + Covered Call on SPY simultaneously
    /// </summary>
    public class UnderlyingConflictRule : IPositionOverlapRule
    {
        private readonly IAlgorithmContext _context;
        private readonly ISmartLogger _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
                
                _logger.Debug($"[{RuleName}] Allowed: {proposedUnderlying.Value} has {optionPositions} options, {stockPositions} stock positions");
                return ValidationResult.Success();
            }
            catch (System.Exception ex)
            {
                _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
        }

    }
}
    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;
namespace CoreAlgo.Architecture.Core.Implementations
{
    public class ValidationResult
    {
        public bool IsValid { get; private set; }
        public List<ValidationError> Errors { get; private set; }
        public List<ValidationWarning> Warnings { get; private set; }
        public string Message
        {
            get
            {
                if (Errors?.Any() == true)
                    return Errors.First().Message;
                if (Warnings?.Any() == true)
                    return Warnings.First().Message;
                return string.Empty;
            }
        }
        public static ValidationResult Success()
        {
            return new ValidationResult
            {
                IsValid = true,
                Errors = new List<ValidationError>(),
                Warnings = new List<ValidationWarning>()
            };
        }
        public static ValidationResult Failure(params ValidationError[] errors)
        {
            return new ValidationResult
            {
                IsValid = false,
                Errors = new List<ValidationError>(errors),
                Warnings = new List<ValidationWarning>()
            };
        }
        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;
        }
        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;
        }
        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;
        }
        public void AddError(string code, string message, string field = null)
        {
            IsValid = false;
            Errors.Add(new ValidationError { Code = code, Message = message, Field = field });
        }
        public void AddWarning(string code, string message, string field = null)
        {
            Warnings.Add(new ValidationWarning { Code = code, Message = message, Field = field });
        }
        public void Merge(ValidationResult other)
        {
            if (!other.IsValid)
            {
                IsValid = false;
            }
            Errors.AddRange(other.Errors);
            Warnings.AddRange(other.Warnings);
        }
        public ValidationResult()
        {
            Errors = new List<ValidationError>();
            Warnings = new List<ValidationWarning>();
            IsValid = true;
        }
    }
    public class ValidationError
    {
        public string Code { get; set; }
        public string Message { get; set; }
        public string Field { get; set; }
    }
    public class ValidationWarning
    {
        public string Code { get; set; }
        public string Message { get; set; }
        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.
        /// </summary>
        ISmartLogger 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 trace message
        /// </summary>
        void LogTrace(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);
    }

    /// <summary>
    /// Non-generic logger interface for use in IAlgorithmContext.
    /// Provides convenience methods matching QCLogger's public API,
    /// eliminating the need for dynamic dispatch.
    /// </summary>
    public interface ISmartLogger
    {
        void Debug(string message);
        void Info(string message);
        void Warning(string message);
        void Error(string message);
        void Trace(string message);
        void LogMessage(string message, LogLevel level = LogLevel.Debug);
        void LogWithContext(string message, string className, string methodName, LogLevel level = LogLevel.Info);
    }
}
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; }
    }
}
    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;
namespace CoreAlgo.Architecture.Core.Interfaces
{
    public interface IStrategy
    {
        string Name { get; }
        string Description { get; }
        string Version { get; }
        StrategyState State { get; }
        Dictionary<string, object> Parameters { get; }
        void Initialize(QCAlgorithm algorithm);
        void Initialize(QCAlgorithm algorithm, Dictionary<string, object> parameters);
        void Execute(Slice slice);
        void Shutdown();
        bool Validate();
        Dictionary<string, double> GetPerformanceMetrics();
        void Reset();
        event EventHandler<StrategyStateChangedEventArgs> StateChanged;
        event EventHandler<StrategyErrorEventArgs> ErrorOccurred;
    }
}
using System;
using System.Collections.Generic;
namespace CoreAlgo.Architecture.Core.Models
{
    public class AdvancedPerformanceMetrics
    {
        public DateTime AsOfUtc { get; set; }
        public string StrategyName { get; set; } = string.Empty;
        public decimal TotalReturn { get; set; }
        public decimal UnrealizedReturn { get; set; }
        public decimal TotalPortfolioReturn => TotalReturn + UnrealizedReturn;
        public decimal ReturnPercent { get; set; }
        public decimal CAGR { get; set; }
        public decimal SharpeRatio { get; set; }
        public decimal SortinoRatio { get; set; }
        public decimal CalmarRatio { get; set; }
        public decimal Volatility { get; set; }
        public decimal DownsideDeviation { get; set; }
        public decimal MaxDrawdown { get; set; }
        public decimal MaxDrawdownPercent { get; set; }
        public DateTime? MaxDrawdownStartDate { get; set; }
        public DateTime? MaxDrawdownEndDate { get; set; }
        public int MaxDrawdownDurationDays { get; set; }
        public DateTime? MaxDrawdownRecoveryDate { get; set; }
        public int? MaxDrawdownRecoveryDays { get; set; }
        public decimal CurrentDrawdown { get; set; }
        public decimal CurrentDrawdownPercent { get; set; }
        public decimal AverageDrawdown { get; set; }
        public TradeStatistics TradeStats { get; set; } = new TradeStatistics();
        public PeriodPerformance DailyPerformance { get; set; } = new PeriodPerformance();
        public PeriodPerformance WeeklyPerformance { get; set; } = new PeriodPerformance();
        public PeriodPerformance MonthlyPerformance { get; set; } = new PeriodPerformance();
        public Dictionary<string, decimal> PnLByStrategy { get; set; } = new Dictionary<string, decimal>();
        public Dictionary<string, decimal> PnLByUnderlying { get; set; } = new Dictionary<string, decimal>();
        public Dictionary<string, int> TradeCountByStrategy { get; set; } = new Dictionary<string, int>();
        public Dictionary<string, decimal> WinRateByStrategy { get; set; } = new Dictionary<string, decimal>();
    }
    public class TradeStatistics
    {
        public int TotalTrades { get; set; }
        public int WinningTrades { get; set; }
        public int LosingTrades { get; set; }
        public int BreakEvenTrades { get; set; }
        public decimal WinRate { get; set; }
        public decimal LossRate { get; set; }
        public decimal AverageWin { get; set; }
        public decimal AverageLoss { get; set; }
        public decimal LargestWin { get; set; }
        public decimal LargestLoss { get; set; }
        public decimal AverageTrade { get; set; }
        public decimal TradeStdDev { get; set; }
        public decimal ProfitFactor { get; set; }
        public decimal PayoffRatio { get; set; }
        public decimal KellyCriterion { get; set; }
        public decimal Expectancy { get; set; }
        public decimal ExpectancyRatio { get; set; }
        public int CurrentWinStreak { get; set; }
        public int CurrentLossStreak { get; set; }
        public int MaxWinStreak { get; set; }
        public int MaxLossStreak { get; set; }
        public decimal AvgWinStreak { get; set; }
        public decimal AvgLossStreak { get; set; }
        public decimal AvgTradeDurationHours { get; set; }
        public decimal AvgWinningTradeDurationHours { get; set; }
        public decimal AvgLosingTradeDurationHours { get; set; }
        public decimal MinTradeDurationHours { get; set; }
        public decimal MaxTradeDurationHours { get; set; }
        public decimal GrossProfit { get; set; }
        public decimal GrossLoss { get; set; }
        public decimal NetProfit { get; set; }
    }
    public class PeriodPerformance
    {
        public int PeriodCount { get; set; }
        public int WinningPeriods { get; set; }
        public int LosingPeriods { get; set; }
        public decimal WinRate { get; set; }
        public decimal BestPeriod { get; set; }
        public DateTime? BestPeriodDate { get; set; }
        public decimal WorstPeriod { get; set; }
        public DateTime? WorstPeriodDate { get; set; }
        public decimal AveragePeriod { get; set; }
        public decimal StdDev { get; set; }
        public Dictionary<DateTime, decimal> Returns { get; set; } = new Dictionary<DateTime, decimal>();
    }
    public class EquityPoint
    {
        public DateTime Timestamp { get; set; }
        public decimal Equity { get; set; }
        public decimal HighWaterMark { get; set; }
        public decimal Drawdown { get; set; }
        public decimal DrawdownPercent { get; set; }
    }
}
using System;
using System.Linq;
using QuantConnect;
using CoreAlgo.Architecture.Core.Attributes;
namespace CoreAlgo.Architecture.Core.Models
{
    public abstract class FuturesConfig : StrategyConfig
    {
        [StrategyParameter("Futures symbol", "ES")]
        public string FutureSymbol { get; set; } = "ES";
        [StrategyParameter("Rollover days to expiry", 5)]
        public int ContractRolloverDays { get; set; } = 5;
        [StrategyParameter("Use continuous contracts", true)]
        public bool UseContinuousContract { get; set; } = true;
        [StrategyParameter("Contract depth offset", 0)]
        public int ContractDepthOffset { get; set; } = 0;
        [StrategyParameter("Position size multiplier", 1.0)]
        public decimal PositionSizeMultiplier { get; set; } = 1.0m;
        [StrategyParameter("Maximum contracts per trade", 5)]
        public int MaxContractsPerTrade { get; set; } = 5;
        [StrategyParameter("Data resolution", "Minute")]
        public string DataResolution { get; set; } = "Minute";
        public Resolution GetDataResolution() => ParseResolution(DataResolution);
        public string GetFuturesCategory()
        {
            var symbol = FutureSymbol?.ToUpperInvariant();
            if (new[] { "ES", "NQ", "YM", "RTY", "EMD", "NKD" }.Contains(symbol))
                return "equity";
            if (new[] { "CL", "NG", "RB", "HO", "BZ" }.Contains(symbol))
                return "energy";
            if (new[] { "GC", "SI", "HG", "PA", "PL" }.Contains(symbol))
                return "metals";
            if (new[] { "ZC", "ZS", "ZW", "ZM", "ZL", "KC", "CT", "SB", "CC", "OJ" }.Contains(symbol))
                return "agricultural";
            if (new[] { "ZB", "ZN", "ZF", "TU", "UB", "ED", "SR1", "SR3" }.Contains(symbol))
                return "bonds";
            if (new[] { "6E", "6J", "6B", "6S", "6C", "6A", "6N", "6M", "E7", "J7" }.Contains(symbol))
                return "currency";
            if (new[] { "VX" }.Contains(symbol))
                return "volatility";
            if (new[] { "BTC", "ETH" }.Contains(symbol))
                return "crypto";
            return "unknown";
        }
        public override string[] OptimizationParameters => base.OptimizationParameters.Concat(new[]
        {
            "FutureSymbol",           // Asset selection for futures
            "PositionSizeMultiplier", // Position sizing relative to equity
            "MaxContractsPerTrade",   // Risk management - contract limit
            "ContractRolloverDays",   // Contract management timing
            "UseContinuousContract",  // Contract type selection
            "ContractDepthOffset",    // Contract selection depth
            "DataResolution"          // Data granularity for strategy
        }).ToArray();
    }
}
using System;
using System.Collections.Generic;
using System.Linq;
using System.Reflection;
using QuantConnect;
using QuantConnect.Algorithm;
using CoreAlgo.Architecture.Core.Attributes;
using CoreAlgo.Architecture.Core.Interfaces;
namespace CoreAlgo.Architecture.Core.Models
{
    public abstract class StrategyConfig
    {
        public string UnderlyingSymbol { get; set; } = "SPY";
        [StrategyParameter("StartDate", "2023-01-01")]
        public DateTime StartDate { get; set; } = new DateTime(2023, 1, 1);
        [StrategyParameter("EndDate", "2023-12-31")]
        public DateTime EndDate { get; set; } = new DateTime(2023, 12, 31);
        public decimal StartingCash { get; set; } = 100000m;
        [StrategyParameter("AccountSize", 100000)]
        public virtual decimal AccountSize { get; set; } = 100000m;
        [StrategyParameter("EnableOverlapPrevention", true)]
        public bool EnableOverlapPrevention { get; set; } = true;
        [StrategyParameter("OverlapPreventionMode", "Block")]
        public string OverlapPreventionMode { get; set; } = "Block";
        [StrategyParameter("AllowMultiStrategyUnderlying", false)]
        public bool AllowSameUnderlyingMultiStrategy { get; set; } = false;
        [StrategyParameter("MaxPositionsPerUnderlying", 1)]
        public int MaxPositionsPerUnderlying { get; set; } = 1;
        [StrategyParameter("MaxPositionsPerCombo", 2)]
        public int MaxPositionsPerCombo { get; set; } = 2;
        [StrategyParameter("ComboOrderLegCount", 0)]
        public int ComboOrderLegCount { get; set; } = 0;
        [StrategyParameter("MinimumStrikeDistance", 5.0)]
        public decimal MinimumStrikeDistance { get; set; } = 5.0m;
        [StrategyParameter("Symbols", "SPY")]
        public string[] Symbols { get; set; } = new[] { "SPY" };
        [StrategyParameter("UseUniverseSelection", false)]
        public bool UseUniverseSelection { get; set; } = false;
        [StrategyParameter("EntryDeltaMin", 0.25)]
        public decimal EntryDeltaMin { get; set; } = 0.25m;
        [StrategyParameter("EntryDeltaMax", 0.35)]
        public decimal EntryDeltaMax { get; set; } = 0.35m;
        [StrategyParameter("ExitDelta", 0.10)]
        public decimal ExitDelta { get; set; } = 0.10m;
        [StrategyParameter("AllocationPerPosition", 0.1)]
        public decimal AllocationPerPosition { get; set; } = 0.1m;
        [StrategyParameter("MaxPositions", 5)]
        public int MaxPositions { get; set; } = 5;
        [StrategyParameter("TradingStartTime", "09:30:00")]
        public TimeSpan TradingStartTime { get; set; } = new TimeSpan(9, 30, 0);
        [StrategyParameter("TradingEndTime", "15:30:00")]
        public TimeSpan TradingEndTime { get; set; } = new TimeSpan(15, 30, 0);
        [StrategyParameter("UseEntryTimeWindow", false)]
        public bool UseEntryTimeWindow { get; set; } = false;
        [StrategyParameter("EntryWindowStart", "00:00:00")]
        public TimeSpan EntryWindowStart { get; set; } = TimeSpan.Zero;
        [StrategyParameter("EntryWindowEnd", "23:59:59")]
        public TimeSpan EntryWindowEnd { get; set; } = new TimeSpan(23, 59, 59);
        [StrategyParameter("ProfitTarget", 0.5)]
        public decimal ProfitTarget { get; set; } = 0.5m;
        [StrategyParameter("StopLoss", -0.5)]
        public decimal StopLoss { get; set; } = -0.5m;
        [StrategyParameter("MaxDaysInTrade", 30)]
        public int MaxDaysInTrade { get; set; } = 30;
        [StrategyParameter("LogEntryRestrictions", false)]
        public virtual bool LogEntryRestrictions { get; set; }
        [StrategyParameter("MinImpliedVolatility", 0.15)]
        public decimal MinImpliedVolatility { get; set; } = 0.15m;
        [StrategyParameter("SmartPricingMode", "Normal")]
        public string SmartPricingMode { get; set; } = "Normal";
        [StrategyParameter("ComboMaxNetSpreadWidth", 5.0)]
        public decimal ComboMaxNetSpreadWidth { get; set; } = 5.0m;
        [StrategyParameter("ComboSmartPricingMode", "")]
        public string ComboSmartPricingMode { get; set; } = "";
        [StrategyParameter("EnableComboSmartPricing", true)]
        public bool EnableComboSmartPricing { get; set; } = true;
        [StrategyParameter("DataResolution", "Minute")]
        public string UnderlyingResolution { get; set; } = "Minute";
        public static Resolution ParseResolution(string resolution)
        {
            return resolution?.ToUpperInvariant() switch
            {
                "SECOND" => Resolution.Second,
                "MINUTE" => Resolution.Minute,
                "HOUR" => Resolution.Hour,
                "DAILY" => Resolution.Daily,
                _ => Resolution.Minute
            };
        }
        public virtual Resolution GetUnderlyingResolution()
        {
            return ParseResolution(UnderlyingResolution);
        }
        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
        };
        public virtual void LoadFromParameters(IAlgorithmContext context)
        {
            var properties = GetType().GetProperties();
            var loadedParams = new System.Collections.Generic.List<string>();
            foreach (var property in properties)
            {
                var attribute = property.GetCustomAttribute<StrategyParameterAttribute>();
                if (attribute != null)
                {
                    var parameterName = attribute.Name;
                    var defaultValue = property.GetValue(this)?.ToString() ?? attribute.DefaultValue?.ToString() ?? "";
                    var parameterValue = context.Algorithm.GetParameter(parameterName, defaultValue);
                    var convertedValue = ConvertParameterValue(parameterValue, property.PropertyType);
                    property.SetValue(this, convertedValue);
                    var formattedValue = convertedValue is string[] arrayValue
                        ? $"[{string.Join(", ", arrayValue)}]"
                        : $"{convertedValue}";
                    loadedParams.Add($"{parameterName}={formattedValue}");
                }
            }
            if (loadedParams.Count > 0)
            {
                context.Logger.Debug($"Loaded parameters: {string.Join(", ", loadedParams)}");
            }
        }
        private object ConvertParameterValue(string value, Type targetType)
        {
            if (targetType == typeof(string))
                return value;
            if (targetType == typeof(int))
                return int.TryParse(value, System.Globalization.NumberStyles.Integer, System.Globalization.CultureInfo.InvariantCulture, out var intVal)
                    ? intVal : 0;
            if (targetType == typeof(decimal))
                return decimal.TryParse(value, System.Globalization.NumberStyles.Number, System.Globalization.CultureInfo.InvariantCulture, out var decVal)
                    ? decVal : 0m;
            if (targetType == typeof(bool))
                return bool.TryParse(value, out var boolVal) && boolVal;
            if (targetType == typeof(DateTime))
                return DateTime.TryParse(value, System.Globalization.CultureInfo.InvariantCulture, System.Globalization.DateTimeStyles.None, out var dtVal)
                    ? dtVal : DateTime.MinValue;
            if (targetType == typeof(TimeSpan))
                return TimeSpan.TryParse(value, System.Globalization.CultureInfo.InvariantCulture, out var tsVal)
                    ? tsVal : TimeSpan.Zero;
            if (targetType == typeof(string[]))
            {
                if (string.IsNullOrWhiteSpace(value))
                {
                    return new string[] { "SPY" }; // Default fallback
                }
                if (value.StartsWith("System.String[]"))
                {
                    return new string[] { "SPY" }; // Fallback, will be overridden by template logic
                }
                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
            }
            return Convert.ChangeType(value, targetType);
        }
        public virtual string[] Validate()
        {
            var errors = new List<string>();
            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})");
            if (ProfitTarget != 0 && ProfitTarget <= 0)
                errors.Add($"ProfitTarget must be positive (current: {ProfitTarget:P1}) or 0 to disable");
            if (StopLoss != 0 && StopLoss >= 0)
                errors.Add($"StopLoss must be negative (current: {StopLoss:P1}) or 0 to disable");
            if (!CoreAlgo.Architecture.Core.Execution.SmartPricingEngineFactory.IsValidMode(SmartPricingMode))
                errors.Add($"Invalid SmartPricingMode '{SmartPricingMode}' (valid: Normal, Fast, Patient, Off)");
            if (UseEntryTimeWindow)
            {
                if (EntryWindowStart > EntryWindowEnd)
                {
                    errors.Add($"EntryWindowStart {EntryWindowStart:hh\\:mm} must be <= EntryWindowEnd {EntryWindowEnd:hh\\:mm}");
                }
            }
            return errors.ToArray();
        }
        public object GetParameterValue(string parameterName, object defaultValue)
        {
            var property = GetType().GetProperty(parameterName);
            if (property != null)
            {
                return property.GetValue(this);
            }
            return defaultValue;
        }
        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 CoreAlgo.Architecture.Core.Attributes;
using QuantConnect;
namespace CoreAlgo.Architecture.Core.Models
{
    public class THTConfig : StrategyConfig
    {
        public THTConfig()
        {
            StartDate = new DateTime(2000, 1, 1);
            EndDate = new DateTime(2025, 12, 31);
            ProfitTarget = 0m;
            StopLoss = 0m;
            MaxPositions = 30;
            TradingStartTime = TimeSpan.Zero;
            TradingEndTime = TimeSpan.Zero;
        }
        [StrategyParameter("FVB33Period", 33)]
        public int FVB33Period { get; set; } = 33;
        [StrategyParameter("FVB20Period", 20)]
        public int FVB20Period { get; set; } = 20;
        [StrategyParameter("FVBPivotLookback", 5)]
        public int FVBPivotLookback { get; set; } = 5;
        [StrategyParameter("BXEmaShort", 8)]
        public int BXEmaShort { get; set; } = 8;
        [StrategyParameter("BXEmaLong", 30)]
        public int BXEmaLong { get; set; } = 30;
        [StrategyParameter("BXRsiPeriod", 8)]
        public int BXRsiPeriod { get; set; } = 8;
        [StrategyParameter("UniverseSize", 100)]
        public int UniverseSize { get; set; } = 100;
        [StrategyParameter("Benchmark", "SPY")]
        public string Benchmark { get; set; } = "SPY";
        [StrategyParameter("MinPrice", 5)]
        public decimal MinPrice { get; set; } = 5m;
        [StrategyParameter("RebalanceDayOfWeek", 1)]
        public int RebalanceDayOfWeek { get; set; } = 1;
        [StrategyParameter("RebalanceAfterOpenMinutes", 30)]
        public int RebalanceAfterOpenMinutes { get; set; } = 30;
        [StrategyParameter("StopLossDayOfMonthThreshold", 14)]
        public int StopLossDayOfMonthThreshold { get; set; } = 14;
        [StrategyParameter("MaxPositionWeight", 0.05)]
        public decimal MaxPositionWeight { get; set; } = 0.05m;
        [StrategyParameter("EnableWeeklyRebalance", 1)]
        public int EnableWeeklyRebalance { get; set; } = 1;
        [StrategyParameter("PivotTPEnabled", 0)]
        public int PivotTPEnabled { get; set; } = 0;
        [StrategyParameter("WeeklyStallTPEnabled", 1)]
        public int WeeklyStallTPEnabled { get; set; } = 1;
        [StrategyParameter("StopLossEnabled", 1)]
        public int StopLossEnabled { get; set; } = 1;
        [StrategyParameter("MinHoldBars", 0)]
        public int MinHoldBars { get; set; } = 0;
        [StrategyParameter("DebugTickers", "")]
        public string DebugTickers { get; set; } = "";
        [StrategyParameter("WarmupDays", 600)]
        public int WarmupDays { get; set; } = 600;
        public override Resolution GetUnderlyingResolution()
        {
            return Resolution.Daily;
        }
        public override string[] OptimizationParameters => new[]
        {
            "Strategy",
            "StartDate",
            "EndDate",
            "AccountSize",
            "FVB33Period",
            "FVB20Period",
            "BXEmaShort",
            "BXEmaLong",
            "BXRsiPeriod",
            "Benchmark",
            "StopLossDayOfMonthThreshold",
            "MaxPositionWeight",
            "EnableWeeklyRebalance",
            "PivotTPEnabled",
            "WeeklyStallTPEnabled",
            "StopLossEnabled",
            "MinHoldBars",
            "DebugTickers"
        };
        public override string[] Validate()
        {
            var errors = new System.Collections.Generic.List<string>();
            if (FVB33Period <= 0)
                errors.Add("FVB33Period must be greater than 0");
            if (FVB20Period <= 0)
                errors.Add("FVB20Period must be greater than 0");
            if (FVBPivotLookback <= 0)
                errors.Add("FVBPivotLookback must be greater than 0");
            if (BXEmaShort <= 0 || BXEmaLong <= 0 || BXRsiPeriod <= 0)
                errors.Add("BX parameters must be greater than 0");
            if (BXEmaShort >= BXEmaLong)
                errors.Add("BXEmaShort must be less than BXEmaLong");
            if (UniverseSize <= 0)
                errors.Add("UniverseSize must be greater than 0");
            if (string.IsNullOrWhiteSpace(Benchmark))
                errors.Add("Benchmark must be specified");
            if (MinPrice < 0)
                errors.Add("MinPrice must be non-negative");
            if (RebalanceDayOfWeek < 0 || RebalanceDayOfWeek > 4)
                errors.Add("RebalanceDayOfWeek must be 0-4 (Mon-Fri)");
            if (RebalanceAfterOpenMinutes < 0)
                errors.Add("RebalanceAfterOpenMinutes must be non-negative");
            if (WarmupDays < 50)
                errors.Add("WarmupDays must be at least 50 for meaningful FVB calculation");
            if (StopLossDayOfMonthThreshold < 1 || StopLossDayOfMonthThreshold > 28)
                errors.Add("StopLossDayOfMonthThreshold must be between 1 and 28");
            if (MaxPositionWeight <= 0m || MaxPositionWeight > 1.0m)
                errors.Add("MaxPositionWeight must be between 0 and 1");
            return errors.ToArray();
        }
    }
}
using System;
using System.Collections.Generic;
using System.Linq;
using CoreAlgo.Architecture.Core.Interfaces;
using CoreAlgo.Architecture.Core.Implementations;
using CoreAlgo.Architecture.Core.Models;
namespace CoreAlgo.Architecture.Core.Services
{
    public class AdvancedMetricsCalculator
    {
        private readonly IAlgorithmContext _context;
        private const decimal RiskFreeRate = 0.05m;
        private const int TradingDaysPerYear = 252;
        public AdvancedMetricsCalculator(IAlgorithmContext context)
        {
            _context = context ?? throw new ArgumentNullException(nameof(context));
        }
        public AdvancedPerformanceMetrics CalculateMetrics(
            TradeTracker tracker,
            List<EquityPoint> equityCurve,
            decimal startingCapital,
            string strategyName = "")
        {
            var metrics = new AdvancedPerformanceMetrics
            {
                AsOfUtc = _context.Algorithm.UtcTime,
                StrategyName = strategyName
            };
            var closedTrades = tracker?.ClosedTrades ?? new List<TradeRecord>();
            var portfolio = _context.Algorithm.Portfolio;
            metrics.TradeStats = CalculateTradeStatistics(closedTrades);
            CalculateReturnMetrics(metrics, closedTrades, portfolio, startingCapital);
            CalculateDrawdownMetrics(metrics, equityCurve);
            CalculateRiskAdjustedMetrics(metrics, equityCurve, startingCapital);
            CalculatePeriodPerformance(metrics, closedTrades, equityCurve);
            CalculateAttribution(metrics, closedTrades);
            return metrics;
        }
        public TradeStatistics CalculateTradeStatistics(List<TradeRecord> closedTrades)
        {
            var stats = new TradeStatistics();
            if (closedTrades == null || closedTrades.Count == 0)
            {
                return stats;
            }
            var trades = closedTrades.ToList();
            var pnls = trades.Select(t => t.PnL).ToList();
            stats.TotalTrades = trades.Count;
            stats.WinningTrades = trades.Count(t => t.PnL > 0);
            stats.LosingTrades = trades.Count(t => t.PnL < 0);
            stats.BreakEvenTrades = trades.Count(t => t.PnL == 0);
            stats.WinRate = stats.TotalTrades > 0
                ? (decimal)stats.WinningTrades / stats.TotalTrades
                : 0;
            stats.LossRate = stats.TotalTrades > 0
                ? (decimal)stats.LosingTrades / stats.TotalTrades
                : 0;
            var winners = trades.Where(t => t.PnL > 0).ToList();
            var losers = trades.Where(t => t.PnL < 0).ToList();
            stats.GrossProfit = winners.Sum(t => t.PnL);
            stats.GrossLoss = losers.Sum(t => t.PnL); // Will be negative
            stats.NetProfit = stats.GrossProfit + stats.GrossLoss;
            stats.AverageWin = winners.Count > 0 ? winners.Average(t => t.PnL) : 0;
            stats.AverageLoss = losers.Count > 0 ? losers.Average(t => t.PnL) : 0;
            stats.AverageTrade = pnls.Count > 0 ? pnls.Average() : 0;
            stats.LargestWin = winners.Count > 0 ? winners.Max(t => t.PnL) : 0;
            stats.LargestLoss = losers.Count > 0 ? losers.Min(t => t.PnL) : 0;
            if (pnls.Count > 1)
            {
                var avg = pnls.Average();
                var sumSquares = pnls.Sum(p => (p - avg) * (p - avg));
                stats.TradeStdDev = (decimal)Math.Sqrt((double)(sumSquares / (pnls.Count - 1)));
            }
            stats.ProfitFactor = stats.GrossLoss != 0
                ? Math.Abs(stats.GrossProfit / stats.GrossLoss)
                : stats.GrossProfit > 0 ? decimal.MaxValue : 0;
            stats.PayoffRatio = stats.AverageLoss != 0
                ? Math.Abs(stats.AverageWin / stats.AverageLoss)
                : stats.AverageWin > 0 ? decimal.MaxValue : 0;
            stats.KellyCriterion = stats.PayoffRatio > 0
                ? stats.WinRate - (stats.LossRate / stats.PayoffRatio)
                : 0;
            stats.KellyCriterion = Math.Max(0, stats.KellyCriterion); // Can't bet negative
            stats.Expectancy = (stats.WinRate * stats.AverageWin) + (stats.LossRate * stats.AverageLoss);
            stats.ExpectancyRatio = stats.TradeStdDev > 0
                ? stats.Expectancy / stats.TradeStdDev
                : 0;
            CalculateStreaks(trades, stats);
            CalculateDurations(trades, stats);
            return stats;
        }
        private void CalculateStreaks(List<TradeRecord> trades, TradeStatistics stats)
        {
            if (trades.Count == 0) return;
            var orderedTrades = trades.OrderBy(t => t.CloseTime ?? t.OpenTime).ToList();
            int currentWinStreak = 0;
            int currentLossStreak = 0;
            int maxWinStreak = 0;
            int maxLossStreak = 0;
            var winStreaks = new List<int>();
            var lossStreaks = new List<int>();
            foreach (var trade in orderedTrades)
            {
                if (trade.PnL > 0)
                {
                    currentWinStreak++;
                    if (currentLossStreak > 0)
                    {
                        lossStreaks.Add(currentLossStreak);
                        currentLossStreak = 0;
                    }
                    maxWinStreak = Math.Max(maxWinStreak, currentWinStreak);
                }
                else if (trade.PnL < 0)
                {
                    currentLossStreak++;
                    if (currentWinStreak > 0)
                    {
                        winStreaks.Add(currentWinStreak);
                        currentWinStreak = 0;
                    }
                    maxLossStreak = Math.Max(maxLossStreak, currentLossStreak);
                }
            }
            if (currentWinStreak > 0) winStreaks.Add(currentWinStreak);
            if (currentLossStreak > 0) lossStreaks.Add(currentLossStreak);
            stats.CurrentWinStreak = currentWinStreak;
            stats.CurrentLossStreak = currentLossStreak;
            stats.MaxWinStreak = maxWinStreak;
            stats.MaxLossStreak = maxLossStreak;
            stats.AvgWinStreak = winStreaks.Count > 0 ? (decimal)winStreaks.Average() : 0;
            stats.AvgLossStreak = lossStreaks.Count > 0 ? (decimal)lossStreaks.Average() : 0;
        }
        private void CalculateDurations(List<TradeRecord> trades, TradeStatistics stats)
        {
            var tradesWithDuration = trades
                .Where(t => t.CloseTime.HasValue)
                .Select(t => new
                {
                    Trade = t,
                    DurationHours = (decimal)(t.CloseTime.Value - t.OpenTime).TotalHours
                })
                .ToList();
            if (tradesWithDuration.Count == 0) return;
            stats.AvgTradeDurationHours = tradesWithDuration.Average(t => t.DurationHours);
            stats.MinTradeDurationHours = tradesWithDuration.Min(t => t.DurationHours);
            stats.MaxTradeDurationHours = tradesWithDuration.Max(t => t.DurationHours);
            var winners = tradesWithDuration.Where(t => t.Trade.PnL > 0).ToList();
            var losers = tradesWithDuration.Where(t => t.Trade.PnL < 0).ToList();
            stats.AvgWinningTradeDurationHours = winners.Count > 0
                ? winners.Average(t => t.DurationHours)
                : 0;
            stats.AvgLosingTradeDurationHours = losers.Count > 0
                ? losers.Average(t => t.DurationHours)
                : 0;
        }
        private void CalculateReturnMetrics(
            AdvancedPerformanceMetrics metrics,
            List<TradeRecord> closedTrades,
            dynamic portfolio,
            decimal startingCapital)
        {
            metrics.TotalReturn = closedTrades.Sum(t => t.PnL);
            metrics.UnrealizedReturn = portfolio?.TotalUnrealizedProfit ?? 0m;
            if (startingCapital > 0)
            {
                metrics.ReturnPercent = metrics.TotalPortfolioReturn / startingCapital;
            }
            var firstTrade = closedTrades.OrderBy(t => t.OpenTime).FirstOrDefault();
            if (firstTrade != null && startingCapital > 0)
            {
                var totalDays = (_context.Algorithm.Time - firstTrade.OpenTime).TotalDays;
                if (totalDays > 0)
                {
                    var years = totalDays / 365.25;
                    var totalValue = startingCapital + metrics.TotalPortfolioReturn;
                    if (totalValue > 0 && years > 0)
                    {
                        metrics.CAGR = (decimal)(Math.Pow((double)(totalValue / startingCapital), 1.0 / years) - 1);
                    }
                }
            }
        }
        private void CalculateDrawdownMetrics(
            AdvancedPerformanceMetrics metrics,
            List<EquityPoint> equityCurve)
        {
            if (equityCurve == null || equityCurve.Count == 0)
            {
                return;
            }
            var maxDd = 0m;
            DateTime? maxDdStart = null;
            DateTime? maxDdEnd = null;
            DateTime? maxDdRecovery = null;
            var currentPeakDate = equityCurve[0].Timestamp;
            var allDrawdowns = new List<decimal>();
            var inDrawdown = false;
            DateTime? currentDrawdownStart = null;
            foreach (var point in equityCurve)
            {
                if (point.Drawdown < 0) // Drawdown is typically negative
                {
                    if (!inDrawdown)
                    {
                        inDrawdown = true;
                        currentDrawdownStart = point.Timestamp;
                    }
                    if (point.Drawdown < maxDd)
                    {
                        maxDd = point.Drawdown;
                        maxDdEnd = point.Timestamp;
                        maxDdStart = currentDrawdownStart;
                    }
                    allDrawdowns.Add(point.Drawdown);
                }
                else if (inDrawdown && point.Drawdown >= 0)
                {
                    if (maxDdStart.HasValue && maxDdRecovery == null && currentDrawdownStart == maxDdStart)
                    {
                        maxDdRecovery = point.Timestamp;
                    }
                    inDrawdown = false;
                    currentDrawdownStart = null;
                }
            }
            var latestPoint = equityCurve.Last();
            metrics.MaxDrawdown = Math.Abs(maxDd);
            metrics.MaxDrawdownPercent = Math.Abs(equityCurve.Min(p => p.DrawdownPercent));
            metrics.MaxDrawdownStartDate = maxDdStart;
            metrics.MaxDrawdownEndDate = maxDdEnd;
            metrics.MaxDrawdownRecoveryDate = maxDdRecovery;
            if (maxDdStart.HasValue && maxDdEnd.HasValue)
            {
                metrics.MaxDrawdownDurationDays = (int)(maxDdEnd.Value - maxDdStart.Value).TotalDays;
            }
            if (maxDdStart.HasValue && maxDdRecovery.HasValue)
            {
                metrics.MaxDrawdownRecoveryDays = (int)(maxDdRecovery.Value - maxDdStart.Value).TotalDays;
            }
            metrics.CurrentDrawdown = Math.Abs(latestPoint.Drawdown);
            metrics.CurrentDrawdownPercent = Math.Abs(latestPoint.DrawdownPercent);
            metrics.AverageDrawdown = allDrawdowns.Count > 0
                ? Math.Abs(allDrawdowns.Average())
                : 0;
        }
        private void CalculateRiskAdjustedMetrics(
            AdvancedPerformanceMetrics metrics,
            List<EquityPoint> equityCurve,
            decimal startingCapital)
        {
            if (equityCurve == null || equityCurve.Count < 2)
            {
                return;
            }
            var dailyReturns = new List<decimal>();
            for (int i = 1; i < equityCurve.Count; i++)
            {
                if (equityCurve[i - 1].Equity > 0)
                {
                    var ret = (equityCurve[i].Equity - equityCurve[i - 1].Equity) / equityCurve[i - 1].Equity;
                    dailyReturns.Add(ret);
                }
            }
            if (dailyReturns.Count < 2)
            {
                return;
            }
            var avgReturn = dailyReturns.Average();
            var variance = dailyReturns.Sum(r => (r - avgReturn) * (r - avgReturn)) / (dailyReturns.Count - 1);
            var dailyVol = (decimal)Math.Sqrt((double)variance);
            metrics.Volatility = dailyVol * (decimal)Math.Sqrt(TradingDaysPerYear);
            var negReturns = dailyReturns.Where(r => r < 0).ToList();
            if (negReturns.Count > 1)
            {
                var negVariance = negReturns.Sum(r => r * r) / (negReturns.Count - 1);
                metrics.DownsideDeviation = (decimal)Math.Sqrt((double)negVariance) * (decimal)Math.Sqrt(TradingDaysPerYear);
            }
            var annualizedReturn = avgReturn * TradingDaysPerYear;
            var excessReturn = annualizedReturn - RiskFreeRate;
            if (metrics.Volatility > 0)
            {
                metrics.SharpeRatio = excessReturn / metrics.Volatility;
            }
            if (metrics.DownsideDeviation > 0)
            {
                metrics.SortinoRatio = excessReturn / metrics.DownsideDeviation;
            }
            if (metrics.MaxDrawdownPercent > 0)
            {
                metrics.CalmarRatio = metrics.CAGR / metrics.MaxDrawdownPercent;
            }
        }
        private void CalculatePeriodPerformance(
            AdvancedPerformanceMetrics metrics,
            List<TradeRecord> closedTrades,
            List<EquityPoint> equityCurve)
        {
            if (equityCurve == null || equityCurve.Count < 2)
            {
                return;
            }
            var byDay = equityCurve
                .GroupBy(e => e.Timestamp.Date)
                .ToDictionary(g => g.Key, g => g.Last());
            var byWeek = equityCurve
                .GroupBy(e => GetWeekStart(e.Timestamp))
                .ToDictionary(g => g.Key, g => g.Last());
            var byMonth = equityCurve
                .GroupBy(e => new DateTime(e.Timestamp.Year, e.Timestamp.Month, 1))
                .ToDictionary(g => g.Key, g => g.Last());
            metrics.DailyPerformance = CalculatePeriodStats(byDay);
            metrics.WeeklyPerformance = CalculatePeriodStats(byWeek);
            metrics.MonthlyPerformance = CalculatePeriodStats(byMonth);
        }
        private PeriodPerformance CalculatePeriodStats(Dictionary<DateTime, EquityPoint> periodEnds)
        {
            var perf = new PeriodPerformance();
            if (periodEnds.Count < 2)
            {
                return perf;
            }
            var orderedPeriods = periodEnds.OrderBy(p => p.Key).ToList();
            var returns = new Dictionary<DateTime, decimal>();
            for (int i = 1; i < orderedPeriods.Count; i++)
            {
                var prev = orderedPeriods[i - 1].Value.Equity;
                var curr = orderedPeriods[i].Value.Equity;
                if (prev > 0)
                {
                    var ret = (curr - prev) / prev;
                    returns[orderedPeriods[i].Key] = ret;
                }
            }
            if (returns.Count == 0)
            {
                return perf;
            }
            perf.PeriodCount = returns.Count;
            perf.WinningPeriods = returns.Count(r => r.Value > 0);
            perf.LosingPeriods = returns.Count(r => r.Value < 0);
            perf.WinRate = perf.PeriodCount > 0 ? (decimal)perf.WinningPeriods / perf.PeriodCount : 0;
            var best = returns.OrderByDescending(r => r.Value).First();
            var worst = returns.OrderBy(r => r.Value).First();
            perf.BestPeriod = best.Value;
            perf.BestPeriodDate = best.Key;
            perf.WorstPeriod = worst.Value;
            perf.WorstPeriodDate = worst.Key;
            perf.AveragePeriod = returns.Values.Average();
            perf.Returns = returns;
            if (returns.Count > 1)
            {
                var avg = perf.AveragePeriod;
                var sumSq = returns.Values.Sum(r => (r - avg) * (r - avg));
                perf.StdDev = (decimal)Math.Sqrt((double)(sumSq / (returns.Count - 1)));
            }
            return perf;
        }
        private void CalculateAttribution(
            AdvancedPerformanceMetrics metrics,
            List<TradeRecord> closedTrades)
        {
            if (closedTrades == null || closedTrades.Count == 0)
            {
                return;
            }
            var byStrategy = closedTrades
                .Where(t => !string.IsNullOrEmpty(t.Strategy))
                .GroupBy(t => t.Strategy);
            foreach (var group in byStrategy)
            {
                var trades = group.ToList();
                metrics.PnLByStrategy[group.Key] = trades.Sum(t => t.PnL);
                metrics.TradeCountByStrategy[group.Key] = trades.Count;
                var winners = trades.Count(t => t.PnL > 0);
                metrics.WinRateByStrategy[group.Key] = trades.Count > 0
                    ? (decimal)winners / trades.Count
                    : 0;
            }
            var byUnderlying = closedTrades
                .Where(t => !string.IsNullOrEmpty(t.Underlying))
                .GroupBy(t => t.Underlying);
            foreach (var group in byUnderlying)
            {
                metrics.PnLByUnderlying[group.Key] = group.Sum(t => t.PnL);
            }
        }
        private DateTime GetWeekStart(DateTime date)
        {
            int diff = (7 + (date.DayOfWeek - DayOfWeek.Monday)) % 7;
            return date.AddDays(-diff).Date;
        }
    }
}
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
{
    public class CoreAlgoRiskManager
    {
        private readonly IAlgorithmContext _context;
        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;
        }
        public bool ValidateNewPosition(string symbol, decimal quantity, decimal estimatedPrice)
        {
            try
            {
                var algorithm = _context.Algorithm;
                var portfolio = algorithm.Portfolio;
                var currentMarginUtilization = GetPortfolioMarginUtilization();
                if (currentMarginUtilization > _maxPortfolioMarginUtilization)
                {
                    _context.Logger.Warning($"Risk: Portfolio margin utilization too high: {currentMarginUtilization:P1} > {_maxPortfolioMarginUtilization:P1}");
                    return false;
                }
                var proposedConcentration = CalculateAssetConcentrationAfterTrade(symbol, quantity, estimatedPrice);
                if (proposedConcentration > _maxAssetConcentration)
                {
                    _context.Logger.Warning($"Risk: Asset concentration would exceed limit: {proposedConcentration:P1} > {_maxAssetConcentration:P1} for {symbol}");
                    return false;
                }
                var totalOptionPositions = portfolio.Values.Count(x => x.Invested && x.Symbol.SecurityType == SecurityType.Option);
                if (totalOptionPositions >= 20) // Reasonable upper limit for option strategies
                {
                    _context.Logger.Warning($"Risk: Too many option positions: {totalOptionPositions} >= 20");
                    return false;
                }
                return true;
            }
            catch (Exception ex)
            {
                _context.Logger.Error($"Risk validation error for {symbol}: {ex.Message}");
                return false; // Fail safe - reject if we can't validate
            }
        }
        public PortfolioRiskMetrics GetPortfolioRisk()
        {
            var algorithm = _context.Algorithm;
            var portfolio = algorithm.Portfolio;
            return new PortfolioRiskMetrics
            {
                TotalPortfolioValue = portfolio.TotalPortfolioValue,
                TotalMarginUsed = portfolio.TotalMarginUsed,
                MarginRemaining = portfolio.MarginRemaining,
                Cash = portfolio.Cash,
                TotalUnrealizedProfit = portfolio.TotalUnrealizedProfit,
                MarginUtilization = GetPortfolioMarginUtilization(),
                AssetConcentrations = GetAssetConcentrations(),
                OptionPositionCount = portfolio.Values.Count(x => x.Invested && x.Symbol.SecurityType == SecurityType.Option),
                LargestAssetExposure = GetLargestAssetExposure(),
                RiskAlerts = GenerateRiskAlerts()
            };
        }
        private decimal GetPortfolioMarginUtilization()
        {
            var portfolio = _context.Algorithm.Portfolio;
            if (portfolio.TotalPortfolioValue <= 0)
                return 0;
            return portfolio.TotalMarginUsed / portfolio.TotalPortfolioValue;
        }
        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;
            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;
        }
        private decimal CalculateAssetConcentrationAfterTrade(string symbol, decimal quantity, decimal price)
        {
            var concentrations = GetAssetConcentrations();
            var portfolio = _context.Algorithm.Portfolio;
            var totalValue = portfolio.TotalPortfolioValue;
            var additionalValue = Math.Abs(quantity * price);
            var newTotalValue = totalValue + additionalValue;
            var underlyingSymbol = symbol; // Assume equity by default
            var currentAssetValue = concentrations.ContainsKey(underlyingSymbol)
                ? concentrations[underlyingSymbol] * totalValue
                : 0;
            var newAssetValue = currentAssetValue + additionalValue;
            return newTotalValue > 0 ? newAssetValue / newTotalValue : 0;
        }
        private decimal GetLargestAssetExposure()
        {
            var concentrations = GetAssetConcentrations();
            return concentrations.Values.DefaultIfEmpty(0).Max();
        }
        private List<RiskAlert> GenerateRiskAlerts()
        {
            var alerts = new List<RiskAlert>();
            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
                });
            }
            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;
        }
    }
    public class PortfolioRiskMetrics
    {
        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; }
        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; }
        public List<RiskAlert> RiskAlerts { get; set; } = new List<RiskAlert>();
    }
    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 System.Text.Json;
using System.Text.Json.Serialization;
using CoreAlgo.Architecture.Core.Interfaces;
using CoreAlgo.Architecture.Core.Models;
namespace CoreAlgo.Architecture.Core.Services
{
    public class EquityCurveTracker
    {
        private readonly IAlgorithmContext _context;
        private readonly List<EquityPoint> _equityCurve;
        private decimal _highWaterMark;
        private DateTime _lastRecordDate;
        private const string EquityCurveKey = "metrics/equity-curve.json";
        private const int MaxDataPoints = 2520; // ~10 years of daily data
        private static readonly JsonSerializerOptions SerializerOptions = new JsonSerializerOptions
        {
            WriteIndented = false,
            DefaultIgnoreCondition = JsonIgnoreCondition.WhenWritingDefault,
            PropertyNamingPolicy = JsonNamingPolicy.CamelCase
        };
        public EquityCurveTracker(IAlgorithmContext context)
        {
            _context = context ?? throw new ArgumentNullException(nameof(context));
            _equityCurve = new List<EquityPoint>();
            _highWaterMark = 0;
            _lastRecordDate = DateTime.MinValue;
        }
        public List<EquityPoint> GetEquityCurve()
        {
            return _equityCurve.ToList();
        }
        public decimal HighWaterMark => _highWaterMark;
        public decimal CurrentDrawdown
        {
            get
            {
                if (_highWaterMark <= 0) return 0;
                var equity = _context.Algorithm.Portfolio.TotalPortfolioValue;
                return Math.Min(0, equity - _highWaterMark);
            }
        }
        public decimal CurrentDrawdownPercent
        {
            get
            {
                if (_highWaterMark <= 0) return 0;
                var equity = _context.Algorithm.Portfolio.TotalPortfolioValue;
                return Math.Min(0, (equity - _highWaterMark) / _highWaterMark);
            }
        }
        public void RecordEquity()
        {
            var now = _context.Algorithm.Time;
            var equity = _context.Algorithm.Portfolio.TotalPortfolioValue;
            if (now.Date == _lastRecordDate.Date && _equityCurve.Count > 0)
            {
                var lastPoint = _equityCurve.Last();
                UpdatePoint(lastPoint, equity);
                return;
            }
            var point = new EquityPoint
            {
                Timestamp = now,
                Equity = equity
            };
            UpdatePoint(point, equity);
            _equityCurve.Add(point);
            _lastRecordDate = now.Date;
            while (_equityCurve.Count > MaxDataPoints)
            {
                _equityCurve.RemoveAt(0);
            }
        }
        public void RecordEquity(decimal equity, DateTime timestamp)
        {
            if (timestamp.Date == _lastRecordDate.Date && _equityCurve.Count > 0)
            {
                var lastPoint = _equityCurve.Last();
                UpdatePoint(lastPoint, equity);
                return;
            }
            var point = new EquityPoint
            {
                Timestamp = timestamp,
                Equity = equity
            };
            UpdatePoint(point, equity);
            _equityCurve.Add(point);
            _lastRecordDate = timestamp.Date;
            while (_equityCurve.Count > MaxDataPoints)
            {
                _equityCurve.RemoveAt(0);
            }
        }
        private void UpdatePoint(EquityPoint point, decimal equity)
        {
            point.Equity = equity;
            if (equity > _highWaterMark)
            {
                _highWaterMark = equity;
            }
            point.HighWaterMark = _highWaterMark;
            point.Drawdown = equity - _highWaterMark; // Negative when in drawdown
            point.DrawdownPercent = _highWaterMark > 0
                ? (equity - _highWaterMark) / _highWaterMark
                : 0;
        }
        public void Initialize(decimal startingCapital)
        {
            if (_equityCurve.Count == 0)
            {
                _highWaterMark = startingCapital;
                var point = new EquityPoint
                {
                    Timestamp = _context.Algorithm.Time,
                    Equity = startingCapital,
                    HighWaterMark = startingCapital,
                    Drawdown = 0,
                    DrawdownPercent = 0
                };
                _equityCurve.Add(point);
                _lastRecordDate = _context.Algorithm.Time.Date;
            }
        }
        public void Save()
        {
            try
            {
                var data = new EquityCurveData
                {
                    Version = "1.0",
                    HighWaterMark = _highWaterMark,
                    LastRecordDate = _lastRecordDate,
                    Points = _equityCurve
                };
                var json = JsonSerializer.Serialize(data, SerializerOptions);
                var bytes = System.Text.Encoding.UTF8.GetBytes(json);
                _context.Algorithm.ObjectStore.SaveBytes(EquityCurveKey, bytes);
                _context.Logger.Trace($"EquityCurveTracker: Saved {_equityCurve.Count} equity points");
            }
            catch (Exception ex)
            {
                _context.Logger.Error($"EquityCurveTracker: Error saving: {ex.Message}");
            }
        }
        public bool Load()
        {
            try
            {
                if (!_context.Algorithm.ObjectStore.ContainsKey(EquityCurveKey))
                {
                    _context.Logger.Info("EquityCurveTracker: No saved data found");
                    return false;
                }
                var bytes = _context.Algorithm.ObjectStore.ReadBytes(EquityCurveKey);
                var json = System.Text.Encoding.UTF8.GetString(bytes);
                var data = JsonSerializer.Deserialize<EquityCurveData>(json, SerializerOptions);
                if (data == null || data.Points == null)
                {
                    _context.Logger.Warning("EquityCurveTracker: Invalid saved data");
                    return false;
                }
                _equityCurve.Clear();
                _equityCurve.AddRange(data.Points);
                _highWaterMark = data.HighWaterMark;
                _lastRecordDate = data.LastRecordDate;
                _context.Logger.Info($"EquityCurveTracker: Loaded {_equityCurve.Count} equity points, HWM: ${_highWaterMark:N2}");
                return true;
            }
            catch (Exception ex)
            {
                _context.Logger.Error($"EquityCurveTracker: Error loading: {ex.Message}");
                return false;
            }
        }
        public void ClearIfBacktest()
        {
            if (_context.Algorithm.LiveMode)
            {
                return;
            }
            _equityCurve.Clear();
            _highWaterMark = 0;
            _lastRecordDate = DateTime.MinValue;
            if (_context.Algorithm.ObjectStore.ContainsKey(EquityCurveKey))
            {
                _context.Algorithm.ObjectStore.Delete(EquityCurveKey);
                _context.Logger.Info("EquityCurveTracker: Cleared for backtest");
            }
        }
        public DrawdownSummary GetDrawdownSummary()
        {
            var summary = new DrawdownSummary();
            if (_equityCurve.Count == 0)
            {
                return summary;
            }
            var current = _equityCurve.Last();
            summary.CurrentEquity = current.Equity;
            summary.HighWaterMark = _highWaterMark;
            summary.CurrentDrawdown = Math.Abs(current.Drawdown);
            summary.CurrentDrawdownPercent = Math.Abs(current.DrawdownPercent);
            var maxDdPoint = _equityCurve.OrderBy(p => p.Drawdown).First();
            summary.MaxDrawdown = Math.Abs(maxDdPoint.Drawdown);
            summary.MaxDrawdownPercent = Math.Abs(maxDdPoint.DrawdownPercent);
            summary.MaxDrawdownDate = maxDdPoint.Timestamp;
            summary.IsInDrawdown = current.Drawdown < 0;
            if (summary.IsInDrawdown)
            {
                for (int i = _equityCurve.Count - 1; i >= 0; i--)
                {
                    if (_equityCurve[i].Drawdown >= 0)
                    {
                        summary.DrawdownStartDate = _equityCurve[i].Timestamp;
                        summary.DrawdownDurationDays = (int)(_context.Algorithm.Time - summary.DrawdownStartDate.Value).TotalDays;
                        break;
                    }
                }
            }
            return summary;
        }
    }
    public class EquityCurveData
    {
        public string Version { get; set; }
        public decimal HighWaterMark { get; set; }
        public DateTime LastRecordDate { get; set; }
        public List<EquityPoint> Points { get; set; } = new List<EquityPoint>();
    }
    public class DrawdownSummary
    {
        public decimal CurrentEquity { get; set; }
        public decimal HighWaterMark { get; set; }
        public decimal CurrentDrawdown { get; set; }
        public decimal CurrentDrawdownPercent { get; set; }
        public decimal MaxDrawdown { get; set; }
        public decimal MaxDrawdownPercent { get; set; }
        public DateTime? MaxDrawdownDate { get; set; }
        public bool IsInDrawdown { get; set; }
        public DateTime? DrawdownStartDate { get; set; }
        public int DrawdownDurationDays { get; set; }
    }
}
using System;
using System.Collections.Generic;
using System.Linq;
using QuantConnect;
using CoreAlgo.Architecture.Core.Interfaces;
using CoreAlgo.Architecture.Core.Implementations;
using CoreAlgo.Architecture.Core.Models;
namespace CoreAlgo.Architecture.Core.Services
{
    public class PortfolioReporter
    {
        private readonly IAlgorithmContext _context;
        private readonly CoreAlgoRiskManager _riskManager;
        private readonly AdvancedMetricsCalculator _metricsCalculator;
        private readonly EquityCurveTracker _equityCurveTracker;
        private decimal _startingCapital;
        public PortfolioReporter(IAlgorithmContext context, CoreAlgoRiskManager riskManager)
        {
            _context = context ?? throw new ArgumentNullException(nameof(context));
            _riskManager = riskManager ?? throw new ArgumentNullException(nameof(riskManager));
            _metricsCalculator = new AdvancedMetricsCalculator(context);
            _equityCurveTracker = new EquityCurveTracker(context);
        }
        public void Initialize(decimal startingCapital)
        {
            _startingCapital = startingCapital;
            _equityCurveTracker.ClearIfBacktest();
            _equityCurveTracker.Initialize(startingCapital);
        }
        public void RecordDailyEquity()
        {
            _equityCurveTracker.RecordEquity();
        }
        public EquityCurveTracker EquityCurveTracker => _equityCurveTracker;
        public DrawdownSummary GetDrawdownSummary()
        {
            return _equityCurveTracker.GetDrawdownSummary();
        }
        public AdvancedPerformanceMetrics GenerateAdvancedMetrics(TradeTracker tracker, string strategyName = "")
        {
            var equityCurve = _equityCurveTracker.GetEquityCurve();
            return _metricsCalculator.CalculateMetrics(
                tracker,
                equityCurve,
                _startingCapital,
                strategyName
            );
        }
        public void LogPerformanceSummary(TradeTracker tracker, string strategyName = "")
        {
            var metrics = GenerateAdvancedMetrics(tracker, strategyName);
            var portfolio = _context.Algorithm.Portfolio;
            var dd = _equityCurveTracker.GetDrawdownSummary();
            _context.Logger.Info("=== ADVANCED PERFORMANCE METRICS ===");
            _context.Logger.Info($"Strategy: {strategyName}");
            _context.Logger.Info($"Portfolio Value: ${portfolio.TotalPortfolioValue:N2}");
            _context.Logger.Info($"Starting Capital: ${_startingCapital:N2}");
            _context.Logger.Info("");
            _context.Logger.Info("--- RETURNS ---");
            _context.Logger.Info($"Total Return: ${metrics.TotalReturn:N2} ({metrics.ReturnPercent:P2})");
            _context.Logger.Info($"  Realized: ${metrics.TradeStats.NetProfit:N2}");
            _context.Logger.Info($"  Unrealized: ${metrics.UnrealizedReturn:N2}");
            _context.Logger.Info($"CAGR: {metrics.CAGR:P2}");
            _context.Logger.Info("");
            _context.Logger.Info("--- RISK-ADJUSTED METRICS ---");
            _context.Logger.Info($"Sharpe Ratio: {metrics.SharpeRatio:F3}");
            _context.Logger.Info($"Sortino Ratio: {metrics.SortinoRatio:F3}");
            _context.Logger.Info($"Calmar Ratio: {metrics.CalmarRatio:F3}");
            _context.Logger.Info($"Volatility (Ann.): {metrics.Volatility:P2}");
            _context.Logger.Info("");
            _context.Logger.Info("--- DRAWDOWN ---");
            _context.Logger.Info($"Current Drawdown: ${dd.CurrentDrawdown:N2} ({dd.CurrentDrawdownPercent:P2})");
            _context.Logger.Info($"Max Drawdown: ${dd.MaxDrawdown:N2} ({dd.MaxDrawdownPercent:P2})");
            if (dd.IsInDrawdown && dd.DrawdownStartDate.HasValue)
            {
                _context.Logger.Info($"In Drawdown: {dd.DrawdownDurationDays} days (since {dd.DrawdownStartDate:yyyy-MM-dd})");
            }
            _context.Logger.Info("");
            var stats = metrics.TradeStats;
            _context.Logger.Info("--- TRADE STATISTICS ---");
            _context.Logger.Info($"Total Trades: {stats.TotalTrades} (W:{stats.WinningTrades} / L:{stats.LosingTrades})");
            _context.Logger.Info($"Win Rate: {stats.WinRate:P1}");
            _context.Logger.Info($"Avg Win: ${stats.AverageWin:N2} | Avg Loss: ${stats.AverageLoss:N2}");
            _context.Logger.Info($"Largest Win: ${stats.LargestWin:N2} | Largest Loss: ${stats.LargestLoss:N2}");
            _context.Logger.Info($"Profit Factor: {stats.ProfitFactor:F2}");
            _context.Logger.Info($"Expectancy: ${stats.Expectancy:N2} per trade");
            _context.Logger.Info($"Kelly Criterion: {stats.KellyCriterion:P1}");
            _context.Logger.Info("");
            _context.Logger.Info("--- STREAKS ---");
            _context.Logger.Info($"Current: {(stats.CurrentWinStreak > 0 ? $"{stats.CurrentWinStreak}W" : $"{stats.CurrentLossStreak}L")}");
            _context.Logger.Info($"Max Win Streak: {stats.MaxWinStreak} | Max Loss Streak: {stats.MaxLossStreak}");
            _context.Logger.Info("");
            if (metrics.MonthlyPerformance.PeriodCount > 0)
            {
                var monthly = metrics.MonthlyPerformance;
                _context.Logger.Info("--- MONTHLY PERFORMANCE ---");
                _context.Logger.Info($"Profitable Months: {monthly.WinningPeriods}/{monthly.PeriodCount} ({monthly.WinRate:P0})");
                _context.Logger.Info($"Best Month: {monthly.BestPeriod:P2} ({monthly.BestPeriodDate:yyyy-MM})");
                _context.Logger.Info($"Worst Month: {monthly.WorstPeriod:P2} ({monthly.WorstPeriodDate:yyyy-MM})");
                _context.Logger.Info($"Avg Monthly Return: {monthly.AveragePeriod:P2}");
            }
            if (metrics.PnLByStrategy.Count > 1)
            {
                _context.Logger.Info("");
                _context.Logger.Info("--- P&L BY STRATEGY ---");
                foreach (var kvp in metrics.PnLByStrategy.OrderByDescending(x => x.Value))
                {
                    var winRate = metrics.WinRateByStrategy.GetValueOrDefault(kvp.Key, 0);
                    var count = metrics.TradeCountByStrategy.GetValueOrDefault(kvp.Key, 0);
                    _context.Logger.Info($"  {kvp.Key}: ${kvp.Value:N2} ({count} trades, {winRate:P0} win)");
                }
            }
            if (metrics.PnLByUnderlying.Count > 1)
            {
                _context.Logger.Info("");
                _context.Logger.Info("--- P&L BY UNDERLYING ---");
                foreach (var kvp in metrics.PnLByUnderlying.OrderByDescending(x => x.Value).Take(5))
                {
                    _context.Logger.Info($"  {kvp.Key}: ${kvp.Value:N2}");
                }
            }
            _context.Logger.Info("=====================================");
        }
        public void SaveEquityCurve()
        {
            _equityCurveTracker.Save();
        }
        public bool LoadEquityCurve()
        {
            return _equityCurveTracker.Load();
        }
    }
}
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
{
    public class PositionOverlapManager
    {
        private readonly IAlgorithmContext _context;
        private readonly List<IPositionOverlapRule> _rules;
        private readonly ISmartLogger _logger;
        public PositionOverlapManager(IAlgorithmContext context)
        {
            _context = context ?? throw new ArgumentNullException(nameof(context));
            _logger = context.Logger;
            _rules = new List<IPositionOverlapRule>();
            InitializeBuiltInRules();
        }
        public ValidationResult ValidateComboOrder(List<QuantConnect.Orders.Leg> legs, int quantity, string strategyTag = "")
        {
            try
            {
                _logger.Debug($"[COMBO VALIDATION] Validating combo order: {legs.Count} legs, qty:{quantity}, strategy:{strategyTag}");
                var existingPositions = _context.Algorithm.Portfolio
                    .Where(p => p.Value.Invested)
                    .ToList();
                return ValidateComboStrategy(legs, quantity, existingPositions, strategyTag);
            }
            catch (Exception ex)
            {
                var errorMsg = $"Error in combo order validation: {ex.Message}";
                _logger.Error(errorMsg);
                return ValidationResult.Error(errorMsg);
            }
        }
        public ValidationResult ValidateNewPosition(Symbol symbol, decimal quantity, string strategyTag = "")
        {
            try
            {
                var existingPositions = _context.Algorithm.Portfolio
                    .Where(p => p.Value.Invested)
                    .ToList();
                _logger.Debug($"[OVERLAP CHECK] Validating {symbol} qty:{quantity} strategy:{strategyTag}");
                _logger.Debug($"[OVERLAP CHECK] Existing positions: {existingPositions.Count}");
                foreach (var rule in _rules)
                {
                    var result = rule.Validate(symbol, quantity, existingPositions, strategyTag);
                    if (!result.IsValid)
                    {
                        _logger.Warning($"[OVERLAP BLOCKED] {rule.GetType().Name}: {result.Message}");
                        return result;
                    }
                }
                _logger.Debug($"[OVERLAP ALLOWED] Position validated successfully");
                return ValidationResult.Success();
            }
            catch (Exception ex)
            {
                var errorMsg = $"Error in position overlap validation: {ex.Message}";
                _logger.Error(errorMsg);
                return ValidationResult.Error(errorMsg);
            }
        }
        private ValidationResult ValidateComboStrategy(List<QuantConnect.Orders.Leg> legs, int quantity,
            List<KeyValuePair<Symbol, SecurityHolding>> existingPositions, string strategyTag)
        {
            var orderStructure = AnalyzeOrderStructure(legs);
            _logger.Debug($"[COMBO STRATEGY] Analyzed structure: {orderStructure.LegCount} legs, {orderStructure.PutCount} puts, {orderStructure.CallCount} calls");
            if (orderStructure.LegCount == 4 && orderStructure.PutCount == 2 && orderStructure.CallCount == 2)
            {
                return ValidateFourLegCombo(legs, quantity, existingPositions, orderStructure);
            }
            else if (orderStructure.LegCount == 2)
            {
                return ValidateTwoLegCombo(legs, quantity, existingPositions, orderStructure);
            }
            else
            {
                return ValidateGenericCombo(legs, quantity, existingPositions, orderStructure);
            }
        }
        private ValidationResult ValidateFourLegCombo(List<QuantConnect.Orders.Leg> legs, int quantity,
            List<KeyValuePair<Symbol, SecurityHolding>> existingPositions, OrderStructureAnalysis structure)
        {
            var underlying = legs.First().Symbol.Underlying;
            var expiry = legs.First().Symbol.ID.Date;
            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();
            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})");
            }
            _logger.Debug($"[4-LEG COMBO] Validation passed: {activeFourLegCombos}/{maxComboPositions} positions");
            return ValidationResult.Success();
        }
        private ValidationResult ValidateTwoLegCombo(List<QuantConnect.Orders.Leg> legs, int quantity,
            List<KeyValuePair<Symbol, SecurityHolding>> existingPositions, OrderStructureAnalysis structure)
        {
            var underlying = legs.First().Symbol.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();
            var maxComboPositions = GetComboPositionLimit(2);
            if (activeTwoLegCombos >= maxComboPositions)
            {
                return ValidationResult.Blocked(
                    $"2-leg combo limit exceeded: {activeTwoLegCombos} active positions on {underlying.Value} (max: {maxComboPositions})");
            }
            _logger.Debug($"[2-LEG COMBO] Validation passed: {activeTwoLegCombos}/{maxComboPositions} positions");
            return ValidationResult.Success();
        }
        private ValidationResult ValidateGenericCombo(List<QuantConnect.Orders.Leg> legs, int quantity,
            List<KeyValuePair<Symbol, SecurityHolding>> existingPositions, OrderStructureAnalysis structure)
        {
            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();
            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})");
            }
            _logger.Debug($"[GENERIC COMBO] Validation passed: {activeComboPositions}/{maxComboPositions} positions");
            return ValidationResult.Success();
        }
        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)
            };
            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;
        }
        private int GetComboPositionLimit(int legCount)
        {
            try
            {
                var algorithm = _context.Algorithm;
                if (algorithm != null)
                {
                    var maxComboPositions = algorithm.GetParameter("MaxPositionsPerCombo");
                    if (int.TryParse(maxComboPositions, out var configLimit))
                    {
                        return configLimit;
                    }
                }
            }
            catch (Exception ex)
            {
                _logger.Debug($"[CONFIG] Could not read MaxPositionsPerCombo: {ex.Message}");
            }
            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
            };
        }
        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; }
        }
        public void AddRule(IPositionOverlapRule rule)
        {
            if (rule == null) throw new ArgumentNullException(nameof(rule));
            _rules.Add(rule);
            _logger.Debug($"[OVERLAP MANAGER] Added rule: {rule.GetType().Name}");
        }
        public void RemoveRule<T>() where T : IPositionOverlapRule
        {
            var removed = _rules.RemoveAll(r => r is T);
            if (removed > 0)
            {
                _logger.Debug($"[OVERLAP MANAGER] Removed {removed} rule(s) of type: {typeof(T).Name}");
            }
        }
        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;
        }
        private void InitializeBuiltInRules()
        {
            AddRule(new UnderlyingConflictRule(_context));
            AddRule(new CollateralValidationRule(_context));
            AddRule(new StrikeOverlapRule(_context));
            _logger.Info($"[OVERLAP MANAGER] Initialized with {_rules.Count} built-in rules");
        }
        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();
        }
        public bool HasActivePositions(Symbol underlying)
        {
            return GetPositionsForUnderlying(underlying).Any();
        }
        public int GetActiveOptionPositionCount(Symbol underlying)
        {
            return _context.Algorithm.Portfolio.Values
                .Count(h => h.Invested &&
                           h.Symbol.SecurityType == SecurityType.Option &&
                           h.Symbol.Underlying == underlying);
        }
        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)
            {
                _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 ISmartLogger Logger { get; }

        public SimpleAlgorithmContext(QCAlgorithm algorithm, ISmartLogger logger)
        {
            Algorithm = algorithm ?? throw new ArgumentNullException(nameof(algorithm));
            Logger = logger ?? throw new ArgumentNullException(nameof(logger));
        }
    }
}
using System;
using System.Collections.Generic;
using System.Linq;
using QuantConnect;
using QuantConnect.Algorithm;
using QuantConnect.Orders;
using QuantConnect.Scheduling;
using QuantConnect.Securities;
using CoreAlgo.Architecture.Core.Execution;
using CoreAlgo.Architecture.Core.Interfaces;
using CoreAlgo.Architecture.QC.Helpers;
namespace CoreAlgo.Architecture.Core.Services
{
    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>();
        }
        public void SetPricingEngine(ISmartPricingEngine pricingEngine)
        {
            _pricingEngine = pricingEngine;
        }
        public void SetOverlapManager(PositionOverlapManager overlapManager)
        {
            _overlapManager = overlapManager;
        }
        public OrderTicket SmartMarketOrder(Symbol symbol, decimal quantity, string tag = "")
        {
            if (_overlapManager != null)
            {
                var validation = _overlapManager.ValidateNewPosition(symbol, quantity, tag);
                if (!validation.IsValid)
                {
                    _context.Logger.Warning($"[OVERLAP PREVENTION] Order blocked: {validation.Message}");
                    return null;
                }
            }
            if (_pricingEngine == null)
            {
                return _algorithm.MarketOrder(symbol, quantity, tag: tag, asynchronous: true);
            }
            try
            {
                var security = _algorithm.Securities[symbol];
                var quote = GetCurrentQuote(security);
                if (quote == null || quote.Spread > 10m) // Skip if spread too wide
                {
                    _context.Logger.Debug($"SmartOrder: Using market order for {symbol} due to wide spread or no quote");
                    return _algorithm.MarketOrder(symbol, quantity, tag: tag, asynchronous: true);
                }
                var direction = quantity > 0 ? CoreAlgo.Architecture.Core.Execution.OrderDirection.Buy : CoreAlgo.Architecture.Core.Execution.OrderDirection.Sell;
                var initialPrice = _pricingEngine.CalculateInitialPrice(quote, direction);
                var roundedInitialPrice = PriceRounding.RoundLimitPrice(_algorithm.Securities, symbol, quantity, initialPrice);
                var ticket = _algorithm.LimitOrder(symbol, quantity, roundedInitialPrice, tag: tag + " [Smart]", asynchronous: true);
                if (ticket == null || ticket.Status == OrderStatus.Invalid)
                {
                    _context.Logger.Error($"SmartOrder: Failed to place initial limit order for {symbol}");
                    return _algorithm.MarketOrder(symbol, quantity, tag: tag, asynchronous: true);
                }
                var tracker = new SmartOrderTracker(ticket, quote, direction, SmartPricingMode.Normal, roundedInitialPrice);
                _activeOrders[ticket.OrderId] = tracker;
                _context.Logger.Info($"SmartOrder: Placed initial limit order for {symbol} at ${initialPrice:F2} " +
                                              $"(Mid: ${quote.Price:F2}, Spread: ${quote.Spread:F2})");
                ScheduleNextPricingUpdate(tracker);
                return ticket;
            }
            catch (Exception ex)
            {
                _context.Logger.Error($"SmartOrder error: {ex.Message}");
                return _algorithm.MarketOrder(symbol, quantity, tag: tag, asynchronous: true);
            }
        }
        public List<OrderTicket> SmartComboMarketOrder(List<Leg> legs, int quantity, string tag = "")
        {
            if (_overlapManager != null)
            {
                var validation = _overlapManager.ValidateComboOrder(legs, quantity, tag);
                if (!validation.IsValid)
                {
                    _context.Logger.Warning($"[COMBO ORDER BLOCKED] {validation.Message}");
                    return new List<OrderTicket>(); // Return empty list to indicate order was blocked
                }
                _context.Logger.Debug($"[COMBO ORDER APPROVED] {legs.Count}-leg order validated for {tag}");
            }
            if (_pricingEngine == null || _pricingEngine.Mode == SmartPricingMode.Off)
            {
                _context.Logger.Debug($"SmartCombo: Using basic combo market order (no smart pricing)");
                return _algorithm.ComboMarketOrder(legs, quantity, tag: tag, asynchronous: true);
            }
            try
            {
                var comboQuote = ComboQuote.FromSecurities(legs, _algorithm.Securities);
                if (comboQuote == null)
                {
                    _context.Logger.Warning($"SmartCombo: No valid quotes available for combo, using market order");
                    return _algorithm.ComboMarketOrder(legs, quantity, tag: tag, asynchronous: true);
                }
                var comboDirection = ComboPricingEngine.DetermineComboDirection(legs);
                if (!_pricingEngine.ShouldAttemptComboPricing(comboQuote, comboDirection))
                {
                    _context.Logger.Debug($"SmartCombo: Conditions not suitable for smart pricing, using market order");
                    return _algorithm.ComboMarketOrder(legs, quantity, tag: tag, asynchronous: true);
                }
                var initialNetPrice = _pricingEngine.CalculateInitialComboPrice(legs, comboQuote, comboDirection);
                if (!initialNetPrice.HasValue)
                {
                    _context.Logger.Debug($"SmartCombo: Could not calculate initial price, using market order");
                    return _algorithm.ComboMarketOrder(legs, quantity, tag: tag, asynchronous: true);
                }
                var comboTickets = _algorithm.ComboLimitOrder(legs, quantity, initialNetPrice.Value, tag: tag + " [SmartCombo]", asynchronous: true);
                if (comboTickets == null || comboTickets.Count == 0)
                {
                    _context.Logger.Error($"SmartCombo: Failed to place combo limit order, trying market order fallback");
                    return _algorithm.ComboMarketOrder(legs, quantity, tag: tag, asynchronous: true);
                }
                var roundedInitialNetPrice = PriceRounding.RoundComboNetPrice(
                    _algorithm.Securities,
                    legs,
                    initialNetPrice.Value,
                    comboDirection == CoreAlgo.Architecture.Core.Execution.OrderDirection.Buy);
                if (Math.Abs(roundedInitialNetPrice - initialNetPrice.Value) > 0m)
                {
                    foreach (var comboTicket in comboTickets)
                    {
                        comboTicket.Update(new UpdateOrderFields { LimitPrice = roundedInitialNetPrice });
                    }
                }
                var comboTracker = new ComboOrderTracker(comboTickets, legs, comboQuote,
                    comboDirection, _pricingEngine.Mode, roundedInitialNetPrice);
                _activeComboOrders[comboTracker.PrimaryOrderId] = comboTracker;
                _context.Logger.Info($"SmartCombo: Placed {legs.Count}-leg combo limit order " +
                                              $"at net price ${roundedInitialNetPrice:F2} " +
                                              $"(NetBid: ${comboQuote.NetBid:F2}, NetAsk: ${comboQuote.NetAsk:F2}, " +
                                              $"NetMid: ${comboQuote.NetMid:F2}, Direction: {comboDirection})");
                ScheduleNextComboPricingUpdate(comboTracker);
                return comboTickets;
            }
            catch (Exception ex)
            {
                _context.Logger.Error($"SmartCombo error: {ex.Message}");
                return _algorithm.ComboMarketOrder(legs, quantity, tag: tag, asynchronous: true);
            }
        }
        public void OnOrderEvent(OrderEvent orderEvent)
        {
            if (_activeOrders.TryGetValue(orderEvent.OrderId, out var tracker))
            {
                switch (orderEvent.Status)
                {
                    case OrderStatus.Filled:
                        _context.Logger.Info($"SmartOrder: Order {orderEvent.OrderId} filled at ${orderEvent.FillPrice:F2} " +
                                                      $"after {tracker.AttemptNumber} attempts");
                        CleanupOrder(tracker);
                        break;
                    case OrderStatus.PartiallyFilled:
                        _context.Logger.Debug($"SmartOrder: Order {orderEvent.OrderId} partially filled " +
                                                       $"({orderEvent.FillQuantity}/{tracker.OrderTicket.Quantity})");
                        tracker.UpdatePartialFill(orderEvent);
                        break;
                    case OrderStatus.Canceled:
                    case OrderStatus.Invalid:
                        _context.Logger.Warning($"SmartOrder: Order {orderEvent.OrderId} {orderEvent.Status}");
                        CleanupOrder(tracker);
                        break;
                }
                return;
            }
            foreach (var comboTracker in _activeComboOrders.Values)
            {
                var matchingTicket = comboTracker.ComboTickets.FirstOrDefault(t => t.OrderId == orderEvent.OrderId);
                if (matchingTicket != null)
                {
                    HandleComboOrderEvent(comboTracker, orderEvent, matchingTicket);
                    return;
                }
            }
        }
        private void HandleComboOrderEvent(ComboOrderTracker comboTracker, OrderEvent orderEvent, OrderTicket matchingTicket)
        {
            switch (orderEvent.Status)
            {
                case OrderStatus.Filled:
                    _context.Logger.Debug($"SmartCombo: Leg {orderEvent.OrderId} of combo {comboTracker.PrimaryOrderId} " +
                                                   $"filled at ${orderEvent.FillPrice:F2}");
                    if (comboTracker.IsCompletelyFilled)
                    {
                        _context.Logger.Info($"SmartCombo: Combo order {comboTracker.PrimaryOrderId} completely filled " +
                                                      $"after {comboTracker.AttemptNumber} pricing attempts");
                        CleanupComboOrder(comboTracker);
                    }
                    break;
                case OrderStatus.PartiallyFilled:
                    _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:
                    _context.Logger.Warning($"SmartCombo: Leg {orderEvent.OrderId} of combo {comboTracker.PrimaryOrderId} {orderEvent.Status}");
                    _context.Logger.Warning($"SmartCombo: Combo order {comboTracker.PrimaryOrderId} failed due to leg {orderEvent.OrderId}");
                    CleanupComboOrder(comboTracker);
                    break;
            }
        }
        private void UpdateOrderPrice(SmartOrderTracker tracker)
        {
            try
            {
                if (tracker.ScheduledEvent != null)
                {
                    _scheduledEvents.Remove(tracker.ScheduledEvent);
                    tracker.ScheduledEvent = null;
                }
                if (!_activeOrders.ContainsKey(tracker.OrderTicket.OrderId) ||
                    tracker.OrderTicket.Status == OrderStatus.Filled)
                {
                    return;
                }
                if (tracker.AttemptNumber >= _pricingEngine.GetMaxAttempts())
                {
                    _context.Logger.Info($"SmartOrder: Max attempts reached for order {tracker.OrderTicket.OrderId}, " +
                                                  "converting to market order");
                    tracker.OrderTicket.Cancel("Max pricing attempts reached");
                    var remainingQty = tracker.OrderTicket.Quantity - tracker.OrderTicket.QuantityFilled;
                    if (remainingQty != 0)
                    {
                        _algorithm.MarketOrder(tracker.OrderTicket.Symbol, remainingQty,
                            tag: tracker.OrderTicket.Tag + " [Smart-Market]", asynchronous: true);
                    }
                    CleanupOrder(tracker);
                    return;
                }
                var security = _algorithm.Securities[tracker.OrderTicket.Symbol];
                var currentQuote = GetCurrentQuote(security);
                if (currentQuote == null)
                {
                    _context.Logger.Warning($"SmartOrder: No quote available for {tracker.OrderTicket.Symbol}");
                    ScheduleNextPricingUpdate(tracker);
                    return;
                }
                tracker.AttemptNumber++;
                var nextPrice = _pricingEngine.CalculateNextPrice(
                    tracker.CurrentPrice, currentQuote, tracker.OrderDirection, tracker.AttemptNumber);
                if (nextPrice.HasValue && Math.Abs(nextPrice.Value - tracker.CurrentPrice) > 0.01m)
                {
                    var roundedNextPrice = PriceRounding.RoundLimitPrice(
                        _algorithm.Securities,
                        tracker.OrderTicket.Symbol,
                        tracker.OrderTicket.Quantity,
                        nextPrice.Value);
                    var updateFields = new UpdateOrderFields { LimitPrice = roundedNextPrice };
                    var response = tracker.OrderTicket.Update(updateFields);
                    if (response.IsSuccess)
                    {
                        tracker.CurrentPrice = roundedNextPrice;
                        _context.Logger.Debug($"SmartOrder: Updated order {tracker.OrderTicket.OrderId} " +
                                                       $"price to ${roundedNextPrice:F2} (attempt {tracker.AttemptNumber})");
                    }
                    else
                    {
                        _context.Logger.Warning($"SmartOrder: Failed to update order price: {response.ErrorMessage}");
                    }
                }
                ScheduleNextPricingUpdate(tracker);
            }
            catch (Exception ex)
            {
                _context.Logger.Error($"SmartOrder update error: {ex.Message}");
                CleanupOrder(tracker);
            }
        }
        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;
        }
        private void CleanupOrder(SmartOrderTracker tracker)
        {
            _activeOrders.Remove(tracker.OrderTicket.OrderId);
            if (tracker.ScheduledEvent != null)
            {
                _algorithm.Schedule.Remove(tracker.ScheduledEvent);
                _scheduledEvents.Remove(tracker.ScheduledEvent);
            }
        }
        private Quote GetCurrentQuote(Security security)
        {
            if (security.BidPrice == 0 || security.AskPrice == 0)
                return null;
            return new Quote(security.BidPrice, security.AskPrice);
        }
        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;
        }
        private void UpdateComboOrderPrice(ComboOrderTracker tracker)
        {
            try
            {
                if (tracker.ScheduledEvent != null)
                {
                    _scheduledEvents.Remove(tracker.ScheduledEvent);
                    tracker.ScheduledEvent = null;
                }
                if (!_activeComboOrders.ContainsKey(tracker.PrimaryOrderId) || tracker.IsCompletelyFilled)
                {
                    return;
                }
                var maxAttempts = _pricingEngine.GetMaxAttempts();
                var maxRuntime = TimeSpan.FromMinutes(5); // Max 5 minutes for combo orders
                if (!tracker.ShouldContinuePricing(maxAttempts, maxRuntime))
                {
                    _context.Logger.Info($"SmartCombo: Max attempts/timeout reached for combo {tracker.PrimaryOrderId} " +
                                                   $"(attempts: {tracker.AttemptNumber}/{maxAttempts}, runtime: {tracker.GetRuntime().TotalSeconds:F0}s), " +
                                                   "converting to market order");
                    CancelComboOrder(tracker, "Max pricing attempts/timeout reached");
                    if (!tracker.IsCompletelyFilled)
                    {
                        var remainingQty = CalculateComboRemainingQuantity(tracker);
                        if (remainingQty != 0)
                        {
                            var originalTag = tracker.PrimaryTicket?.Tag?.Replace(" [SmartCombo]", "") ?? "";
                            _algorithm.ComboMarketOrder(tracker.Legs, remainingQty,
                                tag: originalTag + " [SmartCombo-Market]", asynchronous: true);
                            _context.Logger.Info($"SmartCombo: Placed market order fallback for combo {tracker.PrimaryOrderId} " +
                                                          $"with {tracker.Legs.Count} legs, qty={remainingQty}");
                        }
                    }
                    CleanupComboOrder(tracker);
                    return;
                }
                var currentComboQuote = ComboQuote.FromSecurities(tracker.Legs, _algorithm.Securities);
                if (currentComboQuote == null)
                {
                    _context.Logger.Warning($"SmartCombo: No quotes available for combo {tracker.PrimaryOrderId}, stopping updates");
                    CleanupComboOrder(tracker);
                    return;
                }
                var nextNetPrice = _pricingEngine.CalculateNextComboPrice(
                    tracker.CurrentNetPrice, currentComboQuote, tracker.ComboDirection, tracker.AttemptNumber + 1);
                if (nextNetPrice.HasValue)
                {
                    var updateSuccess = true;
                    foreach (var ticket in tracker.ComboTickets)
                    {
                        if (ticket.Status == OrderStatus.Submitted || ticket.Status == OrderStatus.PartiallyFilled)
                        {
                            var result = ticket.Update(new UpdateOrderFields { LimitPrice = nextNetPrice.Value });
                            if (!result.IsSuccess)
                            {
                                _context.Logger.Warning($"SmartCombo: Failed to update combo ticket {ticket.OrderId}: {result.ErrorMessage}");
                                updateSuccess = false;
                            }
                        }
                    }
                    if (updateSuccess)
                    {
                        var isDebit = tracker.ComboDirection == CoreAlgo.Architecture.Core.Execution.OrderDirection.Buy;
                        var roundedNetPrice = PriceRounding.RoundComboNetPrice(_algorithm.Securities, tracker.Legs, nextNetPrice.Value, isDebit);
                        if (Math.Abs(roundedNetPrice - nextNetPrice.Value) > 0m)
                        {
                            foreach (var ticket in tracker.ComboTickets)
                            {
                                if (ticket.Status == OrderStatus.Submitted || ticket.Status == OrderStatus.PartiallyFilled)
                                {
                                    ticket.Update(new UpdateOrderFields { LimitPrice = roundedNetPrice });
                                }
                            }
                        }
                        tracker.UpdateNetPrice(roundedNetPrice, currentComboQuote);
                        _context.Logger.Debug($"SmartCombo: Updated combo {tracker.PrimaryOrderId} " +
                                                       $"to net price ${nextNetPrice.Value:F2} (attempt {tracker.AttemptNumber}) " +
                                                       $"NetMid: ${currentComboQuote.NetMid:F2}");
                        if (tracker.AttemptNumber < maxAttempts)
                        {
                            ScheduleNextComboPricingUpdate(tracker);
                        }
                        else
                        {
                            _context.Logger.Debug($"SmartCombo: Reached max attempts for combo {tracker.PrimaryOrderId}");
                        }
                    }
                    else
                    {
                        _context.Logger.Warning($"SmartCombo: Failed to update combo order, stopping progressive pricing");
                        CleanupComboOrder(tracker);
                    }
                }
                else
                {
                    _context.Logger.Debug($"SmartCombo: No more price improvements for combo {tracker.PrimaryOrderId}");
                    CleanupComboOrder(tracker);
                }
            }
            catch (Exception ex)
            {
                _context.Logger.Error($"SmartCombo update error for combo {tracker.PrimaryOrderId}: {ex.Message}");
                CleanupComboOrder(tracker);
            }
        }
        private void CleanupComboOrder(ComboOrderTracker tracker)
        {
            _activeComboOrders.Remove(tracker.PrimaryOrderId);
            if (tracker.ScheduledEvent != null)
            {
                _algorithm.Schedule.Remove(tracker.ScheduledEvent);
                _scheduledEvents.Remove(tracker.ScheduledEvent);
            }
        }
        private void CancelComboOrder(ComboOrderTracker tracker, string reason)
        {
            foreach (var ticket in tracker.ComboTickets)
            {
                if (ticket.Status == OrderStatus.Submitted || ticket.Status == OrderStatus.PartiallyFilled)
                {
                    ticket.Cancel(reason);
                }
            }
        }
        private int CalculateComboRemainingQuantity(ComboOrderTracker tracker)
        {
            var primaryTicket = tracker.PrimaryTicket;
            if (primaryTicket == null)
                return 0;
            var originalQty = (int)primaryTicket.Quantity;
            var filledQty = (int)primaryTicket.QuantityFilled;
            return originalQty - filledQty;
        }
        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.Collections.Generic;
    using System.Linq;
    using System.Text;
    using QuantConnect;
    using QuantConnect.Data;
    using QuantConnect.Orders;
    using QuantConnect.Securities;
    using CoreAlgo.Architecture.Core.Implementations;
namespace CoreAlgo.Architecture.Core.Services
{
    public static class TradeHighlightFormatter
    {
        public static string BuildEntry(
            string symbol,
            DateTime time,
            decimal entryPrice,
            int quantity,
            decimal? stopLoss = null,
            decimal? takeProfit = null,
            int? orderId = null)
        {
            var parts = new List<string>
            {
                $"==> ENTRY {symbol} {time:HH:mm} @{entryPrice:F2} x{quantity}"
            };
            if (stopLoss.HasValue)
                parts.Add($"SL {stopLoss.Value:F2}");
            if (takeProfit.HasValue)
                parts.Add($"TP {takeProfit.Value:F2}");
            if (orderId.HasValue)
                parts.Add($"O:{orderId.Value}");
            return string.Join(" | ", parts);
        }
        public static string BuildExit(
            string symbol,
            DateTime time,
            decimal exitPrice,
            decimal pnl,
            ExitType exitType,
            int? orderId = null)
        {
            var prefix = exitType switch
            {
                ExitType.TakeProfit => "<== TP",
                ExitType.StopLoss => "!! SL",
                ExitType.TrailingStop => "~~> TS",
                ExitType.Cancel => "X CANCEL",
                _ => "<==" // fallback
            };
            var pnlSign = pnl >= 0 ? "+" : "";
            var parts = new List<string>
            {
                $"{prefix} {symbol} {time:HH:mm} @{exitPrice:F2}",
                $"PnL {pnlSign}{pnl:F2}"
            };
            if (orderId.HasValue)
                parts.Add($"O:{orderId.Value}");
            return string.Join(" | ", parts);
        }
        public static string FormatMetricsSuffix(IDictionary<string, string> metrics)
        {
            if (metrics == null || metrics.Count == 0)
                return string.Empty;
            var sorted = metrics.OrderBy(kvp => kvp.Key).ToList();
            var pairs = sorted.Select(kvp => $"{kvp.Key}={kvp.Value}");
            var content = string.Join(", ", pairs);
            if (content.Length > 120)
                content = content.Substring(0, 117) + "...";
            return $"[{content}]";
        }
        public static string FormatTradeTracking(
            int entryId,
            DateTime time,
            decimal currentPrice,
            TradeRecord trade,
            decimal highestPriceSinceEntry,
            bool trailingArmed,
            decimal currentStopPrice,
            OrderTicket slTicket = null,
            bool isLongPosition = true,
            bool includeQuotes = false,
            Security security = null)
        {
            var parts = new List<string>
            {
                $"[TT] E{entryId} @{time:HH:mm:ss}",
                $"Price:{currentPrice:F2}",
                $"Entry:{trade.OpenPrice:F2}@{trade.OpenTime:HH:mm:ss}",
                $"High:{highestPriceSinceEntry:F2}"
            };
            var unrealizedPnL = (currentPrice - trade.OpenPrice) * trade.Quantity;
            var pnlSign = unrealizedPnL >= 0 ? "+" : "";
            parts.Add($"UnrealPnL:{pnlSign}{unrealizedPnL:F2}");
            parts.Add($"Trail:{(trailingArmed ? "ARMED" : "DISARMED")}");
            if (currentStopPrice > 0)
            {
                parts.Add($"Stop:{currentStopPrice:F2}");
            }
            if (slTicket != null)
            {
                var stopPrice = slTicket.Get(OrderField.StopPrice);
                var limitPrice = slTicket.Get(OrderField.LimitPrice);
                var ticketParts = new List<string> { $"SL:O{slTicket.OrderId}" };
                if (stopPrice != 0)
                {
                    ticketParts.Add($"Stop:{stopPrice:F2}");
                    bool stopTriggered = isLongPosition
                        ? currentPrice <= stopPrice
                        : currentPrice >= stopPrice;
                    if (stopTriggered && slTicket.Status == OrderStatus.Submitted)
                    {
                        ticketParts.Add("[TRIGGERED]");
                    }
                }
                if (limitPrice != 0)
                {
                    ticketParts.Add($"Limit:{limitPrice:F2}");
                    if (stopPrice != 0)
                    {
                        var distanceToLimit = Math.Abs(limitPrice - currentPrice);
                        ticketParts.Add($"Dist:{distanceToLimit:F4}");
                    }
                }
                ticketParts.Add($"Status:{slTicket.Status}");
                parts.Add(string.Join(" ", ticketParts));
            }
            if (includeQuotes && security != null)
            {
                var bid = security.BidPrice;
                var ask = security.AskPrice;
                if (bid > 0 && ask > 0)
                {
                    var spread = ask - bid;
                    parts.Add($"Bid:{bid:F2} Ask:{ask:F2} Spread:{spread:F4}");
                }
            }
            return string.Join(" | ", parts);
        }
        public static string FormatEntryFill(
            int entryId,
            DateTime time,
            decimal fillPrice,
            int quantity,
            decimal stopDist,
            decimal tpDist,
            decimal atrAtEntry,
            decimal adaptFactor,
            decimal slPrice,
            decimal slLimitPrice,
            decimal stopLimitBuffer,
            decimal tick,
            int openTradesBefore,
            bool includeQuotes = false,
            Security security = null)
        {
            var parts = new List<string>
            {
                $"[TT ENTRY FILL] E{entryId} @{time:HH:mm:ss}",
                $"Fill:{fillPrice:F2} Qty:{quantity}",
                $"StopDist:{stopDist:F4} TpDist:{tpDist:F4} ATR:{atrAtEntry:F4} Adapt:{adaptFactor:F4}",
                $"SL:{slPrice:F2} Limit:{slLimitPrice:F2} Buffer:{stopLimitBuffer:F4} Tick:{tick:F4}",
                $"OpenTrades:{openTradesBefore}"
            };
            if (includeQuotes && security != null)
            {
                var bid = security.BidPrice;
                var ask = security.AskPrice;
                if (bid > 0 && ask > 0)
                {
                    parts.Add($"Bid:{bid:F2} Ask:{ask:F2}");
                }
            }
            return string.Join(" | ", parts);
        }
        public static string FormatExitFill(
            int entryId,
            DateTime time,
            string exitReason,
            decimal entryPrice,
            DateTime entryTime,
            decimal exitPrice,
            decimal pnl,
            bool trailingArmed,
            decimal highestPriceSinceEntry,
            decimal currentStopPrice,
            int openTradesBefore,
            OrderTicket slTicket = null,
            bool isLongPosition = true,
            bool includeQuotes = false,
            Security security = null)
        {
            var pnlSign = pnl >= 0 ? "+" : "";
            var parts = new List<string>
            {
                $"[TT EXIT FILL] E{entryId} @{time:HH:mm:ss} Reason:{exitReason}",
                $"Entry:{entryPrice:F2}@{entryTime:HH:mm:ss} Exit:{exitPrice:F2} PnL:{pnlSign}{pnl:F2}",
                $"Trail:{(trailingArmed ? "ARMED" : "DISARMED")} High:{highestPriceSinceEntry:F2} Stop:{currentStopPrice:F2}",
                $"OpenTrades:{openTradesBefore}→{openTradesBefore - 1}"
            };
            if (slTicket != null)
            {
                var stopPrice = slTicket.Get(OrderField.StopPrice);
                var limitPrice = slTicket.Get(OrderField.LimitPrice);
                var ticketParts = new List<string> { $"SL:O{slTicket.OrderId}" };
                if (stopPrice != 0) ticketParts.Add($"Stop:{stopPrice:F2}");
                if (limitPrice != 0) ticketParts.Add($"Limit:{limitPrice:F2}");
                ticketParts.Add($"Status:{slTicket.Status}");
                parts.Add(string.Join(" ", ticketParts));
            }
            if (includeQuotes && security != null)
            {
                var bid = security.BidPrice;
                var ask = security.AskPrice;
                if (bid > 0 && ask > 0)
                {
                    parts.Add($"Bid:{bid:F2} Ask:{ask:F2}");
                }
            }
            return string.Join(" | ", parts);
        }
        public static string FormatEodFlattenTrade(
            int entryId,
            DateTime time,
            TradeRecord trade,
            decimal currentPrice,
            decimal unrealizedPnL,
            bool trailingArmed,
            decimal highestPriceSinceEntry,
            decimal currentStopPrice,
            OrderTicket slTicket = null,
            bool isLongPosition = true,
            bool includeQuotes = false,
            Security security = null)
        {
            var pnlSign = unrealizedPnL >= 0 ? "+" : "";
            var parts = new List<string>
            {
                $"[TT EOD PRE-FLATTEN] E{entryId} @{time:HH:mm:ss}",
                $"Entry:{trade.OpenPrice:F2}@{trade.OpenTime:HH:mm:ss} Current:{currentPrice:F2} UnrealPnL:{pnlSign}{unrealizedPnL:F2}",
                $"Trail:{(trailingArmed ? "ARMED" : "DISARMED")} High:{highestPriceSinceEntry:F2} Stop:{currentStopPrice:F2}"
            };
            if (slTicket != null)
            {
                var stopPrice = slTicket.Get(OrderField.StopPrice);
                var limitPrice = slTicket.Get(OrderField.LimitPrice);
                var ticketParts = new List<string> { $"SL:O{slTicket.OrderId}" };
                if (stopPrice != 0)
                {
                    ticketParts.Add($"Stop:{stopPrice:F2}");
                    bool stopTriggered = isLongPosition
                        ? currentPrice <= stopPrice
                        : currentPrice >= stopPrice;
                    if (stopTriggered)
                    {
                        ticketParts.Add("[TRIGGERED]");
                    }
                }
                if (limitPrice != 0) ticketParts.Add($"Limit:{limitPrice:F2}");
                ticketParts.Add($"Status:{slTicket.Status}");
                parts.Add(string.Join(" ", ticketParts));
            }
            if (includeQuotes && security != null)
            {
                var bid = security.BidPrice;
                var ask = security.AskPrice;
                if (bid > 0 && ask > 0)
                {
                    parts.Add($"Bid:{bid:F2} Ask:{ask:F2}");
                }
            }
            return string.Join(" | ", parts);
        }
        public static string FormatSlCreated(
            int entryId,
            int slOrderId,
            OrderStatus status,
            decimal stopPrice,
            decimal limitPrice,
            decimal fillPrice,
            bool isLongPosition)
        {
            var parts = new List<string>
            {
                $"[TT SL CREATED] E{entryId}",
                $"SL OrderId:{slOrderId} Status:{status}",
                $"Stop:{stopPrice:F2} Limit:{limitPrice:F2}",
                $"Fill:{fillPrice:F2}"
            };
            return string.Join(" | ", parts);
        }
    }
    public enum ExitType
    {
        TakeProfit,
        StopLoss,
        TrailingStop,
        Cancel
    }
}
using System;
using System.Collections.Generic;
using System.Linq;
using System.Text.Json;
using System.Text.Json.Serialization;
using CoreAlgo.Architecture.Core.Interfaces;
using CoreAlgo.Architecture.Core.Implementations;
namespace CoreAlgo.Architecture.Core.Services
{
    public class TradePersistenceService
    {
        private readonly IAlgorithmContext _context;
        private const string CurrentPositionsKey = "positions/current.json";
        private const string SnapshotKeyPrefix = "positions/snapshots/";
        private DateTime _lastSaveDate = DateTime.MinValue;
        private static readonly JsonSerializerOptions SerializerOptions = new JsonSerializerOptions
        {
            WriteIndented = true,
            DefaultIgnoreCondition = JsonIgnoreCondition.Never,
            PropertyNamingPolicy = JsonNamingPolicy.CamelCase
        };
        public TradePersistenceService(IAlgorithmContext context)
        {
            _context = context ?? throw new ArgumentNullException(nameof(context));
        }
        public void SaveTrades(TradeTracker tracker)
        {
            if (tracker == null) return;
            if (!_context.Algorithm.LiveMode)
            {
                var today = _context.Algorithm.Time.Date;
                if (today == _lastSaveDate)
                    return;
                _lastSaveDate = today;
            }
            try
            {
                var openAndWorking = tracker.WorkingTrades.Concat(tracker.OpenTrades).ToList();
                var book = new TradeBook
                {
                    Version = "1.0",
                    AsOfUtc = _context.Algorithm.UtcTime,
                    AllTrades = openAndWorking,
                    WorkingTrades = tracker.WorkingTrades.Select(t => t.OrderId).ToList(),
                    OpenTrades = tracker.OpenTrades.Select(t => t.OrderId).ToList(),
                    ClosedTrades = new List<string>() // Don't persist closed trades
                };
                var json = JsonSerializer.Serialize(book, SerializerOptions);
                var bytes = System.Text.Encoding.UTF8.GetBytes(json);
                _context.Algorithm.ObjectStore.SaveBytes(CurrentPositionsKey, bytes);
                _context.Logger.Trace($"TradePersistence: Saved {openAndWorking.Count} open/working trades to ObjectStore (excluded {tracker.ClosedTrades.Count} closed)");
            }
            catch (Exception ex)
            {
                _context.Logger.Error($"TradePersistence: Error saving trades: {ex.Message}");
            }
        }
        public void ClearPositionsIfBacktest()
        {
            if (_context.Algorithm.LiveMode)
            {
                return;
            }
            if (_context.Algorithm.ObjectStore.ContainsKey(CurrentPositionsKey))
            {
                _context.Algorithm.ObjectStore.Delete(CurrentPositionsKey);
                _context.Logger.Info("TradePersistence: Cleared current positions for backtest");
            }
        }
        public void ClearSnapshotsIfBacktest()
        {
            if (_context.Algorithm.LiveMode)
            {
                return;
            }
            try
            {
                var deletedCount = 0;
                var keysToDelete = new List<string>();
                foreach (var key in _context.Algorithm.ObjectStore.Keys)
                {
                    if (key.StartsWith(SnapshotKeyPrefix))
                    {
                        keysToDelete.Add(key);
                    }
                }
                foreach (var key in keysToDelete)
                {
                    _context.Algorithm.ObjectStore.Delete(key);
                    deletedCount++;
                }
                if (deletedCount > 0)
                {
                    _context.Logger.Info($"TradePersistence: Cleared {deletedCount} daily snapshots for backtest");
                }
            }
            catch (Exception ex)
            {
                _context.Logger.Error($"TradePersistence: Error clearing snapshots: {ex.Message}");
            }
        }
        public TradeTracker LoadTrades()
        {
            try
            {
                if (!_context.Algorithm.ObjectStore.ContainsKey(CurrentPositionsKey))
                {
                    _context.Logger.Info("TradePersistence: No saved trades found, starting fresh");
                    return null;
                }
                var bytes = _context.Algorithm.ObjectStore.ReadBytes(CurrentPositionsKey);
                var json = System.Text.Encoding.UTF8.GetString(bytes);
                var book = JsonSerializer.Deserialize<TradeBook>(json, SerializerOptions);
                if (book == null || book.AllTrades == null)
                {
                    _context.Logger.Warning("TradePersistence: Invalid saved data, starting fresh");
                    return null;
                }
                var tracker = new TradeTracker
                {
                    AllTrades = book.AllTrades
                };
                foreach (var trade in book.AllTrades)
                {
                    if (book.WorkingTrades.Contains(trade.OrderId))
                    {
                        tracker.WorkingTrades.Add(trade);
                    }
                    else if (book.OpenTrades.Contains(trade.OrderId))
                    {
                        tracker.OpenTrades.Add(trade);
                    }
                    else if (book.ClosedTrades.Contains(trade.OrderId))
                    {
                        tracker.ClosedTrades.Add(trade);
                    }
                }
                _context.Logger.Info($"TradePersistence: Loaded {tracker.AllTrades.Count} trades " +
                                               $"(Working: {tracker.WorkingTrades.Count}, Open: {tracker.OpenTrades.Count}, " +
                                               $"Closed: {tracker.ClosedTrades.Count})");
                return tracker;
            }
            catch (Exception ex)
            {
                _context.Logger.Error($"TradePersistence: Error loading trades: {ex.Message}");
                return null;
            }
        }
        public void SaveDailySnapshot(TradeTracker tracker)
        {
            if (tracker == null) return;
            if (!_context.Algorithm.LiveMode) return;
            try
            {
                var date = _context.Algorithm.Time.ToString("yyyy-MM-dd");
                var snapshotKey = $"{SnapshotKeyPrefix}{date}.json";
                var openAndWorking = tracker.WorkingTrades.Concat(tracker.OpenTrades).ToList();
                var book = new TradeBook
                {
                    Version = "1.0",
                    AsOfUtc = _context.Algorithm.UtcTime,
                    AllTrades = openAndWorking,
                    WorkingTrades = tracker.WorkingTrades.Select(t => t.OrderId).ToList(),
                    OpenTrades = tracker.OpenTrades.Select(t => t.OrderId).ToList(),
                    ClosedTrades = new List<string>() // Don't persist closed trades
                };
                var json = JsonSerializer.Serialize(book, SerializerOptions);
                var bytes = System.Text.Encoding.UTF8.GetBytes(json);
                _context.Algorithm.ObjectStore.SaveBytes(snapshotKey, bytes);
                _context.Logger.Trace($"TradePersistence: Saved daily snapshot for {date} ({openAndWorking.Count} open/working trades)");
            }
            catch (Exception ex)
            {
                _context.Logger.Error($"TradePersistence: Error saving daily snapshot: {ex.Message}");
            }
        }
        public TradeSummary GetTradeSummary(TradeTracker tracker)
        {
            if (tracker == null)
            {
                return new TradeSummary();
            }
            return new TradeSummary
            {
                TotalTrades = tracker.AllTrades.Count,
                WorkingCount = tracker.WorkingTrades.Count,
                OpenCount = tracker.OpenTrades.Count,
                ClosedCount = tracker.ClosedTrades.Count,
                PartialFillCount = tracker.AllTrades.Count(t => t.Status == "PartialFill"),
                CancelledCount = tracker.AllTrades.Count(t => t.Status == "Cancelled"),
                TotalPnL = tracker.ClosedTrades.Sum(t => t.PnL),
                AsOfUtc = DateTime.UtcNow
            };
        }
    }
    public class TradeBook
    {
        public string Version { get; set; }
        public DateTime AsOfUtc { get; set; }
        public List<TradeRecord> AllTrades { get; set; } = new List<TradeRecord>();
        public List<string> WorkingTrades { get; set; } = new List<string>();
        public List<string> OpenTrades { get; set; } = new List<string>();
        public List<string> ClosedTrades { get; set; } = new List<string>();
    }
    public class TradeSummary
    {
        public int TotalTrades { get; set; }
        public int WorkingCount { get; set; }
        public int OpenCount { get; set; }
        public int ClosedCount { get; set; }
        public int PartialFillCount { get; set; }
        public int CancelledCount { get; set; }
        public decimal TotalPnL { get; set; }
        public DateTime AsOfUtc { get; set; }
    }
}
using System;
using System.Collections.Generic;
using System.Linq;
using QuantConnect;
using QuantConnect.Data;
using QuantConnect.Data.Consolidators;
using QuantConnect.Data.Market;
using QuantConnect.Data.UniverseSelection;
using QuantConnect.Securities;
using QuantConnect.Data.Fundamental;
using CoreAlgo.Architecture.Core.Implementations;
using CoreAlgo.Architecture.Core.Models;
using CoreAlgo.Architecture.Core.Helpers;
using CoreAlgo.Architecture.QC.Helpers;
namespace CoreAlgo.Architecture.Core.Templates
{
    public class THTTemplate : SimpleBaseStrategy
    {
        private THTConfig _config;
        private Symbol _spySymbol;
        private readonly Dictionary<Symbol, THTSymbolState> _symbolStates = new Dictionary<Symbol, THTSymbolState>();
        private readonly HashSet<Symbol> _removedWhileInvested = new HashSet<Symbol>();
        private bool _needsRebalance;
        private bool _firstTradeLogged;
        private bool _isDebugMode;
        public override string Name => "THT";
        public override string Description =>
            "Trend-High-Timing strategy using FVB + BX-Trender indicators on S&P 500 universe with weekly rebalancing";
        public override void OnInitialize()
        {
            Configure<THTConfig>();
            _config = (THTConfig)Config;
            SmartLog($"THT init: FVB={_config.FVB33Period}/{_config.FVB20Period} BX=EMA({_config.BXEmaShort},{_config.BXEmaLong})RSI({_config.BXRsiPeriod}) " +
                $"Warmup:{_config.WarmupDays} SLday>{_config.StopLossDayOfMonthThreshold} MaxWt:{_config.MaxPositionWeight:P0} " +
                $"Rebal:{_config.EnableWeeklyRebalance} PivotTP:{_config.PivotTPEnabled} StallTP:{_config.WeeklyStallTPEnabled} " +
                $"SL:{_config.StopLossEnabled} MinHold:{_config.MinHoldBars} Debug:{(_config.DebugTickers ?? "(none)")}");
            Algorithm.UniverseSettings.Resolution = Resolution.Daily;
            Algorithm.UniverseSettings.DataNormalizationMode = DataNormalizationMode.SplitAdjusted;
            Algorithm.UniverseSettings.Asynchronous = true;
            var spySecurity = AssetManager.AddAsset(this, _config.Benchmark, Resolution.Daily);
            _spySymbol = spySecurity.Symbol;
            Algorithm.SetBenchmark(_config.Benchmark);
            if (!string.IsNullOrEmpty(_config.DebugTickers))
            {
                var tickers = _config.DebugTickers.Split(',')
                    .Select(t => t.Trim())
                    .Where(t => !string.IsNullOrEmpty(t))
                    .ToArray();
                foreach (var ticker in tickers)
                {
                    AssetManager.AddAsset(this, ticker, Resolution.Daily);
                    Algorithm.Log($"[THT-DEBUG] Added debug ticker: {ticker}");
                }
                _isDebugMode = true;
                Algorithm.Log($"[THT-DEBUG] Single-ticker debug mode with {tickers.Length} tickers");
            }
            else
            {
                Algorithm.AddUniverse(fundamental =>
                {
                    return fundamental
                        .Where(f => f.HasFundamentalData
                                    && f.Price > (decimal)_config.MinPrice
                                    && f.DollarVolume > 1_000_000)
                        .OrderByDescending(f => f.MarketCap)
                        .Take(_config.UniverseSize)
                        .Select(f => f.Symbol);
                });
            }
            Algorithm.Schedule.On(
                Algorithm.DateRules.WeekStart(),
                Algorithm.TimeRules.AfterMarketOpen(_spySymbol, _config.RebalanceAfterOpenMinutes),
                ScheduledRebalance
            );
            Algorithm.Schedule.On(
                Algorithm.DateRules.EveryDay(_spySymbol),
                Algorithm.TimeRules.AfterMarketClose(_spySymbol, 1),
                DailySignalCheck
            );
            Algorithm.Schedule.On(
                Algorithm.DateRules.EveryDay(_spySymbol),
                Algorithm.TimeRules.BeforeMarketClose(_spySymbol, 5),
                () =>
                {
                    if (Algorithm.Time.Date >= Algorithm.EndDate.Date)
                        FlattenAllPositions("EndOfBacktest");
                }
            );
            SmartLog("THT strategy initialized successfully");
        }
        public override void OnSecuritiesChanged(SecurityChanges changes)
        {
            var newStates = new Dictionary<Symbol, THTSymbolState>();
            foreach (var security in changes.AddedSecurities)
            {
                var symbol = security.Symbol;
                if (symbol.SecurityType != SecurityType.Equity || symbol == _spySymbol)
                    continue;
                if (_symbolStates.ContainsKey(symbol))
                    continue;
                var state = new THTSymbolState
                {
                    Symbol = symbol,
                    FVB33 = new FairValueBand(_config.FVB33Period),
                    FVB20 = new FairValueBand(_config.FVB20Period, computePivotBands: true, pivotLookback: _config.FVBPivotLookback),
                    MonthlyBX = new BXTrender(_config.BXEmaShort, _config.BXEmaLong, _config.BXRsiPeriod),
                    WeeklyBX = new BXTrender(_config.BXEmaShort, _config.BXEmaLong, _config.BXRsiPeriod)
                };
                var weeklyConsolidator = new TradeBarConsolidator(CalendarType.Weekly);
                weeklyConsolidator.DataConsolidated += (s, bar) =>
                {
                    state.FVB33.Update(bar.EndTime, bar.Open, bar.High, bar.Low, bar.Close);
                    state.FVB20.Update(bar.EndTime, bar.Open, bar.High, bar.Low, bar.Close);
                    state.WeeklyBX.Update(bar.EndTime, bar.Close);
                    state.WeeklyHigh = bar.High;
                    state.WeeklyLow = bar.Low;
                    state.WeeklyClose = bar.Close;
                    state.HasNewWeeklyBar = true;
                };
                Algorithm.SubscriptionManager.AddConsolidator(symbol, weeklyConsolidator);
                state.WeeklyConsolidator = weeklyConsolidator;
                var monthlyConsolidator = new TradeBarConsolidator(CalendarType.Monthly);
                monthlyConsolidator.DataConsolidated += (s, bar) =>
                {
                    state.MonthlyBX.Update(bar.EndTime, bar.Close);
                };
                Algorithm.SubscriptionManager.AddConsolidator(symbol, monthlyConsolidator);
                state.MonthlyConsolidator = monthlyConsolidator;
                newStates[symbol] = state;
                _symbolStates[symbol] = state;
            }
            if (newStates.Count > 0)
            {
                BatchWarmUpIndicators(newStates);
            }
            foreach (var security in changes.RemovedSecurities)
            {
                var symbol = security.Symbol;
                if (!_symbolStates.ContainsKey(symbol))
                    continue;
                if (Portfolio[symbol].Invested)
                {
                    _removedWhileInvested.Add(symbol);
                    continue;
                }
                RemoveSymbolState(symbol);
            }
        }
        private void BatchWarmUpIndicators(Dictionary<Symbol, THTSymbolState> newStates)
        {
            try
            {
                var today = Algorithm.Time.Date;
                var symbols = newStates.Keys.ToList();
                var allHistory = Algorithm.History<TradeBar>(symbols, _config.WarmupDays, Resolution.Daily)
                    .SelectMany(dict => dict.Values)
                    .Where(b => b.EndTime.Date < today)
                    .GroupBy(b => b.Symbol)
                    .ToDictionary(g => g.Key, g => g.OrderBy(b => b.EndTime).ToList());
                var currentWeekStart = GetWeekStart(Algorithm.Time);
                var currentMonthStart = new DateTime(Algorithm.Time.Year, Algorithm.Time.Month, 1);
                foreach (var kvp in newStates)
                {
                    var symbol = kvp.Key;
                    var state = kvp.Value;
                    if (!allHistory.TryGetValue(symbol, out var history) || history.Count == 0)
                    {
                        SmartLog($"[THT] No history for {symbol}, indicators will warm up organically");
                        continue;
                    }
                    DateTime? weekStart = null;
                    TradeBar weekBar = null;
                    DateTime? monthStart = null;
                    TradeBar monthBar = null;
                    foreach (var bar in history)
                    {
                        var weekOfBar = GetWeekStart(bar.EndTime);
                        if (weekStart == null || weekOfBar != weekStart)
                        {
                            if (weekBar != null)
                            {
                                state.FVB33.Update(weekBar.EndTime, weekBar.Open, weekBar.High, weekBar.Low, weekBar.Close);
                                state.FVB20.Update(weekBar.EndTime, weekBar.Open, weekBar.High, weekBar.Low, weekBar.Close);
                                state.WeeklyBX.Update(weekBar.EndTime, weekBar.Close);
                            }
                            weekStart = weekOfBar;
                            weekBar = new TradeBar
                            {
                                Time = bar.Time, Open = bar.Open, High = bar.High,
                                Low = bar.Low, Close = bar.Close, EndTime = bar.EndTime
                            };
                        }
                        else if (weekBar != null)
                        {
                            if (bar.High > weekBar.High) weekBar.High = bar.High;
                            if (bar.Low < weekBar.Low) weekBar.Low = bar.Low;
                            weekBar.Close = bar.Close;
                            weekBar.EndTime = bar.EndTime;
                        }
                        var monthKey = new DateTime(bar.EndTime.Year, bar.EndTime.Month, 1);
                        if (monthStart == null || monthKey != monthStart)
                        {
                            if (monthBar != null)
                                state.MonthlyBX.Update(monthBar.EndTime, monthBar.Close);
                            monthStart = monthKey;
                            monthBar = new TradeBar
                            {
                                Time = bar.Time, Open = bar.Open, High = bar.High,
                                Low = bar.Low, Close = bar.Close, EndTime = bar.EndTime
                            };
                        }
                        else if (monthBar != null)
                        {
                            if (bar.High > monthBar.High) monthBar.High = bar.High;
                            if (bar.Low < monthBar.Low) monthBar.Low = bar.Low;
                            monthBar.Close = bar.Close;
                            monthBar.EndTime = bar.EndTime;
                        }
                    }
                    if (weekBar != null && weekStart != currentWeekStart)
                    {
                        state.FVB33.Update(weekBar.EndTime, weekBar.Open, weekBar.High, weekBar.Low, weekBar.Close);
                        state.FVB20.Update(weekBar.EndTime, weekBar.Open, weekBar.High, weekBar.Low, weekBar.Close);
                        state.WeeklyBX.Update(weekBar.EndTime, weekBar.Close);
                    }
                    if (monthBar != null && monthStart != currentMonthStart)
                        state.MonthlyBX.Update(monthBar.EndTime, monthBar.Close);
                }
                SmartLog($"[THT] Batch warmup complete: {newStates.Count} symbols, {allHistory.Values.Sum(h => h.Count)} bars processed");
            }
            catch (Exception ex)
            {
                SmartWarn($"[THT] Batch warmup failed: {ex.Message}");
            }
        }
        private static DateTime GetWeekStart(DateTime date)
        {
            var diff = (7 + (date.DayOfWeek - DayOfWeek.Monday)) % 7;
            return date.Date.AddDays(-diff);
        }
        private int _diagnosticDaysLogged;
        private void DailySignalCheck()
        {
            _needsRebalance = false;
            if (Algorithm.Time.Date >= Algorithm.EndDate.Date)
            {
                FlattenAllPositions("EndOfBacktest");
                return;
            }
            if (!_isDebugMode && _diagnosticDaysLogged < 5)
            {
                var total = _symbolStates.Count;
                var fvb33Ready = _symbolStates.Values.Count(s => s.FVB33.IsReady);
                var fvb20Ready = _symbolStates.Values.Count(s => s.FVB20.IsReady);
                var monthlyReady = _symbolStates.Values.Count(s => s.MonthlyBX.IsReady);
                var weeklyReady = _symbolStates.Values.Count(s => s.WeeklyBX.IsReady);
                SmartLog($"[THT] Readiness ({Algorithm.Time:yyyy-MM-dd}): {total} symbols — FVB33:{fvb33Ready} FVB20:{fvb20Ready} MonthlyBX:{monthlyReady} WeeklyBX:{weeklyReady}");
                _diagnosticDaysLogged++;
            }
            if (!_isDebugMode && Algorithm.Time.DayOfWeek == DayOfWeek.Monday)
            {
                var active = _symbolStates.Values.Count(s => s.Position == 1);
                var invested = Portfolio.Values.Count(h => h.Invested
                    && h.Symbol.SecurityType == SecurityType.Equity && h.Symbol != _spySymbol);
                var greenCount = _symbolStates.Values.Count(s => s.FVB33.IsReady && s.FVB33.DirSwitch == 1);
                var bullishCount = _symbolStates.Values.Count(s => s.MonthlyBX.IsReady && s.MonthlyBX.IsBullish);
                SmartLog($"[THT] Weekly ({Algorithm.Time:yyyy-MM-dd}): {active} active, {invested} invested, " +
                         $"FVB33green={greenCount}, MonthlyBull={bullishCount}, tracked={_symbolStates.Count}");
            }
            foreach (var kvp in _symbolStates)
            {
                var symbol = kvp.Key;
                var state = kvp.Value;
                if (!state.HasNewWeeklyBar)
                    continue;
                state.HasNewWeeklyBar = false;
                var high = state.WeeklyHigh;
                var low = state.WeeklyLow;
                var close = state.WeeklyClose;
                if (close <= 0) continue;
                var fvb33Green = state.FVB33.IsReady && state.FVB33.DirSwitch == 1;
                var monthlyBullish = state.MonthlyBX.IsReady && state.MonthlyBX.IsBullish;
                var weeklyJustStalled = state.WeeklyBX.IsReady && state.WeeklyBX.JustStalled;
                if (!weeklyJustStalled)
                    state.WeeklyStalledTPConsumed = false;
                var pivotBandTP = state.FVB20.IsReady
                    && state.FVB20.PivotBandUp > 0
                    && high >= state.FVB20.PivotBandUp;
                var inReEntryBandZone = state.FVB20.IsReady
                    && state.FVB20.UpperBand > 0
                    && low <= state.FVB20.UpperBand
                    && high >= state.FVB20.LowerBand;
                var reEntryBandFresh = inReEntryBandZone
                    && (!state.PrevReEntryBandTouch || state.IsNextBarAfterTP);
                var isDarkRedAndPastThreshold = state.MonthlyBX.IsReady
                    && state.MonthlyBX.ColorState == "DarkRed"
                    && Algorithm.Time.Day > _config.StopLossDayOfMonthThreshold;
                if (state.Position == 1)
                {
                    state.BarsHeld++;
                    if (_config.StopLossEnabled == 1
                        && isDarkRedAndPastThreshold && !state.PrevDarkRedAndPastThreshold
                        && !state.StopLossTriggeredThisMonth)
                    {
                        var heldBars = state.BarsHeld;
                        state.Position = 0;
                        state.LastExitWasTakeProfit = false;
                        state.ConditionsReset = false;
                        state.StopLossTriggeredThisMonth = true;
                        state.BarsHeld = 0;
                        state.PendingSignal = "StopLoss";
                        _needsRebalance = true;
                        SmartLog($"[THT] STOP LOSS: {symbol} — Monthly BX DarkRed + day={Algorithm.Time.Day}>{_config.StopLossDayOfMonthThreshold}");
                        if (_isDebugMode) Algorithm.Log($"[THT-DEBUG] SL: {symbol} @ ${close:F2}, held {heldBars}bars, entry=${state.EntryPrice:F2}, return={(close/state.EntryPrice - 1)*100:F1}%");
                    }
                    else if (state.BarsHeld >= _config.MinHoldBars)
                    {
                        var pivotTP = _config.PivotTPEnabled == 1 && pivotBandTP;
                        var stallTP = _config.WeeklyStallTPEnabled == 1
                            && weeklyJustStalled && !state.WeeklyStalledTPConsumed && close > state.EntryPrice;
                        if (pivotTP || stallTP)
                        {
                            var reason = pivotTP ? "PivotBandUp touch" : "Weekly BX stall";
                            var heldBars = state.BarsHeld;
                            SmartLog($"[THT] TAKE PROFIT: {symbol} — {reason} (held {heldBars} bars)");
                            state.Position = 0;
                            state.LastExitWasTakeProfit = true;
                            state.IsNextBarAfterTP = true;
                            state.ConditionsReset = false;
                            state.BarsHeld = 0;
                            state.PendingSignal = "TakeProfit";
                            _needsRebalance = true;
                            if (weeklyJustStalled) state.WeeklyStalledTPConsumed = true;
                            if (_isDebugMode) Algorithm.Log($"[THT-DEBUG] TP: {symbol} @ ${close:F2}, {reason}, held {heldBars}bars, entry=${state.EntryPrice:F2}, return={(close/state.EntryPrice - 1)*100:F1}%");
                        }
                    }
                }
                else // Position == 0
                {
                    if (!fvb33Green || !monthlyBullish)
                        state.ConditionsReset = true;
                    if (state.LastExitWasTakeProfit && reEntryBandFresh
                        && monthlyBullish && fvb33Green)
                    {
                        state.Position = 1;
                        state.EntryPrice = close;
                        state.BarsHeld = 0;
                        state.BarsFlat = 0;
                        state.LastExitWasTakeProfit = false;
                        state.PendingSignal = "ReEntry";
                        _needsRebalance = true;
                        SmartLog($"[THT] RE-ENTRY: {symbol} — FVB20 band touch + bullish" +
                            (state.IsNextBarAfterTP ? " (next bar after TP)" : ""));
                        if (_isDebugMode) Algorithm.Log($"[THT-DEBUG] ReEntry: {symbol} @ ${close:F2}, FVB20 band=[{state.FVB20.LowerBand:F2}-{state.FVB20.UpperBand:F2}]");
                    }
                    else if (fvb33Green && monthlyBullish
                        && state.ConditionsReset && !state.LastExitWasTakeProfit)
                    {
                        state.Position = 1;
                        state.EntryPrice = close;
                        state.BarsHeld = 0;
                        state.BarsFlat = 0;
                        state.ConditionsReset = false;
                        state.LastExitWasTakeProfit = false;
                        state.PendingSignal = "Entry";
                        _needsRebalance = true;
                        SmartLog($"[THT] ENTRY: {symbol} — FVB33 green + Monthly BX bullish (conditions reset)");
                        if (_isDebugMode) Algorithm.Log($"[THT-DEBUG] Entry: {symbol} @ ${close:F2}, FVB33dir={state.FVB33.DirSwitch}, MonthlyBX={state.MonthlyBX.ColorState}, WeeklyBX={state.WeeklyBX.ColorState}");
                    }
                    state.IsNextBarAfterTP = false;
                    state.BarsFlat++;
                    if (state.LastExitWasTakeProfit && state.BarsFlat > 12)
                    {
                        state.LastExitWasTakeProfit = false;
                        state.ConditionsReset = true;
                        if (_isDebugMode) Algorithm.Log($"[THT-DEBUG] {symbol}: LastExitWasTakeProfit decayed after {state.BarsFlat} bars flat");
                    }
                }
                if (_isDebugMode && state.FVB33.IsReady && Algorithm.Time.Day <= 3 && Algorithm.Time.DayOfWeek == DayOfWeek.Monday)
                {
                    Algorithm.Log($"[THT-DEBUG] {Algorithm.Time:yyyy-MM-dd} {symbol}: pos={state.Position} close=${close:F2} " +
                        $"FVB33dir={state.FVB33.DirSwitch} mBX={state.MonthlyBX.ColorState}({(state.MonthlyBX.IsReady?"R":"NR")}) " +
                        $"wBX={state.WeeklyBX.ColorState}({(state.WeeklyBX.IsReady?"R":"NR")}) " +
                        $"FVB20=[{state.FVB20.LowerBand:F2}-{state.FVB20.UpperBand:F2}] pivot={state.FVB20.PivotBandUp:F2} " +
                        $"condReset={state.ConditionsReset} lastTP={state.LastExitWasTakeProfit} barsFlat={state.BarsFlat}");
                }
                state.PrevReEntryBandTouch = inReEntryBandZone;
                state.PrevDarkRedAndPastThreshold = isDarkRedAndPastThreshold;
                var yearMonth = Algorithm.Time.Year * 100 + Algorithm.Time.Month;
                if (yearMonth != state.LastStopLossResetMonth)
                {
                    state.StopLossTriggeredThisMonth = false;
                    state.LastStopLossResetMonth = yearMonth;
                }
            }
            if (_removedWhileInvested.Count > 0)
            {
                var toClean = _removedWhileInvested.Where(s => !Portfolio[s].Invested).ToList();
                foreach (var symbol in toClean)
                {
                    RemoveSymbolState(symbol);
                    _removedWhileInvested.Remove(symbol);
                }
            }
            if (_needsRebalance)
            {
                if (!_firstTradeLogged)
                {
                    var activeCount = _symbolStates.Values.Count(s => s.Position == 1);
                    SmartLog($"[THT] *** FIRST SIGNAL TRIGGERED on {Algorithm.Time:yyyy-MM-dd} — {activeCount} positions activated ***");
                    _firstTradeLogged = true;
                }
                Rebalance("signal");
            }
        }
        private void ScheduledRebalance()
        {
            if (_config.EnableWeeklyRebalance == 0) return;
            Rebalance("weekly");
        }
        private void Rebalance(string trigger)
        {
            var activeSymbols = _symbolStates.Values
                .Where(s => s.Position == 1)
                .Select(s => s.Symbol)
                .ToList();
            if (activeSymbols.Count > _config.MaxPositions)
            {
                var invested = activeSymbols.Where(s => Portfolio[s].Invested).ToList();
                var newEntries = activeSymbols.Where(s => !Portfolio[s].Invested).ToList();
                activeSymbols = invested.Concat(newEntries).Take(_config.MaxPositions).ToList();
                foreach (var st in _symbolStates.Values.Where(st => st.Position == 1
                    && !activeSymbols.Contains(st.Symbol)))
                    st.Position = 0;
                SmartLog($"[THT] MaxPositions cap: trimmed to {_config.MaxPositions} (dropped {invested.Count + newEntries.Count - activeSymbols.Count} excess)");
            }
            foreach (var kvp in _symbolStates)
            {
                var symbol = kvp.Key;
                var state = kvp.Value;
                if (state.Position == 0 && Portfolio[symbol].Invested)
                {
                    var reason = state.PendingSignal ?? "Exit";
                    Liquidate(symbol, $"THT_Close_{reason}");
                    state.PendingSignal = null;
                }
            }
            foreach (var holding in Portfolio.Values)
            {
                if (holding.Invested && holding.Symbol.SecurityType == SecurityType.Equity
                    && holding.Symbol != _spySymbol && !_symbolStates.ContainsKey(holding.Symbol))
                {
                    Liquidate(holding.Symbol, "THT_Close_UniverseRemoved");
                }
            }
            if (activeSymbols.Count == 0)
            {
                SmartLog($"[THT] Rebalance ({trigger}): No active positions");
                return;
            }
            var targetWeight = Math.Min(1.0m / activeSymbols.Count, _config.MaxPositionWeight);
            SmartLog($"[THT] Rebalance ({trigger}): {activeSymbols.Count} active positions @ {targetWeight:P2} each");
            foreach (var symbol in activeSymbols)
            {
                var state = _symbolStates[symbol];
                var isNewEntry = !Portfolio[symbol].Invested;
                if (isNewEntry)
                {
                    var signal = state.PendingSignal ?? "Entry";
                    Algorithm.SetHoldings(symbol, targetWeight, tag: $"THT_Entry_{signal}");
                }
                else if (trigger != "signal")
                {
                    var currentWeight = Portfolio.TotalPortfolioValue > 0
                        ? Portfolio[symbol].HoldingsValue / Portfolio.TotalPortfolioValue : 0m;
                    var drift = targetWeight > 0 ? Math.Abs(currentWeight - targetWeight) / targetWeight : 0m;
                    if (drift > 0.20m)
                    {
                        Algorithm.SetHoldings(symbol, targetWeight, tag: "THT_Resize");
                    }
                }
                state.PendingSignal = null;
            }
        }
        protected override void OnExecute(Slice slice)
        {
        }
        protected override void CheckExitConditions(Slice slice)
        {
        }
        protected override void OnShutdown()
        {
            FlattenAllPositions("EndOfAlgo");
            base.OnShutdown();
        }
        private void FlattenAllPositions(string reason)
        {
            var openCount = 0;
            foreach (var holding in Portfolio.Values)
            {
                if (holding.Invested && holding.Symbol.SecurityType == SecurityType.Equity
                    && holding.Symbol != _spySymbol)
                {
                    Liquidate(holding.Symbol, $"THT_Close_{reason}");
                    openCount++;
                }
            }
            foreach (var state in _symbolStates.Values)
                state.Position = 0;
            if (openCount > 0)
                SmartLog($"[THT] FlattenAllPositions({reason}): Liquidated {openCount} open positions at {Algorithm.Time:yyyy-MM-dd HH:mm}");
        }
        private void RemoveSymbolState(Symbol symbol)
        {
            if (!_symbolStates.ContainsKey(symbol))
                return;
            var state = _symbolStates[symbol];
            if (state.WeeklyConsolidator != null)
                Algorithm.SubscriptionManager.RemoveConsolidator(symbol, state.WeeklyConsolidator);
            if (state.MonthlyConsolidator != null)
                Algorithm.SubscriptionManager.RemoveConsolidator(symbol, state.MonthlyConsolidator);
            _symbolStates.Remove(symbol);
        }
        private class THTSymbolState
        {
            public Symbol Symbol { get; set; }
            public FairValueBand FVB33 { get; set; }
            public FairValueBand FVB20 { get; set; }
            public BXTrender MonthlyBX { get; set; }
            public BXTrender WeeklyBX { get; set; }
            public IDataConsolidator WeeklyConsolidator { get; set; }
            public IDataConsolidator MonthlyConsolidator { get; set; }
            public decimal WeeklyHigh { get; set; }
            public decimal WeeklyLow { get; set; }
            public decimal WeeklyClose { get; set; }
            public bool HasNewWeeklyBar { get; set; }
            public int Position { get; set; }                   // 0=flat, 1=long
            public bool LastExitWasTakeProfit { get; set; }
            public bool ConditionsReset { get; set; } = true;     // Start true so cold-start entries work; cycles off→on for re-entry
            public decimal EntryPrice { get; set; }             // Price at entry for TP guard
            public bool StopLossTriggeredThisMonth { get; set; }
            public int LastStopLossResetMonth { get; set; }
            public string PendingSignal { get; set; }           // Signal that caused last position change (Entry/ReEntry/TakeProfit/StopLoss)
            public bool WeeklyStalledTPConsumed { get; set; }    // Prevents repeated TP from same JustStalled event
            public bool IsNextBarAfterTP { get; set; }           // Relaxes re-entry freshness check on bar after TP
            public int BarsHeld { get; set; }                     // Bars since entry (for MinHoldBars check)
            public int BarsFlat { get; set; }                     // Bars since last exit (for TP flag decay)
            public bool PrevReEntryBandTouch { get; set; }
            public bool PrevDarkRedAndPastThreshold { get; set; } // Combined SL state for transition detection
        }
    }
}
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
{
    public static class AssetManager
    {
        private static readonly HashSet<string> CashIndices = new HashSet<string>
        {
            "SPX", "VIX", "NDX", "RUT", "DJX"
        };
        private static readonly HashSet<string> Futures = new HashSet<string>
        {
            "ES", "NQ", "YM", "RTY", "EMD", "NKD",
            "CL", "NG", "RB", "HO", "BZ",
            "GC", "SI", "HG", "PA", "PL",
            "ZC", "ZS", "ZW", "ZM", "ZL", "KC", "CT", "SB", "CC", "OJ",
            "ZB", "ZN", "ZF", "TU", "UB", "ED", "SR1", "SR3",
            "6E", "6J", "6B", "6S", "6C", "6A", "6N", "6M", "E7", "J7",
            "VX",
            "BTC", "ETH"
        };
        public static Security AddAsset(IAlgorithmContext context, string symbol, Resolution resolution = Resolution.Minute,
            bool useContinuousContract = true, int contractDepthOffset = 0, bool extendedMarketHours = false)
        {
            if (context == null)
                throw new ArgumentNullException(nameof(context));
            if (string.IsNullOrWhiteSpace(symbol))
                throw new ArgumentException("Symbol cannot be null or empty", nameof(symbol));
            var upperSymbol = symbol.ToUpperInvariant();
            try
            {
                Security addedSecurity;
                if (CashIndices.Contains(upperSymbol))
                {
                    context.Logger.Info($"AssetManager: Adding index {upperSymbol} using AddIndex()");
                    addedSecurity = context.Algorithm.AddIndex(upperSymbol, resolution);
                }
                else if (Futures.Contains(upperSymbol))
                {
                    context.Logger.Info($"AssetManager: Adding future {upperSymbol} using AddFuture() with continuous contract: {useContinuousContract}, extendedMarketHours: {extendedMarketHours}");
                    if (useContinuousContract)
                    {
                        addedSecurity = context.Algorithm.AddFuture(upperSymbol, resolution,
                            extendedMarketHours: extendedMarketHours,
                            dataMappingMode: DataMappingMode.OpenInterest,
                            dataNormalizationMode: DataNormalizationMode.BackwardsRatio,
                            contractDepthOffset: contractDepthOffset);
                        context.Logger.Info($"AssetManager: Continuous contract configured - DataMapping: OpenInterest, DataNormalization: BackwardsRatio, Depth: {contractDepthOffset}");
                    }
                    else
                    {
                        addedSecurity = context.Algorithm.AddFuture(upperSymbol, resolution, extendedMarketHours: extendedMarketHours);
                    }
                }
                else
                {
                    context.Logger.Info($"AssetManager: Adding equity {upperSymbol} using AddEquity()");
                    addedSecurity = context.Algorithm.AddEquity(upperSymbol, resolution);
                }
                if (addedSecurity != null)
                {
                    var isInSecurities = context.Algorithm.Securities.ContainsKey(addedSecurity.Symbol);
                    context.Logger.Info($"AssetManager: [SUCCESS] {upperSymbol} successfully added to Securities collection: {isInSecurities}");
                    context.Logger.Info($"AssetManager: Security details - Type: {addedSecurity.Type}, Resolution: {addedSecurity.Subscriptions.GetHighestResolution()}, Exchange: {addedSecurity.Exchange}");
                    var hasPrice = addedSecurity.Price > 0;
                    var priceStatus = hasPrice ? $"${addedSecurity.Price:F2}" : "No price data yet";
                    context.Logger.Info($"AssetManager: Price data status: {priceStatus}");
                    if (!hasPrice)
                    {
                        context.Logger.Info($"AssetManager: [WARNING] No price data yet for {upperSymbol} - this is normal during initialization, data should arrive during backtest");
                    }
                }
                else
                {
                    context.Logger.Error($"AssetManager: [FAILED] Failed to add {upperSymbol} - returned null security");
                }
                return addedSecurity;
            }
            catch (Exception ex)
            {
                context.Logger.Error($"AssetManager: Failed to add asset {upperSymbol}: {ex.Message}");
                throw;
            }
        }
        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)
                {
                    if (symbol == "SPX")
                    {
                        context.Logger.Info($"AssetManager: Adding SPX index options using SPXW with {resolution} resolution");
                        context.Logger.Info($"AssetManager: [UNIVERSE] UniverseSettings.Resolution={context.Algorithm.UniverseSettings.Resolution}, FillForward={context.Algorithm.UniverseSettings.FillForward}, ExtendedMktHours={context.Algorithm.UniverseSettings.ExtendedMarketHours}");
                        var requestedResolution = resolution == Resolution.Daily ? Resolution.Minute : resolution;
                        if (requestedResolution != resolution)
                        {
                            context.Logger.Info($"AssetManager: [RESOLUTION OVERRIDE] Changing from {resolution} to {requestedResolution} for intraday option data");
                        }
                        optionSymbol = context.Algorithm.AddIndexOption(underlying.Symbol, "SPXW", requestedResolution).Symbol;
                        context.Logger.Info($"AssetManager: [RESOLUTION VERIFICATION] Requested: {requestedResolution}");
                    }
                    else
                    {
                        context.Logger.Info($"AssetManager: Adding index options for {symbol} with {resolution} resolution");
                        context.Logger.Info($"AssetManager: [UNIVERSE] UniverseSettings.Resolution={context.Algorithm.UniverseSettings.Resolution}, FillForward={context.Algorithm.UniverseSettings.FillForward}, ExtendedMktHours={context.Algorithm.UniverseSettings.ExtendedMarketHours}");
                        var requestedResolution = resolution == Resolution.Daily ? Resolution.Minute : resolution;
                        if (requestedResolution != resolution)
                        {
                            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);
                        context.Logger.Info($"AssetManager: [RESOLUTION VERIFICATION] Requested: {requestedResolution}");
                    }
                }
                else if (underlying.Type == SecurityType.Future)
                {
                    context.Logger.Info($"AssetManager: Adding future options for {symbol}");
                    context.Algorithm.AddFutureOption(underlying.Symbol);
                    optionSymbol = Symbol.CreateCanonicalOption(underlying.Symbol);
                }
                else
                {
                    context.Logger.Info($"AssetManager: Adding equity options for {symbol}");
                    optionSymbol = context.Algorithm.AddOption(symbol, resolution).Symbol;
                }
                if (optionSymbol != null)
                {
                    context.Logger.Info($"AssetManager: [SUCCESS] Options chain created for {symbol}");
                    context.Logger.Info($"AssetManager: [DEBUG] DETAILED Option Symbol Analysis:");
                    context.Logger.Info($"   optionSymbol: {optionSymbol}");
                    context.Logger.Info($"   optionSymbol.Value: {optionSymbol.Value}");
                    context.Logger.Info($"   optionSymbol.SecurityType: {optionSymbol.SecurityType}");
                    context.Logger.Info($"   optionSymbol.ID: {optionSymbol.ID}");
                    context.Logger.Info($"   optionSymbol.HasCanonical: {optionSymbol.HasCanonical()}");
                    if (optionSymbol.HasCanonical())
                    {
                        context.Logger.Info($"   optionSymbol.Canonical: {optionSymbol.Canonical}");
                    }
                    var isInSecurities = context.Algorithm.Securities.ContainsKey(optionSymbol);
                    context.Logger.Info($"AssetManager: Option chain in Securities collection: {isInSecurities}");
                    if (isInSecurities)
                    {
                        var optionSecurity = context.Algorithm.Securities[optionSymbol];
                        var subscriptionResolutions = new System.Collections.Generic.List<Resolution>();
                        foreach (var sub in optionSecurity.Subscriptions)
                        {
                            subscriptionResolutions.Add(sub.Resolution);
                            context.Logger.Info($"AssetManager: [SUB] DataType={sub.Type?.Name}, Resolution={sub.Resolution}, TickType={sub.TickType}");
                        }
                        var distinctRes = subscriptionResolutions.Distinct().OrderBy(x => x).ToList();
                        context.Logger.Info($"AssetManager: [DEBUG] Option Security Details:");
                        context.Logger.Info($"   Type: {optionSecurity.Type}");
                        context.Logger.Info($"   Subscriptions: {string.Join(", ", distinctRes)}");
                        context.Logger.Info($"   Exchange: {optionSecurity.Exchange}");
                        context.Logger.Info($"   IsMarketOpen: {optionSecurity.Exchange.ExchangeOpen}");
                        var hasMinuteData = distinctRes.Contains(Resolution.Minute) ||
                                          distinctRes.Contains(Resolution.Second);
                        if (!hasMinuteData && distinctRes.All(r => r == Resolution.Daily))
                        {
                            context.Logger.Warning($"AssetManager: [RESOLUTION WARNING] Only Daily resolution subscriptions found");
                            context.Logger.Warning($"AssetManager: [RESOLUTION WARNING] Intraday option chains may not be available");
                            context.Logger.Warning($"AssetManager: [HINT] If Minute was requested, ensure UniverseSettings.Resolution=Minute before adding options.");
                        }
                        else
                        {
                            context.Logger.Info($"AssetManager: [RESOLUTION SUCCESS] Intraday subscriptions available");
                        }
                    }
                    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())
                    {
                        context.Logger.Info($"AssetManager: [DEBUG] Option-related securities in collection ({optionRelatedSecurities.Count}):");
                        foreach (var sec in optionRelatedSecurities)
                        {
                            context.Logger.Info($"   {sec} (Type: {sec.SecurityType})");
                        }
                    }
                    else
                    {
                        context.Logger.Info($"AssetManager: [ERROR] No option-related securities found in collection");
                    }
                    context.Logger.Info($"AssetManager: Option chain ready for filtering and data feed");
                    context.Logger.Info($"AssetManager: [TARGET] Symbol to use for slice.OptionChains access: {optionSymbol}");
                }
                else
                {
                    context.Logger.Error($"AssetManager: [FAILED] Failed to create options chain for {symbol} - returned null symbol");
                }
                return optionSymbol;
            }
            catch (Exception ex)
            {
                context.Logger.Error($"AssetManager: Failed to add options chain for {symbol}: {ex.Message}");
                throw;
            }
        }
        public static bool IsIndex(string symbol)
        {
            if (string.IsNullOrWhiteSpace(symbol))
                return false;
            return CashIndices.Contains(symbol.ToUpperInvariant());
        }
        public static bool IsFuture(string symbol)
        {
            if (string.IsNullOrWhiteSpace(symbol))
                return false;
            return Futures.Contains(symbol.ToUpperInvariant());
        }
        public static bool IsEquity(string symbol)
        {
            if (string.IsNullOrWhiteSpace(symbol))
                return false;
            return !IsIndex(symbol) && !IsFuture(symbol);
        }
        public static string GetAssetType(string symbol)
        {
            if (IsIndex(symbol)) return "INDEX";
            if (IsFuture(symbol)) return "FUTURE";
            return "EQUITY";
        }
        public static string[] GetSupportedIndices()
        {
            return CashIndices.ToArray();
        }
        public static string[] GetSupportedFutures()
        {
            return Futures.ToArray();
        }
        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];
            }
        }
        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
            {
                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
{
    public static class MultiAssetHelper
    {
        private static readonly Dictionary<string, AssetProfile> AssetProfiles = new Dictionary<string, AssetProfile>
        {
            ["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 },
            ["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 },
            ["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 },
            ["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 },
        };
        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
                {
                    var security = AssetManager.AddAsset(context, symbol, resolution);
                    var optionsSymbol = AssetManager.AddOptionsChain(context, security, resolution);
                    result[symbol.ToUpperInvariant()] = (security, optionsSymbol);
                    context.Logger.Debug($"MultiAssetHelper: Successfully added {symbol} with options chain");
                }
                catch (Exception ex)
                {
                    context.Logger.Error($"MultiAssetHelper: Failed to add {symbol}: {ex.Message}");
                    throw;
                }
            }
            return result;
        }
        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;
            }
            return baseStrikeWidth;
        }
        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))
            {
                var recommendedAllocation = Math.Max(0.05m, Math.Min(0.2m, 0.15m / profile.TypicalVolatility));
                return (profile.MinPosition, profile.MaxPosition, recommendedAllocation);
            }
            return (1, 5, 0.1m);
        }
        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))
            {
                if (profile.TypicalVolatility > 0.4m)
                {
                    var adjustment = 0.05m;
                    return (baseDeltaMin + adjustment, baseDeltaMax - adjustment);
                }
                else if (profile.TypicalVolatility < 0.2m)
                {
                    var adjustment = 0.03m;
                    return (Math.Max(0.05m, baseDeltaMin - adjustment), Math.Min(0.45m, baseDeltaMax + adjustment));
                }
            }
            return (baseDeltaMin, baseDeltaMax);
        }
        public static bool HasLiquidOptions(string symbol)
        {
            if (string.IsNullOrWhiteSpace(symbol))
                return false;
            var upperSymbol = symbol.ToUpperInvariant();
            return AssetProfiles.ContainsKey(upperSymbol);
        }
        public static string[] GetSupportedSymbols()
        {
            var result = new string[AssetProfiles.Count];
            AssetProfiles.Keys.CopyTo(result, 0);
            return result;
        }
        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;
        }
        public static decimal GetStrikeIncrement(string symbol)
        {
            if (string.IsNullOrWhiteSpace(symbol))
                return 1m;
            var upperSymbol = symbol.ToUpperInvariant();
            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
            }
        }
    }
    public class AssetProfile
    {
        public decimal TypicalVolatility { get; set; }
        public decimal StrikeWidthMultiplier { get; set; }
        public int MinPosition { get; set; }
        public int MaxPosition { get; set; }
        public decimal EstimatedMarginMultiplier { get; set; } = 0.3m;
        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.Collections.Generic;
using System.Linq;
using QuantConnect;
using QuantConnect.Orders;
using QuantConnect.Securities;
namespace CoreAlgo.Architecture.QC.Helpers
{
    public static class PriceRounding
    {
        public static decimal GetMinPriceVariation(Security security)
        {
            if (security == null)
            {
                return 0.01m;
            }
            try
            {
                var tick = security.PriceVariationModel?.GetMinimumPriceVariation(new GetMinimumPriceVariationParameters(security, security.Price)) ?? 0m;
                if (tick <= 0m)
                {
                    tick = security.SymbolProperties?.MinimumPriceVariation ?? 0m;
                }
                if (tick > 0m)
                {
                    return tick;
                }
                if (security.Type == SecurityType.Equity)
                {
                    return security.Price < 1m ? 0.0001m : 0.01m;
                }
            }
            catch
            {
            }
            return 0.01m;
        }
        public static decimal GetMinPriceVariation(SecurityManager securities, Symbol symbol)
        {
            if (securities == null || symbol == null)
            {
                return 0.01m;
            }
            return securities.TryGetValue(symbol, out var security)
                ? GetMinPriceVariation(security)
                : 0.01m;
        }
        public static decimal FloorToTick(decimal price, decimal tick)
        {
            return tick > 0m ? Math.Floor(price / tick) * tick : price;
        }
        public static decimal CeilToTick(decimal price, decimal tick)
        {
            return tick > 0m ? Math.Ceiling(price / tick) * tick : price;
        }
        public static decimal RoundToNearestTick(decimal price, decimal tick)
        {
            return tick > 0m ? Math.Round(price / tick, MidpointRounding.AwayFromZero) * tick : price;
        }
        public static decimal RoundLimitPrice(SecurityManager securities, Symbol symbol, decimal quantity, decimal limitPrice)
        {
            var tick = GetMinPriceVariation(securities, symbol);
            return quantity >= 0 ? FloorToTick(limitPrice, tick) : CeilToTick(limitPrice, tick);
        }
        public static decimal RoundStopPrice(SecurityManager securities, Symbol symbol, decimal quantity, decimal stopPrice)
        {
            var tick = GetMinPriceVariation(securities, symbol);
            return quantity >= 0 ? CeilToTick(stopPrice, tick) : FloorToTick(stopPrice, tick);
        }
        public static decimal RoundTrailingStopPrice(SecurityManager securities, Symbol symbol, decimal quantity, decimal trailingAmount, bool isDollarTrailing)
        {
            var tick = GetMinPriceVariation(securities, symbol);
            return CeilToTick(trailingAmount, tick);
        }
        public static (decimal RoundedStop, decimal RoundedLimit) RoundStopLimitPrices(
            SecurityManager securities,
            Symbol symbol,
            decimal quantity,
            decimal stopPrice,
            decimal limitPrice)
        {
            var tick = GetMinPriceVariation(securities, symbol);
            if (tick <= 0m)
            {
                tick = 0.01m;
            }
            if (stopPrice <= 0m || limitPrice <= 0m)
            {
                throw new ArgumentException($"Invalid stop-limit prices: stop={stopPrice}, limit={limitPrice} must be > 0");
            }
            if (quantity >= 0)
            {
                var roundedStop = CeilToTick(stopPrice, tick);
                var roundedLimit = FloorToTick(limitPrice, tick);
                if (roundedLimit < roundedStop + tick)
                {
                    roundedLimit = roundedStop + tick;
                }
                if (roundedLimit < roundedStop)
                {
                    roundedLimit = roundedStop + tick;
                }
                return (roundedStop, roundedLimit);
            }
            else
            {
                var roundedStop = FloorToTick(stopPrice, tick);
                var roundedLimit = CeilToTick(limitPrice, tick);
                if (roundedLimit > roundedStop - tick)
                {
                    roundedLimit = roundedStop - tick;
                }
                if (roundedLimit >= roundedStop)
                {
                    roundedLimit = roundedStop - tick;
                }
                if (roundedLimit >= roundedStop - tick)
                {
                    roundedLimit = roundedStop - tick;
                }
                if (roundedLimit <= 0m)
                {
                    throw new ArgumentException($"Stop-limit prices invalid after rounding: stop={roundedStop}, limit={roundedLimit}, tick={tick}. Buffer too small for tick size.");
                }
                return (roundedStop, roundedLimit);
            }
        }
        public static decimal GetComboNetTick(SecurityManager securities, IReadOnlyList<Leg> legs)
        {
            if (legs == null || legs.Count == 0)
            {
                return 0.01m;
            }
            var ticks = legs
                .Select(leg => securities != null && securities.TryGetValue(leg.Symbol, out var security)
                    ? GetMinPriceVariation(security)
                    : 0.01m)
                .Where(t => t > 0m);
            var minTick = ticks.DefaultIfEmpty(0.01m).Min();
            return minTick > 0m ? minTick : 0.01m;
        }
        public static decimal RoundComboNetPrice(
            SecurityManager securities,
            IReadOnlyList<Leg> legs,
            decimal netPrice,
            bool isDebit)
        {
            var netTick = GetComboNetTick(securities, legs);
            return isDebit ? FloorToTick(netPrice, netTick) : CeilToTick(netPrice, netTick);
        }
    }
}
using System;
using System.Collections.Generic;
using System.Linq;
using CoreAlgo.Architecture.Core.Models;
using QuantConnect;
using QuantConnect.Algorithm;
using QuantConnect.Data.Market;
using QuantConnect.Securities.Option;
namespace CoreAlgo.Architecture.QC.Helpers
{
    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;
        }
        public class DeltaTargets
        {
            public decimal ShortPut { get; set; }
            public decimal LongPut { get; set; }
            public decimal ShortCall { get; set; }
            public decimal LongCall { get; set; }
        }
        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; }
        }
        public DeltaTargets GetAssetSpecificDeltas(string symbol, decimal currentVolatility)
        {
            var (deltaMin, deltaMax) = MultiAssetHelper.GetAssetDeltaTargets(symbol, 0.15m, 0.25m);
            var adjustment = currentVolatility > _highVolThreshold ? _volAdjustment : 0m;
            return new DeltaTargets
            {
                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)
            };
        }
        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 };
            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();
            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);
            return ValidateAndAdjustStrikes(strikes, chain.Symbol.Underlying.Value);
        }
        public decimal GetATMStrike(OptionChain chain)
        {
            var underlyingPrice = chain.Underlying.Price;
            var atmStrike = chain
                .Select(x => x.Strike)
                .Distinct()
                .OrderBy(strike => Math.Abs(strike - underlyingPrice))
                .FirstOrDefault();
            return atmStrike > 0 ? atmStrike : underlyingPrice;
        }
        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)
            {
                if (contract.Greeks?.Delta == null) continue;
                var deltaDiff = Math.Abs(contract.Greeks.Delta - targetDelta);
                if (deltaDiff < bestDeltaDiff)
                {
                    bestDeltaDiff = deltaDiff;
                    bestContract = contract;
                }
            }
            if (bestContract == null)
            {
                return EstimateStrikeByDelta(contracts, targetDelta, atmStrike);
            }
            return bestContract.Strike;
        }
        private decimal EstimateStrikeByDelta(List<OptionContract> contracts, decimal targetDelta, decimal atmStrike)
        {
            var isPut = contracts.FirstOrDefault()?.Right == OptionRight.Put;
            var absTargetDelta = Math.Abs(targetDelta);
            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);
            return contracts
                .Select(x => x.Strike)
                .OrderBy(strike => Math.Abs(strike - targetStrike))
                .FirstOrDefault();
        }
        private StrikeRange ValidateAndAdjustStrikes(StrikeRange strikes, string symbol)
        {
            var increment = MultiAssetHelper.GetStrikeIncrement(symbol);
            var minSpreadWidth = increment * 2; // Minimum 2 strikes apart
            if (strikes.ShortPutStrike - strikes.LongPutStrike < minSpreadWidth)
            {
                strikes.LongPutStrike = strikes.ShortPutStrike - minSpreadWidth;
            }
            if (strikes.LongCallStrike - strikes.ShortCallStrike < minSpreadWidth)
            {
                strikes.LongCallStrike = strikes.ShortCallStrike + minSpreadWidth;
            }
            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;
        }
        private decimal RoundToIncrement(decimal strike, decimal increment)
        {
            return Math.Round(strike / increment) * increment;
        }
        public bool ValidateStrikeSpacing(StrikeRange strikes)
        {
            var hasLongStrikes = strikes.LongPutStrike > 0 || strikes.LongCallStrike > 0;
            var hasShortStrikes = strikes.ShortPutStrike > 0 || strikes.ShortCallStrike > 0;
            var hasBothPutAndCall = strikes.ShortPutStrike > 0 && strikes.ShortCallStrike > 0;
            if (hasLongStrikes && hasShortStrikes && hasBothPutAndCall &&
                strikes.LongPutStrike > 0 && strikes.LongCallStrike > 0)
            {
                return strikes.LongPutStrike < strikes.ShortPutStrike &&
                       strikes.ShortPutStrike < strikes.ATMStrike &&
                       strikes.ATMStrike < strikes.ShortCallStrike &&
                       strikes.ShortCallStrike < strikes.LongCallStrike;
            }
            if (hasShortStrikes && hasBothPutAndCall && !hasLongStrikes)
            {
                return strikes.ShortPutStrike < strikes.ATMStrike &&
                       strikes.ShortCallStrike > strikes.ATMStrike;
            }
            return ValidateBasicStrikeOrder(strikes);
        }
        private bool ValidateBasicStrikeOrder(StrikeRange strikes)
        {
            bool validPutSide = strikes.ShortPutStrike <= 0 || strikes.ShortPutStrike < strikes.ATMStrike;
            bool validCallSide = strikes.ShortCallStrike <= 0 || strikes.ShortCallStrike > strikes.ATMStrike;
            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;
        }
        public Dictionary<string, object> GetStrikeStats(StrikeRange strikes)
        {
            return new Dictionary<string, object>
            {
                ["ATMStrike"] = strikes.ATMStrike,
                ["PutSpreadWidth"] = strikes.ShortPutStrike - strikes.LongPutStrike,
                ["CallSpreadWidth"] = strikes.LongCallStrike - strikes.ShortCallStrike,
                ["TotalWidth"] = strikes.LongCallStrike - strikes.LongPutStrike,
                ["PutDistance"] = (strikes.ATMStrike - strikes.ShortPutStrike) / strikes.ATMStrike,
                ["CallDistance"] = (strikes.ShortCallStrike - strikes.ATMStrike) / strikes.ATMStrike
            };
        }
    }
}
using System;
using System.Collections.Concurrent;
using System.Collections.Generic;
using System.Linq;
using QuantConnect;
using QuantConnect.Algorithm;
using QuantConnect.Data.Market;
using QuantConnect.Data.UniverseSelection;
using QuantConnect.Securities;
namespace CoreAlgo.Architecture.QC.Helpers
{
    public static class UniverseOptimizer
    {
        public static Dictionary<Symbol, List<decimal>> BatchFetchHistory(
            QCAlgorithm algorithm,
            IEnumerable<Symbol> symbols,
            int days,
            Resolution resolution = Resolution.Daily,
            int batchSize = 500)
        {
            var results = new ConcurrentDictionary<Symbol, List<decimal>>();
            var symbolList = symbols.ToList();
            for (int i = 0; i < symbolList.Count; i += batchSize)
            {
                var batch = symbolList.Skip(i).Take(batchSize).ToList();
                try
                {
                    var history = algorithm.History<TradeBar>(batch, days, resolution);
                    foreach (var dateGroup in history)
                    {
                        foreach (var symbolData in dateGroup)
                        {
                            var symbol = symbolData.Key;
                            var bar = symbolData.Value;
                            if (!results.ContainsKey(symbol))
                            {
                                results[symbol] = new List<decimal>();
                            }
                            results[symbol].Add(bar.Volume);
                        }
                    }
                }
                catch (Exception ex)
                {
                    algorithm.Log($"Error fetching history for batch starting at index {i}: {ex.Message}");
                }
            }
            return results.ToDictionary(kvp => kvp.Key, kvp => kvp.Value);
        }
        public static Dictionary<Symbol, decimal> CalculateBatchedADV(
            QCAlgorithm algorithm,
            IEnumerable<Symbol> symbols,
            int days = 21)
        {
            var historyData = BatchFetchHistory(algorithm, symbols, days, Resolution.Daily);
            var advResults = new Dictionary<Symbol, decimal>();
            foreach (var kvp in historyData)
            {
                var symbol = kvp.Key;
                var volumes = kvp.Value;
                if (volumes.Any())
                {
                    advResults[symbol] = volumes.Average();
                }
                else
                {
                    advResults[symbol] = 0;
                }
            }
            return advResults;
        }
        public static Dictionary<Symbol, decimal> CalculateVolumeShock(
            QCAlgorithm algorithm,
            ConcurrentDictionary<Symbol, long> intradayVolumes,
            IEnumerable<Symbol> symbols,
            int advDays = 21)
        {
            var advData = CalculateBatchedADV(algorithm, symbols, advDays);
            var shockRatios = new Dictionary<Symbol, decimal>();
            foreach (var symbol in symbols)
            {
                var intradayVol = intradayVolumes.GetValueOrDefault(symbol, 0L);
                var adv = advData.GetValueOrDefault(symbol, 0m);
                var ratio = adv > 0 ? (decimal)intradayVol / adv : 0m;
                shockRatios[symbol] = ratio;
            }
            return shockRatios;
        }
        public static void CleanupRemovedSecurities(
            QCAlgorithm algorithm,
            SecurityChanges changes,
            params object[] trackingDictionaries)
        {
            foreach (var removed in changes.RemovedSecurities)
            {
                if (algorithm.Portfolio[removed.Symbol].Invested)
                {
                    algorithm.Log($"Liquidating position in removed security: {removed.Symbol}");
                    algorithm.Liquidate(symbol: removed.Symbol, tag: "Security removed from universe", asynchronous: true);
                }
                foreach (var dict in trackingDictionaries)
                {
                    switch (dict)
                    {
                        case ConcurrentDictionary<Symbol, long> longDict:
                            longDict.TryRemove(removed.Symbol, out _);
                            break;
                        case ConcurrentDictionary<Symbol, decimal> decimalDict:
                            decimalDict.TryRemove(removed.Symbol, out _);
                            break;
                        case Dictionary<Symbol, long> simpleLongDict:
                            simpleLongDict.Remove(removed.Symbol);
                            break;
                        case Dictionary<Symbol, decimal> simpleDecimalDict:
                            simpleDecimalDict.Remove(removed.Symbol);
                            break;
                        case HashSet<Symbol> symbolSet:
                            symbolSet.Remove(removed.Symbol);
                            break;
                    }
                }
            }
        }
        public static void SetupOptimizedUniverse(
            QCAlgorithm algorithm,
            Resolution resolution = Resolution.Minute,
            bool enableAsync = true,
            bool extendedHours = false)
        {
            algorithm.UniverseSettings.Resolution = resolution;
            algorithm.UniverseSettings.Asynchronous = enableAsync;
            algorithm.UniverseSettings.ExtendedMarketHours = extendedHours;
            algorithm.Log($"Universe configured: Resolution={resolution}, Async={enableAsync}, ExtendedHours={extendedHours}");
        }
        public static IEnumerable<TResult> ProcessInParallel<TInput, TResult>(
            IEnumerable<TInput> data,
            Func<TInput, TResult> processor,
            int batchSize = 500)
        {
            var results = new ConcurrentBag<TResult>();
            var batches = data.Batch(batchSize);
            System.Threading.Tasks.Parallel.ForEach(batches, batch =>
            {
                foreach (var item in batch)
                {
                    try
                    {
                        var result = processor(item);
                        results.Add(result);
                    }
                    catch
                    {
                    }
                }
            });
            return results;
        }
    }
    public static class UniverseExtensions
    {
        public static IEnumerable<IEnumerable<T>> Batch<T>(this IEnumerable<T> source, int batchSize)
        {
            var batch = new List<T>(batchSize);
            foreach (var item in source)
            {
                batch.Add(item);
                if (batch.Count == batchSize)
                {
                    yield return batch;
                    batch = new List<T>(batchSize);
                }
            }
            if (batch.Count > 0)
                yield return batch;
        }
    }
}
    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;
namespace QuantConnect.Algorithm.CSharp
{
    public class CoreAlgo : QCAlgorithm
    {
        private IStrategy _strategy;
        private int _debugCallCount = 0;
        public QCLogger<CoreAlgo> Logger { get; private set; }
        private bool _warmupCompleteLogged = false;
        public override void Initialize()
        {
            var startDate = GetParameter("StartDate", "2023-12-25");
            var endDate = GetParameter("EndDate", "2024-12-25");
            var accountSize = GetParameter("AccountSize", 100000m);
            SetStartDate(DateTime.Parse(startDate));
            SetEndDate(DateTime.Parse(endDate));
            SetCash(accountSize);
            SetBrokerageModel(BrokerageName.InteractiveBrokersBrokerage, AccountType.Margin);
            Portfolio.SetPositions(SecurityPositionGroupModel.Null);
            SetSecurityInitializer(CompleteSecurityInitializer);
            UniverseSettings.Resolution = Resolution.Minute;
            UniverseSettings.ExtendedMarketHours = false;
            UniverseSettings.DataNormalizationMode = DataNormalizationMode.Raw;
            var logLevel = GetParameter("LogLevel", 2);
            var verboseMode = bool.Parse(GetParameter("VerboseMode", "false"));
            Logger = new QCLogger<CoreAlgo>(this, logLevel, verboseMode);
            Logger.Info($"UniverseSettings: Resolution={UniverseSettings.Resolution}, FillForward={UniverseSettings.FillForward}, ExtendedMarketHours={UniverseSettings.ExtendedMarketHours}, DataNormalizationMode={UniverseSettings.DataNormalizationMode}");
            var strategyType = GetParameter("Strategy", "IronCondor");
            Logger.Info($"Initializing strategy: {strategyType}");
            try
            {
                _strategy = StrategyDiscovery.CreateStrategy(strategyType);
            }
            catch (ArgumentException ex)
            {
                Logger.Warning($"Unknown strategy '{strategyType}', defaulting to IronCondor. {ex.Message}");
                _strategy = StrategyDiscovery.CreateStrategy("IronCondor");
            }
            if (_strategy is SimpleBaseStrategy sbs)
            {
                sbs.SetContext(Logger);
                var desiredResolution = sbs.Config?.GetUnderlyingResolution() ?? Resolution.Minute;
                UniverseSettings.Resolution = desiredResolution;
                _strategy.Initialize(this);
                Logger.Info($"Strategy '{_strategy.Name}' initialized (state: {_strategy.State})");
                Logger.Info($"Description: {_strategy.Description}");
                sbs.EnsureSmartPricingInitialized();
                sbs.TradePersistence?.ClearPositionsIfBacktest();
                sbs.TradePersistence?.ClearSnapshotsIfBacktest();
                ScheduleEodSummary();
            }
            else
            {
                _strategy.Initialize(this);
                Logger.Warning($"Strategy is not SimpleBaseStrategy: {_strategy?.GetType().Name}");
            }
        }
        public override void OnData(Slice data)
        {
            _debugCallCount++;
            if (IsWarmingUp)
            {
                return;
            }
            if (!_warmupCompleteLogged)
            {
                var chainsCount = data.OptionChains.Count;
                var keys = chainsCount > 0 ? string.Join(", ", data.OptionChains.Keys) : "<none>";
                Logger?.Info($"WARMUP COMPLETED at {Time:yyyy-MM-dd HH:mm:ss} - LiveMode: {LiveMode}, Resolution: {UniverseSettings.Resolution}");
                Logger?.Info($"Option chain snapshot - Count: {chainsCount}, Keys: [{keys}]");
                _warmupCompleteLogged = true;
            }
            if (data.OptionChains.Count > 0)
            {
                Logger.Debug($"Options chains available: {data.OptionChains.Count}");
            }
            if (_strategy != null)
            {
                try
                {
                    _strategy.Execute(data);
                }
                catch (Exception ex)
                {
                    Logger.LogError(ex, "Strategy execution error");
                }
            }
            else
            {
                if (_debugCallCount <= 5)
                {
                    Logger.Error("CRITICAL: No strategy initialized - algorithm cannot trade");
                    Logger.Error("Check strategy initialization logs above for errors");
                }
            }
        }
        public override void OnSecuritiesChanged(SecurityChanges changes)
        {
            base.OnSecuritiesChanged(changes);
            if (_strategy is SimpleBaseStrategy baseStrategy)
            {
                try
                {
                    baseStrategy.OnSecuritiesChanged(changes);
                }
                catch (Exception ex)
                {
                    Logger.LogError(ex, "Error in strategy OnSecuritiesChanged");
                }
            }
        }
        public override void OnOrderEvent(OrderEvent orderEvent)
        {
            base.OnOrderEvent(orderEvent);
            if (!(_strategy is SimpleBaseStrategy s)) return;
            s.SmartOrderManager?.OnOrderEvent(orderEvent);
            var orderId = orderEvent.OrderId.ToString();
            switch (orderEvent.Status)
            {
                case OrderStatus.Submitted:
                    s.TradePersistence.SaveTrades(s.TradeTracker);
                    break;
                case OrderStatus.PartiallyFilled:
                    var trade = s.TradeTracker.AllTrades.FirstOrDefault(t => t.OrderId == orderId);
                    if (trade != null)
                    {
                        trade.MarkAsPartialFill((int)orderEvent.FillQuantity, orderEvent.FillPrice);
                        Logger.Debug($"Partial fill: {orderId} - {orderEvent.FillQuantity} @ {orderEvent.FillPrice}");
                        s.TradePersistence.SaveTrades(s.TradeTracker);
                    }
                    break;
                case OrderStatus.Filled:
                    s.TrackOrderFilled(orderEvent);
                    break;
                case OrderStatus.Canceled:
                    s.TrackOrderCancelled(orderId);
                    break;
            }
            s.OnOrderEventRouted(orderEvent);
        }
        public override void OnEndOfDay(Symbol symbol)
        {
            Logger?.Debug($"OnEndOfDay called for symbol {symbol}");
        }
        private void ScheduleEodSummary()
        {
            Schedule.On(DateRules.EveryDay(), TimeRules.At(16, 5), () =>
            {
                Logger?.Debug("ScheduleEodSummary triggered at 16:05 ET");
                TradeSummary summary = null;
                if (_strategy is SimpleBaseStrategy strategy)
                {
                    if (strategy.Reporter != null)
                    {
                        strategy.Reporter.RecordDailyEquity();
                        strategy.Reporter.SaveEquityCurve();
                    }
                    if (strategy.TradePersistence != null)
                    {
                        strategy.TradePersistence.SaveDailySnapshot(strategy.TradeTracker);
                        summary = strategy.TradePersistence.GetTradeSummary(strategy.TradeTracker);
                    }
                }
                SmartLoggerStore.ProcessDailyLogs(this, summary);
            });
        }
        public override void OnEndOfAlgorithm()
        {
            if (_strategy is SimpleBaseStrategy strategy)
            {
                strategy.Shutdown();
                strategy.Reporter?.RecordDailyEquity();
                strategy.Reporter?.SaveEquityCurve();
                strategy.Reporter?.LogPerformanceSummary(strategy.TradeTracker, strategy.Name);
                strategy.TradePersistence?.SaveTrades(strategy.TradeTracker);
            }
            SmartLoggerStore.ProcessDailyLogs(this);
            Logger?.Info($"Algorithm finished. Final portfolio value: ${Portfolio.TotalPortfolioValue:N2}");
            Logger?.Info($"Total trades: {Transactions.GetOrderTickets().Count()}");
        }
        public override void OnAssignmentOrderEvent(OrderEvent assignmentEvent)
        {
            Log($"Assignment event forwarded to strategy: {assignmentEvent.Symbol}");
            if (_strategy is SimpleBaseStrategy strategy)
            {
                strategy.OnAssignmentOrderEvent(assignmentEvent);
            }
            else
            {
                Log($"Strategy does not support assignment handling: {_strategy?.GetType().Name ?? "null"}");
            }
        }
        private void CompleteSecurityInitializer(Security security)
        {
            Logger?.Debug($"CompleteSecurityInitializer: {security.Symbol} ({security.Type})");
            if (security.Type == SecurityType.IndexOption)
            {
                Logger?.Trace($"Configuring SPX index option: {security.Symbol}");
                security.SetBuyingPowerModel(BuyingPowerModel.Null);
                if (security is IndexOption indexOption)
                {
                    indexOption.SetOptionAssignmentModel(new NullOptionAssignmentModel());
                    Logger?.Trace($"Applied NullOptionAssignmentModel to {security.Symbol}");
                }
            }
            else
            {
                security.SetBuyingPowerModel(BuyingPowerModel.Null);
            }
            security.SetSlippageModel(new VolumeShareSlippageModel());
            if (LiveMode)
                return;
            var normMode = UniverseSettings.DataNormalizationMode;
            security.Subscriptions.SetDataNormalizationMode(normMode);
            var mode = security.Subscriptions.FirstOrDefault()?.DataNormalizationMode;
            Logger?.Trace($"Initializer normalization set: {security.Symbol} => {mode}");
            var lastPrices = GetLastKnownPrices(security);
            if (lastPrices != null && lastPrices.Any())
            {
                security.SetMarketPrice(lastPrices.First());
            }
        }
    }
}