Overall Statistics
Total Trades
1347
Average Win
1.72%
Average Loss
-1.34%
Compounding Annual Return
24.201%
Drawdown
35.000%
Expectancy
0.335
Net Profit
1608.684%
Sharpe Ratio
0.849
Probabilistic Sharpe Ratio
16.819%
Loss Rate
42%
Win Rate
58%
Profit-Loss Ratio
1.29
Alpha
0.031
Beta
0.949
Annual Standard Deviation
0.226
Annual Variance
0.051
Information Ratio
0.14
Tracking Error
0.161
Treynor Ratio
0.202
Total Fees
$5926.89
Estimated Strategy Capacity
$40000000.00
Lowest Capacity Asset
DLTR R735QTJ8XC9X
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;

namespace QuantConnect.Algorithm.CSharp
{
    public class MomentumRotation : QCAlgorithm
    {
        private const int PORTFOLIO_NUM = 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>();
        public override void Initialize()
        {
            SetStartDate(2009, 3, 1); // Set Start Date
            SetEndDate(2022, 4, 1); // Set Start Date
            SetCash(50000); // Set Strategy Cash

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

            var security = AddEquity("QQQ", Resolution.Daily);

            SetBenchmark("QQQ");

            AddUniverseSelection(new FineFundamentalUniverseSelectionModel(SelectCoarse, SelectFine));

            Schedule.On(Schedule.DateRules.MonthStart("QQQ"),
                Schedule.TimeRules.AfterMarketOpen("QQQ", 1), Monthly);

            //Schedule.On(Schedule.DateRules.EveryDay("QQQ"),
            //    Schedule.TimeRules.AfterMarketOpen("QQQ", 1), Daily);
        }

        public void Daily()
        {
            /*if(ActiveSecurities["QQQ"].Close < SymbolDict["QQQ"].TwoHundredEma)
            {
                Liquidate();
            }
            else if(!Portfolio.Invested)
            {*/
                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 Monthly()
        {
        	Liquidate();
        	
            if(ActiveSecurities["QQQ"].Close < SymbolDict["QQQ"].TwoHundredEma)
            {
                Liquidate();
            }
            else
            {
                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);
                }
            }
        }

        /// OnData event is the primary entry point for your algorithm. Each new data point will be pumped in here.
        /// Slice object keyed by symbol containing the stock data
        public override void OnData(Slice data)
        {
        }

        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)
            {
                if(!SymbolDict.ContainsKey(security.Symbol))
                {
                    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
                       //orderby x.DollarVolume 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 threeMonth;
        public RateOfChangePercent sixMonth;
        public RateOfChangePercent nineMonth;
        public RateOfChangePercent twelveMonth;
        public ExponentialMovingAverage TwoHundredEma;
        public SymbolData(Symbol symbol, QCAlgorithm algorithm)
        {
            Symbol = symbol;
            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);
            TwoHundredEma = algorithm.EMA(symbol, 200, Resolution.Daily);
        }

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