Overall Statistics
Total Trades
4420
Average Win
0.82%
Average Loss
-0.64%
Compounding Annual Return
17.965%
Drawdown
30.400%
Expectancy
0.251
Net Profit
2947.865%
Sharpe Ratio
0.787
Probabilistic Sharpe Ratio
7.698%
Loss Rate
45%
Win Rate
55%
Profit-Loss Ratio
1.29
Alpha
0.09
Beta
0.495
Annual Standard Deviation
0.176
Annual Variance
0.031
Information Ratio
0.234
Tracking Error
0.177
Treynor Ratio
0.28
Total Fees
$19845.46
Estimated Strategy Capacity
$18000000.00
Lowest Capacity Asset
ON VZHCG4656OV9
#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.Execution;
    using QuantConnect.Algorithm.Framework.Risk;
    using QuantConnect.Parameters;
    using QuantConnect.Benchmarks;
    using QuantConnect.Brokerages;
    using QuantConnect.Util;
    using QuantConnect.Interfaces;
    using QuantConnect.Algorithm;
    using QuantConnect.Indicators;
    using QuantConnect.Data;
    using QuantConnect.Data.Consolidators;
    using QuantConnect.Data.Custom;
    using QuantConnect.DataSource;
    using QuantConnect.Data.Fundamental;
    using QuantConnect.Data.Market;
    using QuantConnect.Data.UniverseSelection;
    using QuantConnect.Notifications;
    using QuantConnect.Orders;
    using QuantConnect.Orders.Fees;
    using QuantConnect.Orders.Fills;
    using QuantConnect.Orders.Slippage;
    using QuantConnect.Scheduling;
    using QuantConnect.Securities;
    using QuantConnect.Securities.Equity;
    using QuantConnect.Securities.Future;
    using QuantConnect.Securities.Option;
    using QuantConnect.Securities.Forex;
    using QuantConnect.Securities.Crypto;
    using QuantConnect.Securities.Interfaces;
    using QuantConnect.Storage;
    using QuantConnect.Data.Custom.AlphaStreams;
    using QCAlgorithmFramework = QuantConnect.Algorithm.QCAlgorithm;
    using QCAlgorithmFrameworkBridge = QuantConnect.Algorithm.QCAlgorithm;
#endregion
using System.Collections.Generic;
using QuantConnect.Algorithm.Framework.Selection;
using QuantConnect.Data;
using QuantConnect.Data.UniverseSelection;
using QuantConnect.Indicators;
using System.Linq;
using QuantConnect.Data.Fundamental;
using System;
using QuantConnect.Data.Consolidators;
using QuantConnect.Data.Market;
using QuantConnect.Securities;

namespace QuantConnect.Algorithm.CSharp
{
    public class MomentumRotation : QCAlgorithm
    {
        const string BENCHMARK = "QQQ";
        private const int PORTFOLIO_NUM = 10;
        private const int NUM_NET_WEEKS = 5;
        private const int _numberOfSymbolsCoarse = 1000;
        private const int _numberOfSymbolsFine = 500;
        // rebalances at the start of each month
        private int _lastYear = -1;
        private readonly Dictionary<Symbol, decimal> _dollarVolumeBySymbol = new Dictionary<Symbol, decimal>();
        private Dictionary<Symbol, SymbolData> SymbolDict = new Dictionary<Symbol, SymbolData>();

        RollingWindow<int> mNetNewHighsLows = new RollingWindow<int>(NUM_NET_WEEKS);

        private int mNumWeeksInvested = 0;

        public override void Initialize()
        {
            /// SUBTRACT 5 WEEKS TO SETUP ROLLING WINDOW
            SetStartDate(2001, 12, 1); // Set Start Date
            SetEndDate(2022, 8, 1); // Set Start Date
            SetCash(50000); // Set Strategy Cash

            UniverseSettings.Resolution = Resolution.Daily;
            EnableAutomaticIndicatorWarmUp = true;

            var security = AddEquity(BENCHMARK, Resolution.Daily);

            SetBenchmark(BENCHMARK);

            // AddUniverseSelection(new QC500UniverseSelectionModel());

            AddUniverseSelection(new FineFundamentalUniverseSelectionModel(SelectCoarse, SelectNonFinancial));

            // AddUniverseSelection(new FineFundamentalUniverseSelectionModel(SelectCoarse, SelectFine));

            Schedule.On(Schedule.DateRules.WeekStart(BENCHMARK),
                Schedule.TimeRules.AfterMarketOpen(BENCHMARK, 1), ManagePortfolio);

            //Schedule.On(Schedule.DateRules.MonthStart(security.Symbol),
            //    Schedule.TimeRules.AfterMarketOpen(security.Symbol, 1), Monthly);

            //Schedule.On(Schedule.DateRules.MonthStart(BENCHMARK),
            //    Schedule.TimeRules.AfterMarketOpen(BENCHMARK, 1), StrengthCheck);
        }
        
        public void Weekly()
        {
            /// Look at Everything in Symbol Dict

            int mNumHighs = 0;
            int mNumLows = 0;
            foreach(var pair in SymbolDict)
            {
                SymbolData lSymbolData = pair.Value;

                /// New 52 Week High
                if(lSymbolData.YearHigh.PeriodsSinceMaximum <= 5)
                {
                    mNumHighs++;
                }
                else if(lSymbolData.YearLow.PeriodsSinceMinimum <= 5)
                {
                    mNumLows++;
                }
            }

            int lNetHighsLows = mNumHighs - mNumLows;

            /// Need 5 Weeks Of Results Before We Can Do Anything
            if(mNetNewHighsLows.Count != NUM_NET_WEEKS)
            {
                mNetNewHighsLows.Add(lNetHighsLows);
                return;
            }

            /// If 2 Weeks Under 0, Close Trades and set mNumWeeksInvested to 0
            if(mNetNewHighsLows[0] < 0 && mNetNewHighsLows[1] < 0)
            {
                Liquidate();
                mNumWeeksInvested = 0;
                //Log("Found Net Lows, Liquidating");
            }

            /// Ma Check
            if(ActiveSecurities[BENCHMARK].Close < SymbolDict[BENCHMARK].TwoHundredEma)
            {
                Liquidate();
                mNumWeeksInvested = 0;
                //Log("Under 200, Closing");
            }

            
            /// Else If 3 Weeks Over 0
            else if(mNetNewHighsLows[0] > 0 && mNetNewHighsLows[1] > 0 && mNetNewHighsLows[2] > 0)
            {
                /// Check If Invested
                if(Portfolio.Invested)
                {   
                    /// If So Check How Long
                    /// If Greater Than a Month(check mNumWeeksInvested), Liquidate(Set mNumWeeksInvested to 0) and Open New Trades(increase mNumWeeksInvested by 1)
                    if(mNumWeeksInvested >= 4)
                    {
                        //Log("Month Passed, Rebalancing");
                        Liquidate();
                        OpenTrades();
                        mNumWeeksInvested = 1;
                    }
                    else
                    {
                        mNumWeeksInvested++;
                    }
                }
                /// If Not, Open New Trades(increase mNumWeeksInvested by 1)
                else
                {
                    /// Ma Check
                    if(ActiveSecurities[BENCHMARK].Close > SymbolDict[BENCHMARK].FiftyEma)
                    {
                        OpenTrades();
                        mNumWeeksInvested++;
                        //Log("Opening Portfolio");
                    }
                }
            }

            /// Update Net Highs and Lows on Weekly Basis   

            mNetNewHighsLows.Add(lNetHighsLows);
        }

        public void StrengthCheck()
        {
            var marketCapLeaders = (from pair in SymbolDict
                where ActiveSecurities.ContainsKey(pair.Key)
                where ActiveSecurities[pair.Key].Fundamentals != null
                orderby ActiveSecurities[pair.Key].Fundamentals.MarketCap descending
                select pair.Key).Take(500).ToList();

            var strongest = (from symbol in marketCapLeaders
                orderby SymbolDict[symbol].threeMonth descending
                select symbol).Take(5).ToList();

            Log("======================");

            foreach(var symbol in strongest)
            {
                Log(symbol.Value + " - Gain = " + SymbolDict[symbol].threeMonth + "%");

            }
        }

        public void OpenTrades()
        {
            var marketCapLeaders = (from pair in SymbolDict
                where ActiveSecurities.ContainsKey(pair.Key)
                orderby ActiveSecurities[pair.Key].Fundamentals.MarketCap descending
                select pair.Key).Take(100).ToList();

            var symbols = (from lSymbol in marketCapLeaders
                orderby SymbolDict[lSymbol].AdjustedStrength descending
                select lSymbol).Take(PORTFOLIO_NUM).ToList();

            /*var symbols = (from symbolData in SymbolDict.Values
            orderby symbolData.AdjustedStrength descending
            select symbolData.Symbol).Take(PORTFOLIO_NUM).ToList();*/

            decimal positionSizing = 1 / (decimal)PORTFOLIO_NUM;

            foreach(var symbol in symbols)
            {
                SetHoldings(symbol, positionSizing);
            }
        }

        public void ManagePortfolio()
        {
            if(ActiveSecurities[BENCHMARK].Close < SymbolDict[BENCHMARK].OneHundredEma)
            {
                Liquidate();
                mNumWeeksInvested = 0;
                return;
            }

            mNumWeeksInvested++;

            if(mNumWeeksInvested == 4 || !Portfolio.Invested)
            {
                mNumWeeksInvested = 0;
                Monthly();
            }     
        }

        public void Monthly()
        {
        	Liquidate();

            var symbols = (from symbolData in SymbolDict.Values
            orderby symbolData.IbdRs descending
            select symbolData.Symbol).Take(PORTFOLIO_NUM).ToList();

            decimal positionSizing = 1 / (decimal)PORTFOLIO_NUM;

            foreach(var symbol in symbols)
            {
                SetHoldings(symbol, positionSizing);
            }
        }

        public override void OnSecuritiesChanged(SecurityChanges changes)
        {
            // if we have no changes, do nothing
            if (changes == SecurityChanges.None) return;

            foreach (var security in changes.RemovedSecurities)
            {
                if(SymbolDict.ContainsKey(security.Symbol))
                {
                    SymbolDict.Remove(security.Symbol);
                }
            }

            foreach (var security in changes.AddedSecurities)
            {
                security.SetLeverage(1);
                if(!SymbolDict.ContainsKey(security.Symbol) && security.IsTradable)
                {
                    SymbolDict.Add(security.Symbol, new SymbolData(security.Symbol, this));
                }
            }
        }

        /// <summary>
        /// Performs coarse selection for the QC500 constituents.
        /// The stocks must have fundamental data
        /// The stock must have positive previous-day close price
        /// The stock must have positive volume on the previous trading day
        /// </summary>
        IEnumerable<Symbol> SelectCoarse(IEnumerable<CoarseFundamental> coarse)
        {
            if (Time.Year == _lastYear)
            {
                return Universe.Unchanged;
            }

            Liquidate();

            var sortedByDollarVolume =
                (from x in coarse
                 where x.HasFundamentalData && x.Volume > 0 && x.Price > 0
                 orderby x.DollarVolume descending
                 select x).Take(_numberOfSymbolsCoarse).ToList();

            _dollarVolumeBySymbol.Clear();
            foreach (var x in sortedByDollarVolume)
            {
                _dollarVolumeBySymbol[x.Symbol] = x.DollarVolume;
            }

            // If no security has met the QC500 criteria, the universe is unchanged.
            // A new selection will be attempted on the next trading day as _lastMonth is not updated
            if (_dollarVolumeBySymbol.Count == 0)
            {
                return Universe.Unchanged;
            }

            return _dollarVolumeBySymbol.Keys;
        }

        /// <summary>
        /// Performs fine selection for the QC500 constituents
        /// The company's headquarter must in the U.S.
        /// The stock must be traded on either the NYSE or NASDAQ
        /// At least half a year since its initial public offering
        /// The stock's market cap must be greater than 500 million
        /// </summary>
        IEnumerable<Symbol> SelectFine(IEnumerable<FineFundamental> fine)
        {
            var filteredFine =
                (from x in fine
                 where x.CompanyReference.CountryId == "USA" &&
                       ( (x.CompanyReference.PrimaryExchangeID == "NAS") )  &&
                       (Time - x.SecurityReference.IPODate).Days > 360
                       orderby x.MarketCap descending
                 select x.Symbol).ToList();
                 // select x.Symbol).Take(100).ToList();

            // Update _lastMonth after all QC500 criteria checks passed
            _lastYear = Time.Year;

            return filteredFine;
        }

        IEnumerable<Symbol> SelectNonFinancial(IEnumerable<FineFundamental> fine)
        {
            var filteredFine =
                (from x in fine
                 where x.CompanyReference.CountryId == "USA" &&
                       ( (x.CompanyReference.PrimaryExchangeID == "NAS") )  &&
                       (Time - x.SecurityReference.IPODate).Days > 90
                       && x.AssetClassification.MorningstarSectorCode != MorningstarSectorCode.FinancialServices
                       orderby x.MarketCap descending
                 select x.Symbol).Take(100).ToList();

            // Update _lastMonth after all QC500 criteria checks passed
            _lastYear = Time.Year;

            return filteredFine;
        }
    }

    

    class SymbolData
    {
        public Symbol Symbol;
        public RateOfChangePercent oneMonth;
        public RateOfChangePercent threeMonth;
        public RateOfChangePercent sixMonth;
        public RateOfChangePercent nineMonth;
        public RateOfChangePercent twelveMonth;
        public ExponentialMovingAverage FiftyEma;
        public ExponentialMovingAverage OneHundredEma;
        public ExponentialMovingAverage TwoHundredEma;
        public Maximum YearHigh;
        public Minimum YearLow;
        public SymbolData(Symbol symbol, QCAlgorithm algorithm)
        {
            Symbol = symbol;
            oneMonth = algorithm.ROCP(symbol, 21, Resolution.Daily);
            threeMonth = algorithm.ROCP(symbol, 63, Resolution.Daily);
            sixMonth = algorithm.ROCP(symbol, 126, Resolution.Daily);
            nineMonth = algorithm.ROCP(symbol, 189, Resolution.Daily);
            twelveMonth = algorithm.ROCP(symbol, 252, Resolution.Daily);
            FiftyEma = algorithm.EMA(symbol, 63, Resolution.Daily);
            OneHundredEma = algorithm.EMA(symbol, 150, Resolution.Daily);
            TwoHundredEma = algorithm.EMA(symbol, 200, Resolution.Daily);

            YearHigh = algorithm.MAX(symbol, 252, Resolution.Daily, x => ((TradeBar)x).High);
            YearLow = algorithm.MIN(symbol, 252, Resolution.Daily, x => ((TradeBar)x).Low);
        }

        public decimal IbdRs
        {
            get
            {
                return (twelveMonth*0.2m) + nineMonth + sixMonth + threeMonth;
            }
        }

        public decimal AdjustedStrength
        {
            get 
            {
                return (sixMonth*0.4m) + (threeMonth*0.4m) + (oneMonth*0.2m);
            }
        }
    }
}