Overall Statistics
Total Trades
2029
Average Win
1.67%
Average Loss
-1.30%
Compounding Annual Return
19.876%
Drawdown
30.100%
Expectancy
0.272
Net Profit
3039.101%
Sharpe Ratio
0.801
Probabilistic Sharpe Ratio
9.178%
Loss Rate
44%
Win Rate
56%
Profit-Loss Ratio
1.29
Alpha
0.126
Beta
0.383
Annual Standard Deviation
0.193
Annual Variance
0.037
Information Ratio
0.364
Tracking Error
0.217
Treynor Ratio
0.404
Total Fees
$29318.52
Estimated Strategy Capacity
$3400000.00
Lowest Capacity Asset
LRCX R735QTJ8XC9X
#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, 1, 1); // Set Start Date
            SetEndDate(2020, 1, 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, SelectRussellProxy));

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

            AddUniverseSelection(new FineFundamentalUniverseSelectionModel(SelectCoarse, SelectMARKET));

            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].TwoHundredEma)
            {
                Liquidate();
                mNumWeeksInvested = 0;
                return;
            }

            mNumWeeksInvested++;

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

                CombinationMonthly();
            }     
        }

        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 void CombinationMonthly()
        {
        	Liquidate();

            /// Ndx

            var ndx100 = (from symbolData in SymbolDict.Values
            where ActiveSecurities.ContainsKey(symbolData.Symbol)
            where ActiveSecurities[symbolData.Symbol].Fundamentals.CompanyReference.PrimaryExchangeID == "NAS"
            && (Time - ActiveSecurities[symbolData.Symbol].Fundamentals.SecurityReference.IPODate).Days > 360
            && ActiveSecurities[symbolData.Symbol].Fundamentals.AssetClassification.MorningstarSectorCode != MorningstarSectorCode.FinancialServices
            orderby ActiveSecurities[symbolData.Symbol].Fundamentals.MarketCap descending
            select symbolData.Symbol).Take(100).ToList();

            var ndxStrongest = (from symbol in ndx100
            orderby SymbolDict[symbol].IbdRs descending
            select symbol).Take(PORTFOLIO_NUM / 2).ToList();

            decimal ndxSize = 1 / (decimal)(PORTFOLIO_NUM / 2);

            foreach(var symbol in ndxStrongest)
            {
                SetHoldings(symbol, ndxSize);
            }

            /// Russell

            var russell1000 = (from symbolData in SymbolDict.Values
            where ActiveSecurities.ContainsKey(symbolData.Symbol)
            where (ActiveSecurities[symbolData.Symbol].Fundamentals.CompanyReference.PrimaryExchangeID == "NAS" || ActiveSecurities[symbolData.Symbol].Fundamentals.CompanyReference.PrimaryExchangeID == "NYS")
            && (Time - ActiveSecurities[symbolData.Symbol].Fundamentals.SecurityReference.IPODate).Days > 360
            orderby ActiveSecurities[symbolData.Symbol].Fundamentals.MarketCap descending
            select symbolData.Symbol).Take(1000).ToList();

            var russellStrongest = (from symbol in russell1000
            orderby SymbolDict[symbol].IbdRs descending
            select symbol).Take(PORTFOLIO_NUM / 2).ToList();

            decimal rsSizing = 1 / (decimal)(PORTFOLIO_NUM / 2);

            foreach(var symbol in russellStrongest)
            {
                SetHoldings(symbol, rsSizing);
            }
        }

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

            var sortedByDollarVolume =
                (from x in coarse
                 where x.HasFundamentalData && x.Volume > 0 && x.Price > 0
                 select x.Symbol).ToList();

            return sortedByDollarVolume;
        }

        IEnumerable<Symbol> SelectRussellProxy(IEnumerable<FineFundamental> fine)
        {
            var filteredFine =
                (from x in fine
                 where x.CompanyReference.CountryId == "USA" &&
                       ( (x.CompanyReference.PrimaryExchangeID == "NAS") || (x.CompanyReference.PrimaryExchangeID == "NYS") )  &&
                       (Time - x.SecurityReference.IPODate).Days > 360
                       // && x.AssetClassification.MorningstarSectorCode != MorningstarSectorCode.FinancialServices
                 // where x.MarketCap > 300000000 && x.MarketCap < 2000000000
                       orderby x.MarketCap descending
                 select x.Symbol).Take(1000).ToList();

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

            return filteredFine;
        }

        IEnumerable<Symbol> SelectMARKET(IEnumerable<FineFundamental> fine)
        {
            var filteredFine =
                (from x in fine
                 where x.CompanyReference.CountryId == "USA" &&
                       ( (x.CompanyReference.PrimaryExchangeID == "NAS") || (x.CompanyReference.PrimaryExchangeID == "NYS") )  &&
                       (Time - x.SecurityReference.IPODate).Days > 360
                       // && x.AssetClassification.MorningstarSectorCode != MorningstarSectorCode.FinancialServices
                 // where x.MarketCap > 300000000 && x.MarketCap < 2000000000
                       orderby x.MarketCap descending
                 select x.Symbol).Take(2000).ToList();

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

            return filteredFine;
        }

        IEnumerable<Symbol> SelectNasdaq100Proxy(IEnumerable<FineFundamental> fine)
        {
            var filteredFine =
                (from x in fine
                 where x.CompanyReference.CountryId == "USA" &&
                       ( (x.CompanyReference.PrimaryExchangeID == "NAS") )  &&
                       (Time - x.SecurityReference.IPODate).Days > 360
                       && 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, 100, 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 (twelveMonth+nineMonth+sixMonth);
            }
        }
    }
}