Overall Statistics
Total Trades
2408
Average Win
0.70%
Average Loss
-0.61%
Compounding Annual Return
22.327%
Drawdown
25.900%
Expectancy
0.290
Net Profit
774.302%
Sharpe Ratio
0.964
Probabilistic Sharpe Ratio
33.652%
Loss Rate
40%
Win Rate
60%
Profit-Loss Ratio
1.15
Alpha
0.015
Beta
0.991
Annual Standard Deviation
0.171
Annual Variance
0.029
Information Ratio
0.143
Tracking Error
0.094
Treynor Ratio
0.167
Total Fees
$6248.40
Estimated Strategy Capacity
$3600000.00
Lowest Capacity Asset
KLAC 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 = 10;
        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, 4, 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("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.WeekStart("QQQ"),
                Schedule.TimeRules.AfterMarketOpen("QQQ", 1), Weekly);

            //Schedule.On(Schedule.DateRules.EveryDay("QQQ"),
            //    Schedule.TimeRules.AfterMarketOpen("QQQ", 1), Daily);
        }
        
        public void Weekly()
        {
        	//if(ActiveSecurities["QQQ"].Close < SymbolDict["QQQ"].TwoHundredEma)
            {
                //Liquidate();
            }
        }

        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") /* ||
                         (x.CompanyReference.PrimaryExchangeID == "NYS")*/ )  &&
                       (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 oneMonth;
        public RateOfChangePercent threeMonth;
        public RateOfChangePercent sixMonth;
        public RateOfChangePercent nineMonth;
        public RateOfChangePercent twelveMonth;
        public ExponentialMovingAverage TwoHundredEma;
        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);
            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);
            }
        }
    }
}