Overall Statistics
Total Orders
438
Average Win
1.99%
Average Loss
-1.07%
Compounding Annual Return
47.713%
Drawdown
51.600%
Expectancy
0.829
Start Equity
1000000
End Equity
8281075.32
Net Profit
728.108%
Sharpe Ratio
1.133
Sortino Ratio
1.177
Probabilistic Sharpe Ratio
53.231%
Loss Rate
36%
Win Rate
64%
Profit-Loss Ratio
1.87
Alpha
0.243
Beta
1.022
Annual Standard Deviation
0.308
Annual Variance
0.095
Information Ratio
0.962
Tracking Error
0.255
Treynor Ratio
0.342
Total Fees
$9232.19
Estimated Strategy Capacity
$130000000.00
Lowest Capacity Asset
ETN R735QTJ8XC9X
Portfolio Turnover
3.74%
#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.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.Interfaces;
using QuantConnect.Securities.Volatility;
using QuantConnect.Storage;
using QuantConnect.Statistics;
using QCAlgorithmFramework = QuantConnect.Algorithm.QCAlgorithm;
using QCAlgorithmFrameworkBridge = QuantConnect.Algorithm.QCAlgorithm;
#endregion

namespace QuantConnect
{

    class TempAlphaModel : AlphaModel
    {
        public override string Name { get; set; }
        private readonly Resolution _resolution;
        // private InsightCollection _insightCollection = new();

        /// <summary>
        /// Dictionary containing basic information for each symbol present as key
        /// </summary>
        // protected Dictionary<Symbol, SymbolData> _symbolData { get; init; }

        public TempAlphaModel(
            Resolution resolution = Resolution.Daily
        )
        {
            // Define a unique name for the alpha model but 
            // should be consistent across different backtests
            // (i.e. don't use Guid.NewGuid().ToString() here)
            // You can use the a comb of the private fields given by the constructor params
            _resolution = resolution;
            // Name = $"{nameof(MacdAlphaModel)}({fastPeriod},{slowPeriod},{signalPeriod},{movingAverageType},{resolution})";
            Name = $"{nameof(TempAlphaModel)}({resolution})";
        }
        public override IEnumerable<Insight> Update(QCAlgorithm algorithm, Slice data)
        {
            // TODO: Check out the impl of the example alpha models in the repo:
            // https://github.com/QuantConnect/Lean/tree/master/Algorithm.Framework/Alphas
            return new List<Insight>();
        }

        // private List<Security> _securities = new List<Security>();

        public override void OnSecuritiesChanged(QCAlgorithm algorithm, SecurityChanges changes)
        {
            // You can also call the base.OnSecuritiesChanged(algorithm, changes) to handle the insights changes.
            // foreach (var security in changes.AddedSecurities)
            // {
            //     var dynamicSecurity = security as dynamic;
            //     dynamicSecurity.Sma = algorithm.SMA(security.Symbol, 20);
            //     dynamicSecurity.Rsi = algorithm.RSI(security.Symbol, 14, MovingAverageType.Simple, Resolution.Daily);
            //     algorithm.WarmUpIndicator(security.Symbol, dynamicSecurity.Sma);
            //     _securities.Add(security);
            // }

            // foreach (var security in changes.RemovedSecurities)
            // {
            //     if (_securities.Contains(security))
            //     {
            //         algorithm.DeregisterIndicator((security as dynamic).Sma);
            //         algorithm.DeregisterIndicator((security as dynamic).Rsi);
            //         _securities.Remove(security);
            //     }
            // }
        }
        public class SymbolData {
            // TODO: Necessary fields for the symbol data
        }
    }
}
#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.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.Interfaces;
using QuantConnect.Securities.Volatility;
using QuantConnect.Storage;
using QuantConnect.Statistics;
using QCAlgorithmFramework = QuantConnect.Algorithm.QCAlgorithm;
using QCAlgorithmFrameworkBridge = QuantConnect.Algorithm.QCAlgorithm;
using System.Security.Cryptography.X509Certificates;
using QLNet;
using Accord.Math;
using Accord.Math.Optimization;
using Accord.Statistics;
using Accord;
using Fasterflect;
using MathNet.Numerics.LinearAlgebra;
using MathNet.Numerics.Statistics;
#endregion
namespace QuantConnect.Algorithm.CSharp
{
   
    // Check out the overview of QCAlgorithm:
    // https://www.quantconnect.com/docs/v2/writing-algorithms/key-concepts/algorithm-engine#03-Your-Algorithm-and-LEAN
    public class League2024Q4 : QCAlgorithm
    {
        // Public fields
        public const string DateFormat = "yyyy-MM-dd HH:mm:ss";
        public const decimal InitialCash = 1_000_000;
        public readonly (DateTime, DateTime)[] TestPeriods = new[]
        {                                                         //[i]  [N] years (start => end, drawdown < -20%)
            (new DateTime(2019, 1, 1), new DateTime(2024, 6, 1)), //[0]  5 years   (2020-01-20 => 2020-04-04, -41.33%)
            (new DateTime(2014, 1, 1), new DateTime(2024, 6, 1)), //[1]  10 years  (2020-01-20 => 2020-04-04, -41.33%)
            (new DateTime(2014, 1, 1), new DateTime(2019, 6, 1)), //[2]  5 years
            (new DateTime(2009, 1, 1), new DateTime(2014, 6, 1)), //[3]  5 years   (2010-03-16 => 2012-01-31, -27.54%)
            (new DateTime(2004, 1, 1), new DateTime(2009, 6, 1)), //[4]  5 years   (2007-07-28 => 2009-07-07, -78.28%)
            (new DateTime(1999, 1, 1), new DateTime(2004, 6, 1)), //[5]  5 years   (1999-03-01 => 2003-03-13, -59.88%)
            (new DateTime(1999, 1, 1), new DateTime(2009, 6, 1)), //[6]  10 years
            (new DateTime(2004, 1, 1), new DateTime(2014, 6, 1)), //[7]  10 years
            (new DateTime(2009, 1, 1), new DateTime(2019, 6, 1)), //[8]  10 years
            (new DateTime(1999, 1, 1), new DateTime(2014, 6, 1)), //[9]  15 years
            (new DateTime(2004, 1, 1), new DateTime(2019, 6, 1)), //[10] 15 years
            (new DateTime(2009, 1, 1), new DateTime(2024, 6, 1)), //[11] 15 years
            (new DateTime(1999, 1, 1), new DateTime(2019, 6, 1)), //[12] 20 years
            (new DateTime(2004, 1, 1), new DateTime(2024, 6, 1)), //[13] 20 years
            (new DateTime(1999, 1, 1), new DateTime(2024, 6, 1)), //[14] 25 years
            (new DateTime(1998, 1, 1), new DateTime(2016, 6, 1)), //[15] 18 years  (1999-11-28 => 2003-02-28, -66.10%) (2007-07-12 => 2009-03-12, -61.17%)
        };
        /* Events Identified:
         1. 2020-01-20 to 2020-04-04 (-41.33%)
            *COVID-19 Pandemic*
            - This period corresponds to the global stock market crash caused by the COVID-19 pandemic.
            Beginning in late January 2020, as the virus spread and lockdown measures were enforced globally, 
            the financial markets experienced extreme volatility. The S&P 500 and other major indexes saw 
            sharp declines, with March 2020 being particularly devastating. Central banks responded with 
            aggressive monetary policy measures, which helped stabilize markets in the second quarter of 2020.

         2. 1999-11-28 to 2003-02-28 (-66.10%)
            *Dot-Com Bubble Burst*
            The period from late 1999 to early 2003 marks the collapse of the dot-com bubble.
            - Many technology stocks were vastly overvalued in the late 1990s, fueled by speculative investments 
            in internet-based companies. The bubble burst in early 2000, and stock prices for technology companies
            plummeted. The NASDAQ Composite Index, which included many of these companies, lost nearly 78% of its 
            value from its peak in March 2000 to its trough in October 2002. The crash was compounded by economic 
            slowdowns and the 9/11 attacks in 2001.

         3. 2007-07-12 to 2009-03-12 (-61.17%)
            *Global Financial Crisis (GFC)*
            This period corresponds to the global financial crisis, which began in mid-2007 and reached
            its peak in late 2008 and early 2009.
            - The collapse of major financial institutions, the bursting of the U.S. housing bubble, and the resulting
            credit crisis led to a severe global recession. Stock markets around the world saw dramatic declines, 
            with the S&P 500 losing more than half its value from October 2007 to March 2009. Central banks and 
            governments worldwide implemented massive stimulus packages to prevent further economic collapse.
        */
        public const int PeriodIndex = 0;
        public override void Initialize()
        
        {
            /*************** Start Default Initialization *****************/
            // Set Dates (will be ignored in live mode)
            SetStartDate(TestPeriods[PeriodIndex].Item1);
            SetEndDate(TestPeriods[PeriodIndex].Item2);

            // Set Account Currency
            // - Default is USD $100,000
            // - Must be done before SetCash()
            // SetAccountCurrency("USD");
            // SetAccountCurrency("BTC", 10);
            //
            // Question: How to set multiple currencies? For example, if you want to use a mix of USD and BTC
            // Answer: No, you cannot set multiple account currency and you can only set it once. Its internal valule is used for all calculations.
            // Reference: https://www.quantconnect.com/docs/v2/writing-algorithms/portfolio/cashbook#02-Account-Currency

            // Set Cash
            // SetCash("BTC", 10);
            SetCash(InitialCash);
            /***************** End Default Initialization *****************/

            // Docs:
            // Universe.Members: When you remove an asset from a universe, LEAN usually removes the security from the Members collection and removes the security subscription.
            // ActiveSecurities: When you remove an asset from a universe, LEAN usually removes the security from the ActiveSecurities collection and removes the security subscription.
            //
            // Question: What's the differences between `Universe.Members` and `Universe.ActiveSecurities`?
            // Answer: `ActiveSecurities` is a collection of all `Members` from all universes.
            //
            // UniverseManager[_universe.Configuration.Symbol].Members:
            // FIXME: Multiple universes are allowed? But the members are only for certain unvierse, but the active securities are for all universes
            // FIXME: what is the Symbol of a universe? Where is it defined?
            // Note: both `UniverseManager` and `ActiveSecurities` are properties of the `QCAlgorithm` class
            // To have access to all securities without considering they are activthee or not, use `Securities` property
            // - There are still cases where the Securities may remove the security, but only from the primary collection (Securities.Values), and can still be accessed from Securities.Total
            //
            // Universe.Selected: Different from Members, Members may contain more assets
            // Diffs Remarks:
            //   This set might be different than QuantConnect.Data.UniverseSelection.Universe.Securities
            //   which might hold members that are no longer selected but have not been removed
            //   yet, this can be because they have some open position, orders, haven't completed
            //   the minimum time in universe

            /***************** Start Algorithm Framework ******************/
            // Set Universe Settings
            UniverseSettings.Resolution = Resolution.Daily;
            // UniverseSettings.DataNormalizationMode = DataNormalizationMode.Raw; // allow options
            UniverseSettings.Schedule.On(DateRules.MonthStart());
            // UniverseSettings.Asynchronous = true; // This would cause backtest consistency issues, see: https://www.quantconnect.com/docs/v2/writing-algorithms/algorithm-framework/universe-selection/universe-settings#09-Asynchronous-Selection
            // UniverseSettings.ExtendedMarketHours = true; // only set to true if you are performing intraday trading
            // AddUniverseSelection(new FundamentalUniverseSelectionModel(Select, UniverseSettings));

            // Set Security Initializer
            // - This allow any custom security-level settings, instead of using the global universe settings
            // - SetSecurityInitializer(security => security.SetFeeModel(new ConstantFeeModel(0, "USD")));
            // - SetSecurityInitializer(new MySecurityInitializer(BrokerageModel, new FuncSecuritySeeder(GetLastKnownPrices)));
            SetSecurityInitializer(new BrokerageModelSecurityInitializer(
                this.BrokerageModel, new FuncSecuritySeeder(this.GetLastKnownPrices)
            ));
            // Set Selection
            AddUniverseSelection(new BasicUniverseSelectionModel());
            // var _optionFilter = new Func<OptionFilterUniverse, OptionFilterUniverse>(universe =>
            // {
            //     return universe
            //         .Strikes(-10, -10)
            //         .Expiration(TimeSpan.FromDays(45), TimeSpan.FromDays(60));
            // });
            // AddUniverseSelection(new DerivedOptionsUniverseSelectionModel(
            //     new BasicUniverseSelectionModel(), _optionFilter
            // )); // Don't need to add the basic universe selection model in advance since it is handled internally
            // Set Alphas
            AddAlpha(new TempAlphaModel());
            // Set Portfolio
            Settings.RebalancePortfolioOnInsightChanges = false;
            Settings.RebalancePortfolioOnSecurityChanges = false;
            SetPortfolioConstruction(new MomentumPortfolioConstructionModel());
            // Set Risk 
            // Set Execution
            SetExecution(new ImmediateExecutionModel());
            /****************** End Algorithm Framework *******************/

            // Set Warmup Period
            // SetWarmUp(PLookback/2, Resolution.Daily);
        }
        public override void OnWarmupFinished()
        {
            // OnWarmupFinished() is the last method called before the algorithm starts running
            // - You can notify yourself by overriding this method: public override void OnWarmupFinished() { Log("Algorithm Ready"); }
            // - You can train machine learning models here: public override void OnWarmupFinished() { Train(MyTrainingMethod); }
            // The OnWarmupFinished() will be called after the warmup period even if the warmup period is not set
            Log("Algorithm Ready");
            // show universities
            // foreach (var universe in UniverseManager.Values)
            // {
            //     Log($"Init Universe: {universe.Configuration.Symbol}: {universe.Members.Count} members");
            // }
            // PostInitialize() method should never be overridden because it is used for predefined post-initialization routines
        }

        // public override void OnData(Slice slice)
        // {
        //     Log($"OnData: {Time}, {slice.Keys.Count} symbols, {slice.Bars.Count} bars");
        //     The suggested way of handling the time-based event is using the Scheduled Events 
        //     instead of checking the time in the OnData method
        //     Source: https://www.quantconnect.com/docs/v2/writing-algorithms/scheduled-events
        //
        //     Scheduled Events let you trigger code to run at specific times of day, regardless of
        //     your algorithm's data subscriptions. It's easier and more reliable to execute
        //     time-based events with Scheduled Events than checking the current algorithm time in
        //     the OnData event handler.
        // }
    }
}
#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.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.Interfaces;
    using QuantConnect.Securities.Volatility;
    using QuantConnect.Storage;
    using QuantConnect.Statistics;
    using QCAlgorithmFramework = QuantConnect.Algorithm.QCAlgorithm;
    using QCAlgorithmFrameworkBridge = QuantConnect.Algorithm.QCAlgorithm;
using Accord.Math.Optimization;
using Accord.Statistics;
using Accord.Math;
#endregion

namespace QuantConnect {
   /// <summary>
    /// A Quadratic Programming-based portfolio optimizer that minimizes risk (variance)
    /// while ensuring weights sum to 1 and are bounded between 0 and 1.
    /// </summary>
    public class QuadraticProgrammingPortfolioOptimizer : IPortfolioOptimizer
    {
        private readonly int _shortLookback;

        /// <summary>
        /// Initializes a new instance of <see cref="QuadraticProgrammingPortfolioOptimizer"/>
        /// </summary>
        /// <param name="shortLookback">The number of days to use for the lookback period</param>
        public QuadraticProgrammingPortfolioOptimizer(int shortLookback = 63)
        {
            _shortLookback = shortLookback;
        }

        /// <summary>
        /// Perform portfolio optimization using Quadratic Programming
        /// </summary>
        /// <param name="historicalReturns">Matrix of historical returns (K x N)</param>
        /// <param name="expectedReturns">Array of expected returns (K x 1)</param>
        /// <param name="covariance">Covariance matrix (K x K)</param>
        /// <returns>Array of portfolio weights (K x 1)</returns>
        public double[] Optimize(double[,] historicalReturns, double[] expectedReturns = null, double[,] covariance = null)
        {
            var nAssets = historicalReturns.GetLength(1);

            // Calculate the covariance matrix if not provided
            covariance ??= historicalReturns.Covariance();
            
            // Calculate average returns if not provided
            var meanReturns = expectedReturns ?? historicalReturns.Mean(0);

            // Step 1: Setup QP Problem to Minimize Portfolio Variance
            var optfunc = new QuadraticObjectiveFunction(covariance, Accord.Math.Vector.Create(nAssets, 0.0));

            // Constraints: weights sum to 1 (budget constraint)
            var constraints = new List<LinearConstraint>
            {
                new LinearConstraint(nAssets)
                {
                    CombinedAs = Accord.Math.Vector.Create(nAssets, 1.0),
                    ShouldBe = ConstraintType.EqualTo,
                    Value = 1.0
                }
            };

            // Boundary conditions: Weights should be between 0 and 1 (no short selling)
            for (int i = 0; i < nAssets; i++)
            {
                constraints.Add(new LinearConstraint(1)
                {
                    VariablesAtIndices = new[] { i },
                    ShouldBe = ConstraintType.GreaterThanOrEqualTo,
                    Value = 0.0 // Lower bound
                });
                constraints.Add(new LinearConstraint(1)
                {
                    VariablesAtIndices = new[] { i },
                    ShouldBe = ConstraintType.LesserThanOrEqualTo,
                    Value = 1.0 // Upper bound
                });
            }

            // Step 2: Solve the optimization problem using Goldfarb-Idnani QP solver
            var solver = new GoldfarbIdnani(optfunc, constraints);
            var x0 = Accord.Math.Vector.Create(nAssets, 1.0 / nAssets); // Starting with equal weights
            var success = solver.Minimize(Accord.Math.Vector.Copy(x0));

            if (!success)
            {
                Console.WriteLine("[OptimizePortfolio] Quadratic programming optimization failed. Returning equal weights.");
                return Enumerable.Repeat(1.0 / nAssets, nAssets).ToArray();
            }

            // Step 3: Extract the optimal solution
            var solution = solver.Solution
                .Select(x => x.IsNaNOrInfinity() ? 0 : x).ToArray();

            // Scale the solution to ensure that the sum of the weights is 1
            var sumOfWeights = solution.Sum();
            if (sumOfWeights.IsNaNOrZero())
            {
                Console.WriteLine("[OptimizePortfolio] Invalid solution from QP optimizer. Returning equal weights.");
                return Enumerable.Repeat(1.0 / nAssets, nAssets).ToArray();
            }

            // Return the scaled portfolio weights
            return solution.Divide(sumOfWeights);
        }
    } 
}
#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.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.Interfaces;
using QuantConnect.Securities.Volatility;
using QuantConnect.Storage;
using QuantConnect.Statistics;
using QCAlgorithmFramework = QuantConnect.Algorithm.QCAlgorithm;
using QCAlgorithmFrameworkBridge = QuantConnect.Algorithm.QCAlgorithm;
using Accord.Statistics;
using Accord.Math.Optimization;
using Accord.Math;
#endregion

namespace QuantConnect
{
    public class SOCPortfolioOptimizer : IPortfolioOptimizer
    {
        private readonly double _riskFreeRate;

        public SOCPortfolioOptimizer(double riskFreeRate = 0.01)
        {
            _riskFreeRate = riskFreeRate;
        }

        /// <summary>
        /// Optimize portfolio weights using Second-Order Cone Programming (SOCP)
        /// </summary>
        /// <param name="historicalReturns">Matrix of historical returns (K x N)</param>
        /// <param name="expectedReturns">Array of expected returns</param>
        /// <param name="covariance">Covariance matrix (K x K)</param>
        /// <returns>Array of portfolio weights (K x 1)</returns>
        public double[] Optimize(double[,] historicalReturns, double[] expectedReturns = null, double[,] covariance = null)
        {
            covariance ??= historicalReturns.Covariance();
            var size = covariance.GetLength(0);
            var returns = expectedReturns ?? historicalReturns.Mean(0);

            // Set up constraints: sum of weights equals 1
            var budgetConstraint = new LinearConstraint(size)
            {
                CombinedAs = Accord.Math.Vector.Create(size, 1.0),
                ShouldBe = ConstraintType.EqualTo,
                Value = 1.0
            };

            // Create SOCP optimization problem
            var optfunc = new QuadraticObjectiveFunction(covariance, Vector.Create(size, 0.0));
            var constraints = new List<LinearConstraint> { budgetConstraint };

            // Set up the solver
            var solver = new GoldfarbIdnani(optfunc, constraints);

            // Initial guess (equal weights)
            var x0 = Accord.Math.Vector.Create(size, 1.0 / size);

            // Minimize downside risk (SOCP formulation)
            var success = solver.Minimize(Accord.Math.Vector.Copy(x0));
            if (!success) return x0;

            // Ensure valid solution (replace NaN/Infinity with zeros)
            var solution = solver.Solution.Select(x => x.IsNaNOrInfinity() ? 0 : x).ToArray();

            // Scale the solution to ensure sum of absolute weights equals 1
            var sumOfAbsoluteWeights = solution.Abs().Sum();
            return sumOfAbsoluteWeights.IsNaNOrZero() ? x0 : solution.Divide(sumOfAbsoluteWeights);
        }
    }
}
#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.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.Interfaces;
using QuantConnect.Securities.Volatility;
using QuantConnect.Storage;
using QuantConnect.Statistics;
using QCAlgorithmFramework = QuantConnect.Algorithm.QCAlgorithm;
using QCAlgorithmFrameworkBridge = QuantConnect.Algorithm.QCAlgorithm;
using Accord.Math;
using MathNet.Numerics.LinearAlgebra;
#endregion
namespace QuantConnect
{
    /// <summary>
    /// A Monte Carlo-based portfolio optimizer that searches for the portfolio with the best Sortino ratio
    /// </summary>
    public class SortinoEfficientFrontierOptimizer : IPortfolioOptimizer
    {
        private readonly int _numPortfolios;
        private readonly int _lookback;
        private readonly int _randomSeed;

        /// <summary>
        /// Initializes a new instance of <see cref="SortinoEfficientFrontierOptimizer"/>
        /// </summary>
        /// <param name="numPortfolios">Number of portfolios to generate in the Monte Carlo simulation</param>
        /// <param name="lookback">Lookback period for historical returns</param>
        /// <param name="randomSeed">Seed for the random number generator</param>
        public SortinoEfficientFrontierOptimizer(int numPortfolios = 1000, int lookback = 63, int randomSeed = 11)
        {
            _numPortfolios = numPortfolios;
            _lookback = lookback;
            _randomSeed = randomSeed;
        }

        /// <summary>
        /// Perform portfolio optimization using Monte Carlo simulation
        /// </summary>
        /// <param name="historicalReturns">Matrix of historical returns (rows: observations, columns: assets)</param>
        /// <param name="expectedReturns">Array of expected returns (not used in this optimizer)</param>
        /// <param name="covariance">Covariance matrix (not used here as we compute it internally)</param>
        /// <returns>Array of portfolio weights</returns>
        public double[] Optimize(double[,] historicalReturns, double[] expectedReturns = null, double[,] covariance = null)
        {
            int nObservations = historicalReturns.GetLength(0); // Number of time periods
            int nAssets = historicalReturns.GetLength(1);       // Number of assets

            if (nAssets == 0)
            {
                Console.WriteLine("[OptimizePortfolio] No assets provided. Returning empty weights.");
                return new double[0];
            }

            // Convert historicalReturns to a matrix for easier computations
            var returnsMatrix = Matrix<double>.Build.DenseOfArray(historicalReturns).Transpose();
            // Now, returnsMatrix rows correspond to assets, and columns correspond to observations

            // Calculate mean returns for each asset
            var meanReturns = new double[nAssets];
            for (int i = 0; i < nAssets; i++)
            {
                meanReturns[i] = returnsMatrix.Row(i).Average();
            }

            // Calculate covariance matrix (assets x assets)
            var covarianceMatrix = returnsMatrix * returnsMatrix.Transpose() / (nObservations - 1);

            var portfolioReturns = new double[_numPortfolios];
            var sortinoRatios = new double[_numPortfolios];
            var weightsRecord = new List<double[]>(_numPortfolios);

            var random = new Random(_randomSeed);

            for (int i = 0; i < _numPortfolios; i++)
            {
                // Generate random weights and normalize
                var weights = new double[nAssets];
                double sumWeights = 0.0;
                for (int j = 0; j < nAssets; j++)
                {
                    double w = random.NextDouble();
                    weights[j] = w;
                    sumWeights += w;
                }
                for (int j = 0; j < nAssets; j++)
                {
                    weights[j] /= sumWeights;
                }

                var weightsVector = Vector<double>.Build.DenseOfArray(weights);

                // Calculate portfolio return (scaled by lookback period)
                double portfolioReturn = 0.0;
                for (int j = 0; j < nAssets; j++)
                {
                    portfolioReturn += meanReturns[j] * weights[j];
                }
                portfolioReturn *= _lookback;

                // Calculate downside risk (downside standard deviation)
                double downsideSum = 0.0;
                for (int j = 0; j < nAssets; j++)
                {
                    var assetReturns = returnsMatrix.Row(j);
                    foreach (var r in assetReturns)
                    {
                        if (r < 0)
                        {
                            downsideSum += Math.Pow(r, 2) * weights[j];
                        }
                    }
                }
                double downsideStdDev = Math.Sqrt(downsideSum / nObservations);

                // Calculate Sortino ratio
                double sortinoRatio = downsideStdDev > 0 ? portfolioReturn / downsideStdDev : 0;

                // Store results
                portfolioReturns[i] = portfolioReturn;
                sortinoRatios[i] = sortinoRatio;
                weightsRecord.Add(weights);
            }

            // Select the portfolio with the highest Sortino ratio
            int bestSortinoIndex = Array.IndexOf(sortinoRatios, sortinoRatios.Max());

            if (bestSortinoIndex < 0 || bestSortinoIndex >= weightsRecord.Count)
            {
                Console.WriteLine("[OptimizePortfolio] Unable to determine the best Sortino index. Returning equal weights.");
                var equalWeights = Enumerable.Repeat(1.0 / nAssets, nAssets).ToArray();
                return equalWeights;
            }

            return weightsRecord[bestSortinoIndex];
        }
    }
}
#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.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.Interfaces;
using QuantConnect.Securities.Volatility;
using QuantConnect.Storage;
using QuantConnect.Statistics;
using QCAlgorithmFramework = QuantConnect.Algorithm.QCAlgorithm;
using QCAlgorithmFrameworkBridge = QuantConnect.Algorithm.QCAlgorithm;
using Ionic.Zip;
using QuantConnect.Algorithm.Framework.Alphas.Analysis;
using Accord;
using QLNet;
#endregion

namespace QuantConnect
{
    public class MomentumPortfolioConstructionModel : PortfolioConstructionModel
    {
        // public constants
        public const string DateFormat = "yyyy-MM-dd HH:mm:ss";
        public const decimal NearZero = 1e-6m;
        public const decimal NearZeroPct = 1e-4m;
        public const int PLookback = 252;
        public const int PShortLookback = 63;
        public const int PNumLong = 5;
        public const decimal PAdjustmentStep = 1.0m;
        public const int PNPortfolios = 1000;
        public const int PRandSeed = 97; // 97, 18, 23, 
        // readonly properties
        private readonly int _lookback;
        private readonly int _shortLookback;
        private readonly int _numLong;
        private readonly decimal _adjustmentStep;
        private readonly Dictionary<Symbol, MomentumPercent> _momp;
        private readonly IPortfolioOptimizer _optimizer;
        // properties
        private DateTime _lastRebalanceTime;
        private HashSet<Symbol> _currentHoldings;
        private Dictionary<Symbol, decimal> _targetWeights;
        public MomentumPortfolioConstructionModel(int lookback = PLookback, int shortLookback = PShortLookback, int numLong = PNumLong, decimal adjustmentStep = PAdjustmentStep, int numPortfolios = PNPortfolios, int randSeed = PRandSeed)
        {
            // Constructor arguments
            _lookback = lookback;
            _shortLookback = shortLookback;
            _numLong = numLong;
            _adjustmentStep = adjustmentStep;

            // Readonly properties
            _momp = new Dictionary<Symbol, MomentumPercent>();

            // Choose a portfolio optimizer: [5 years] awesome (>= 300), good (>= 200), medium (>= 100), ordinary (< 100)
            _optimizer = new SortinoEfficientFrontierOptimizer(numPortfolios, shortLookback, randSeed); // awesome

            // _optimizer = new QuadraticProgrammingPortfolioOptimizer(shortLookback); // medium
            // _optimizer = new SOCPortfolioOptimizer(); // ordinary
            // _optimizer = new MaximumSharpeRatioPortfolioOptimizer(0, 1, 0.01); // medium
            // _optimizer = new MinimumVariancePortfolioOptimizer(); // good
            // _optimizer = new UnconstrainedMeanVariancePortfolioOptimizer(); // medium, but very unstable
            // _optimizer = new RiskParityPortfolioOptimizer(); // good

            // Other properties
            _currentHoldings = new HashSet<Symbol>();
            _targetWeights = new Dictionary<Symbol, decimal>();
        }
        // Create list of PortfolioTarget objects from Insights.
        public override List<PortfolioTarget> CreateTargets(QCAlgorithm algorithm, Insight[] insights)
        {
            foreach (var kvp in this._momp)
            {
                kvp.Value.Update(algorithm.Time, algorithm.Securities[kvp.Key].Close);
            }

            var targets = new List<IPortfolioTarget>();

            if (!IsRebalanceDue(algorithm.UtcTime))
            {
                return targets.Cast<PortfolioTarget>().ToList();
            }
            var sortedMom = (from kvp in this._momp
                             where kvp.Value.IsReady
                             orderby kvp.Value.Current.Value descending
                             select kvp.Key).ToList();
            var selected = sortedMom.Take(this._numLong).ToList();
            var newHoldings = new HashSet<Symbol>(selected);

            if (!newHoldings.SetEquals(this._currentHoldings) || this._lastRebalanceTime == algorithm.UtcTime)
            {
                if (selected.Count > 0)
                {
                    var optimalWeights = OptimizePortfolio(algorithm, selected);
                    this._targetWeights = selected.Zip(optimalWeights, (k, v) => new { k, v }).ToDictionary(x => x.k, x => x.v);
                    this._currentHoldings = newHoldings;
                    AdjustPortfolio(algorithm);
                }
            }

            foreach (var kvp in this._targetWeights)
            {
                var symbol = kvp.Key;
                var weight = kvp.Value;
                var quantity = algorithm.CalculateOrderQuantity(symbol, weight);
                var currentQuantity = algorithm.Portfolio[symbol].Quantity; 
                var targetQuantity = quantity + currentQuantity;
                var target = new PortfolioTarget(symbol, targetQuantity);
                targets.Add(target);
            }
            return targets.Cast<PortfolioTarget>().ToList();
        }
        private bool IsRebalanceDue(DateTime algorithmUtc)
        {
            if (_lastRebalanceTime == default(DateTime))
            {
                _lastRebalanceTime = algorithmUtc;
                return true;
            }
            if (algorithmUtc.Month != _lastRebalanceTime.Month)
            {
                _lastRebalanceTime = algorithmUtc;
                return true;
            }
            return false;
        }

        // Determine if the portfolio should rebalance based on the provided rebalancing function.
        // protected override bool IsRebalanceDue(Insight[] insights, DateTime algorithmUtc)
        // {
        //     return base.IsRebalanceDue(insights, algorithmUtc);
        // }

        // Determine the target percent for each insight.
        // protected override Dictionary<Insight, double> DetermineTargetPercent(List<Insight> activeInsights)
        // {
        //     return new Dictionary<Insight, double>();
        // }

        // Get the target insights to calculate a portfolio target percent. They will be piped to DetermineTargetPercent().
        // protected override List<Insight> GetTargetInsights()
        // {
        //     return Algorithm.Insights.GetActiveInsights(Algorithm.UtcTime).ToList();
        // }

        // Determine if the portfolio construction model should create a target for this insight.
        // protected override bool ShouldCreateTargetForInsight(Insight insight)
        // {
        //     return base.ShouldCreateTargetForInsight(insight);
        // }

        // Security change details.
        public override void OnSecuritiesChanged(QCAlgorithm algorithm, SecurityChanges changes)
        {
            // base.OnSecuritiesChanged(algorithm, changes);
            foreach (var symbol in changes.RemovedSecurities.Select(security => security.Symbol))
            {
                if (this._momp.Remove(symbol, out var _))
                {
                    algorithm.Liquidate(symbol, "Removed from universe");
                }
            }

            foreach (var symbol in changes.AddedSecurities.Select(security => security.Symbol))
            {
                if (!this._momp.ContainsKey(symbol) && symbol.SecurityType == SecurityType.Equity)
                {
                    this._momp[symbol] = new MomentumPercent(this._lookback);
                }else {
                    if (symbol.SecurityType != SecurityType.Equity)
                    {
                        algorithm.Log($"[OnSecuritiesChanged] {symbol.Value} is not an equity security. Skipping.");
                    }
                }
            }
            var addedSymbols = (from kvp in this._momp
                                where !kvp.Value.IsReady
                                select kvp.Key).ToList();
            var history = algorithm.History(addedSymbols, 1 + this._lookback, Resolution.Daily); // iterable of slices
            foreach (var symbol in addedSymbols)
            {
                var symbolHistory = history.Where(slice => slice.Bars.ContainsKey(symbol));
                foreach (var slice in symbolHistory)
                {
                    var time = slice.Time;
                    var value = slice.Bars[symbol].Close;
                    var item = new IndicatorDataPoint(symbol, time, value);
                    this._momp[symbol].Update(item);
                }
            }

            // Log the changes
            var currentDate = algorithm.Time.ToString(DateFormat);
            foreach (var universe in algorithm.UniverseManager.Values)
            {
                algorithm.Log($"{currentDate}: Updated Universe: {universe.Configuration.Symbol}: {universe.Members.Count} members");
            }
            var addedStr = string.Join(", ", changes.AddedSecurities.Select(security => security.Symbol.Value));
            var removedStr = string.Join(", ", changes.RemovedSecurities.Select(security => security.Symbol.Value));
            algorithm.Log($"{currentDate}: Security Changes: (+{changes.AddedSecurities.Count})[{addedStr}], (-{changes.RemovedSecurities.Count})[{removedStr}]");
        }
        // Customized helper methods
        public void AdjustPortfolio(QCAlgorithm algorithm)
        {
            var currentSymbols = algorithm.Portfolio.Keys.ToHashSet();
            var targetSymbols = this._targetWeights.Keys.ToHashSet();

            var removedSymbols = currentSymbols.Except(targetSymbols);
            foreach (var symbol in removedSymbols)
            {
                algorithm.Liquidate(symbol);
            }

            foreach (var kvp in this._targetWeights)
            {
                var symbol = kvp.Key;
                var targetWeight = kvp.Value;
                var currentWeight = algorithm.Portfolio[symbol].Quantity / algorithm.Portfolio.TotalPortfolioValue;
                if (!algorithm.Portfolio.ContainsKey(symbol))
                {
                    currentWeight = 0;
                }
                var adjustedWeight = currentWeight * (1 - this._adjustmentStep) + targetWeight * this._adjustmentStep;
                algorithm.SetHoldings(symbol, adjustedWeight);
            }

            var holdings = new Dictionary<string, decimal>();
            var sumOfAllHoldings = 0m;
            foreach (var symbol in algorithm.Portfolio.Keys)
            {
                var holdingPercentage = algorithm.Portfolio[symbol].HoldingsValue / algorithm.Portfolio.TotalPortfolioValue * 100;
                if (holdingPercentage.IsGreaterThan(NearZeroPct))
                {
                    sumOfAllHoldings += holdingPercentage;
                    holdings[symbol.Value] = Math.Round(holdingPercentage, 2);
                }
            }
            var currentDate = algorithm.Time.ToString(DateFormat);
            var targetedWeightsStr = string.Join(", ", this._targetWeights.OrderByDescending(kvp => kvp.Value).Select(kvp => $"{kvp.Key.Value}: {kvp.Value * 100:F2}%"));
            algorithm.Log($"{currentDate}: Targeted Holdings: [{targetedWeightsStr}]");
            var holdingsStr = string.Join(", ", holdings.OrderByDescending(kvp => kvp.Value).Select(kvp => $"{kvp.Key}: {kvp.Value:F2}%"));
            algorithm.Log($"{currentDate}: Holdings[{sumOfAllHoldings:F2}%]: [{holdingsStr}]");
        }
        public List<decimal> OptimizePortfolio(QCAlgorithm algorithm, List<Symbol> selectedSymbols)
        {
            // historical returns matrix and symbols
            var (historicalReturns, validSymbols) = GetHistoricalReturnsMatrix(algorithm, selectedSymbols);

            int nAssets = validSymbols.Count;
            if (nAssets == 0 || historicalReturns.GetLength(0) == 0)
            {
                algorithm.Log("[OptimizePortfolio] No valid symbols with sufficient historical data. Returning empty weights.");
                return new List<decimal>();
            }

            // Portfolio Optimizers: [5 years] awesome (>= 300), good (>= 200), medium (>= 100), ordinary (< 100)
            var optimizer = this._optimizer;
            var optimizedWeights = optimizer.Optimize(historicalReturns);

            // logging
            var symbolWeights = new Dictionary<Symbol, decimal>();
            for (int i = 0; i < nAssets; i++)
            {
                symbolWeights[validSymbols[i]] = (decimal)optimizedWeights[i];
            }
            var weightsStr = string.Join(", ", symbolWeights.OrderByDescending(kvp => kvp.Value).Select(kvp => $"{kvp.Key.Value}: {kvp.Value * 100:F2}%"));
            algorithm.Log($"[OptimizePortfolio] Optimized Weights: {weightsStr}");
            return optimizedWeights.Select(w => (decimal)w).ToList();
        }

        private (double[,] historicalReturns, List<Symbol> validSymbols) GetHistoricalReturnsMatrix(QCAlgorithm algorithm, List<Symbol> selectedSymbols)
        {
            var shortLookback = this._shortLookback;

            var historySlices = algorithm.History(selectedSymbols, shortLookback, Resolution.Daily);

            var history = historySlices
                .SelectMany(slice => slice.Bars)
                .GroupBy(bar => bar.Key)
                .ToDictionary(
                    group => group.Key,
                    group => group.Select(g => (double)g.Value.Close).ToList()
                );

            var validSymbols = new List<Symbol>();
            var returnsList = new List<List<double>>();

            foreach (var symbol in selectedSymbols)
            {
                if (!history.ContainsKey(symbol))
                {
                    algorithm.Log($"[GetHistoricalReturnsMatrix] Missing historical data for {symbol.Value}. Skipping this symbol.");
                    continue;
                }

                var closePrices = history[symbol];
                if (closePrices.Count < shortLookback)
                {
                    algorithm.Log($"[GetHistoricalReturnsMatrix] Insufficient historical data for {symbol.Value}. Required: {shortLookback}, Available: {closePrices.Count}. Skipping this symbol.");
                    continue;
                }

                var returns = closePrices
                    .Select((price, index) => index == 0 ? 0.0 : (price - closePrices[index - 1]) / closePrices[index - 1])
                    .Skip(1) // Skip the first return as it's zero
                    .ToList();

                validSymbols.Add(symbol);
                returnsList.Add(returns);
            }

            int nAssets = validSymbols.Count;

            if (nAssets == 0)
            {
                algorithm.Log("[GetHistoricalReturnsMatrix] No valid symbols with sufficient historical data. Returning empty matrix.");
                return (new double[0, 0], validSymbols);
            }

            int nObservations = returnsList[0].Count; // Assuming all assets have the same number of observations
            var historicalReturns = new double[nObservations, nAssets];

            for (int i = 0; i < nAssets; i++)
            {
                var returns = returnsList[i];
                for (int j = 0; j < nObservations; j++)
                {
                    historicalReturns[j, i] = returns[j];
                }
            }

            return (historicalReturns, validSymbols);
        }
    }
}
#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.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.Interfaces;
using QuantConnect.Securities.Volatility;
using QuantConnect.Storage;
using QuantConnect.Statistics;
using QCAlgorithmFramework = QuantConnect.Algorithm.QCAlgorithm;
using QCAlgorithmFrameworkBridge = QuantConnect.Algorithm.QCAlgorithm;
using Accord.MachineLearning;
using QuantConnect.Algorithm.CSharp;
#endregion

namespace QuantConnect
{

    public class BasicUniverseSelectionModel : FundamentalUniverseSelectionModel
    {
        public const int PNumCoarse = 200;
        public const int PNumFine = 70;

        private int _numCoarse;
        private int _numFine;
        public BasicUniverseSelectionModel(int numCoarse = PNumCoarse, int numFine = PNumFine)
        {
            this._numCoarse = numCoarse;
            this._numFine = numFine;
        }
        public override IEnumerable<Symbol> Select(QCAlgorithmFramework algorithm, IEnumerable<Fundamental> fundamental)
        {
            var selected = fundamental;
            // coarse selection
            selected = selected
                .Where(f => f.HasFundamentalData && f.Price > 5)
                .OrderByDescending(f => f.DollarVolume)
                .Take(this._numCoarse);
            // fine selection
            selected = selected
                .OrderByDescending(f => f.MarketCap)
                .Take(this._numFine);

            return selected.Select(f => f.Symbol);
        }
    }
}
#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.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.Interfaces;
using QuantConnect.Securities.Volatility;
using QuantConnect.Storage;
using QuantConnect.Statistics;
using QCAlgorithmFramework = QuantConnect.Algorithm.QCAlgorithm;
using QCAlgorithmFrameworkBridge = QuantConnect.Algorithm.QCAlgorithm;
#endregion


namespace QuantConnect
{
    /// <summary>
    /// This universe selection model will chain to the security changes of a given <see cref="UniverseSelectionModel"/> selection
    /// output and create a new <see cref="OptionChainUniverse"/> for each of them
    /// 
    /// This class is based on https://www.quantconnect.com/docs/v2/writing-algorithms/algorithm-framework/universe-selection/options-universes#61-Option-Chained-Universe-Selection
    /// instead of attaching to a Universe, it attaches to a UniverseSelectionModel
    /// </summary>
    public class DerivedOptionsUniverseSelectionModel : UniverseSelectionModel
    {
        private DateTime _nextRefreshTimeUtc;
        private readonly UniverseSelectionModel _universeSelectionModel;
        private readonly Func<OptionFilterUniverse, OptionFilterUniverse> _optionFilter;
        private readonly Dictionary<Universe, IEnumerable<Symbol>> _fundamentalUniverses;

        /// <summary>
        /// Gets the next time the framework should invoke the `CreateUniverses` method to refresh the set of universes.
        /// </summary>
        public override DateTime GetNextRefreshTimeUtc()
        {
            var parentRefreshTime = _universeSelectionModel.GetNextRefreshTimeUtc();
            if (parentRefreshTime <= _nextRefreshTimeUtc)
            {
                _fundamentalUniverses.Clear();
                _nextRefreshTimeUtc = parentRefreshTime;
            }
            return _nextRefreshTimeUtc;
        }

        /// <summary>
        /// Creates a new instance of <see cref="DerivedOptionsUniverseSelectionModel"/>
        /// </summary>
        /// <param name="universeSelectionModel">The universe selection model we want to chain to</param>
        /// <param name="optionFilter">The option filter universe to use</param>
        public DerivedOptionsUniverseSelectionModel(UniverseSelectionModel universeSelectionModel, Func<OptionFilterUniverse, OptionFilterUniverse> optionFilter = null)
        {
            _nextRefreshTimeUtc = DateTime.MaxValue;
            _universeSelectionModel = universeSelectionModel;
            _optionFilter = optionFilter;
            _fundamentalUniverses = new Dictionary<Universe, IEnumerable<Symbol>>();
        }

        /// <summary>
        /// Creates the universes for this algorithm. Called when the original universeSelectionModel
        /// or when the symbols it contains change
        /// </summary>
        /// <param name="algorithm">The algorithm instance to create universes for</param>
        /// <returns>The universes to be used by the algorithm</returns>
        public override IEnumerable<Universe> CreateUniverses(QCAlgorithm algorithm)
        {
            _nextRefreshTimeUtc = DateTime.MaxValue;

            if (_fundamentalUniverses.Count <= 0)
            {
                var universes = _universeSelectionModel.CreateUniverses(algorithm);

                foreach (var universe in universes)
                {
                    _fundamentalUniverses.Add(universe, Enumerable.Empty<Symbol>());
                    universe.SelectionChanged += (sender, args) =>
                    {
                        // We must create the new option Symbol using the CreateOption(Symbol, ...) overload.
                        // Otherwise, we'll end up loading equity data for the selected Symbol, which won't
                        // work whenever we're loading options data for any non-equity underlying asset class.
                        _fundamentalUniverses[universe] = ((Universe.SelectionEventArgs)args).CurrentSelection
                            .Select(symbol => Symbol.CreateOption(
                                symbol,
                                symbol.ID.Market,
                                symbol.SecurityType.DefaultOptionStyle(),
                                default(OptionRight),
                                0m,
                                SecurityIdentifier.DefaultDate))
                            .ToList();

                        _nextRefreshTimeUtc = DateTime.MinValue;
                    };
                }
            }

            foreach (var kpv in _fundamentalUniverses)
            {
                yield return kpv.Key;

                foreach (var optionSymbol in kpv.Value)
                {
                    yield return algorithm.CreateOptionChain(optionSymbol, _optionFilter, kpv.Key.UniverseSettings);
                }
            }
        }
    }
}