Overall Statistics
Total Trades
2054
Average Win
0.32%
Average Loss
-0.55%
Compounding Annual Return
17.101%
Drawdown
20.100%
Expectancy
0.141
Net Profit
120.379%
Sharpe Ratio
0.932
Probabilistic Sharpe Ratio
37.357%
Loss Rate
28%
Win Rate
72%
Profit-Loss Ratio
0.58
Alpha
0.038
Beta
0.453
Annual Standard Deviation
0.134
Annual Variance
0.018
Information Ratio
-0.468
Tracking Error
0.146
Treynor Ratio
0.277
Total Fees
$3094.09
Estimated Strategy Capacity
$7100000.00
Lowest Capacity Asset
STNE WYZEPGZ8HZ6T
#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
namespace QuantConnect.Algorithm.CSharp
{
    public class CONSTANTS
    {
        public const int ROLLING_WINDOW_COUNT = 21;
    }
    public class Template : QCAlgorithm
    {
        /********** Const ************/
        const string BENCHMARK = "QQQ";
        
        /// Used to Enable Ticker List For Specialized Runs
        const bool ENABLE_TICKER_LIST = false;

        /********** Vars ************/
        int _LastMonth = -1;

        // Start Times
        DateTime startDate = new DateTime(2016, 3, 1);
        DateTime endDate = new DateTime(2021, 3, 1);

        /// NYS = NYSE
        /// NAS = NASDAQ
        /// ASE = AMEX
        /// See: https://www.quantconnect.com/forum/discussion/12234/exchange-id-mapping/p1
        /// See: https://www.quantconnect.com/forum/discussion/11121/how-to-backtest-and-live-trade-on-chinese-stocks/p1
        string[] _ValidExchanges = {"NYS", "NAS", "ASE"};


        /********** Structures ************/
        string[] _TickerList = {};

        // Map Symbols to their Rolling Window of Tradebar Data
        Dictionary<Symbol, RollingWindow<TradeBar>> _WindowDict = new Dictionary<Symbol, RollingWindow<TradeBar>>();

        // Map Symbols to their Indicator Data
        Dictionary<Symbol, SymbolData> _SymbolDict = new Dictionary<Symbol, SymbolData>();

        // Industry Group Mapping
        Dictionary<int, string> _Industries = new Dictionary<int, string>();

        // Mapping Stocks To Their Industry Group
        Dictionary<int, List<Security>> _IndustryStocks = new Dictionary <int, List<Security>>();

        /********** Methods ************/

        public override void Initialize()
        {
            SetStartDate(startDate);
            SetEndDate(endDate);
            SetCash(100000);

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

            AddUniverseSelection(new FineFundamentalUniverseSelectionModel(SelectCoarse, SelectNasdaq100Proxy));
            var security = AddEquity(BENCHMARK, Resolution.Daily);

            SetBenchmark(BENCHMARK);

            /// Setup Industry Mapping
            Type type = typeof(MorningstarIndustryCode); // MyClass is static class with static properties
            foreach (var p in type.GetFields( System.Reflection.BindingFlags.Static | System.Reflection.BindingFlags.NonPublic | System.Reflection.BindingFlags.Public
                                              ))
            {
                object v = p.GetValue(null); // static classes cannot be instanced, so use null...=

                var industryCode = v.ToString().ToInt32();
                _Industries[industryCode] = p.Name;
                _IndustryStocks[industryCode] = new List<Security>();
            }

            Schedule.On(Schedule.DateRules.EveryDay(),
                Schedule.TimeRules.BeforeMarketClose(BENCHMARK, 1), OnNewDay);

            SetWarmUp(new TimeSpan(200, 0, 0, 0));
        } 

        public void OnNewDay()
        {
            if(IsWarmingUp) return;

            if(ActiveSecurities[BENCHMARK].Close < _SymbolDict[BENCHMARK].TwoHundredSma)
            {
                Liquidate();
                return;
            }

            foreach(var kvp in ActiveSecurities)
            {
                bool lContainKey = _SymbolDict.ContainsKey(kvp.Key);
                bool lInvested = kvp.Value.Invested;
                if(!lContainKey && lInvested)
                {
                    Liquidate(kvp.Key);
                    continue;
                }
                else if(lContainKey && lInvested && _SymbolDict[kvp.Key].FiveDayRSI > 65)
                {
                    Liquidate(kvp.Key);
                    continue;
                }
            }
            
            var lPotentialTrades = (from kvp in ActiveSecurities
                where _SymbolDict.ContainsKey(kvp.Key)
                where _WindowDict.ContainsKey(kvp.Key)
                where kvp.Key != BENCHMARK
                where !kvp.Value.Invested
                where kvp.Value.Close > _SymbolDict[kvp.Key].TwoHundredSma
                where kvp.Value.Close < _SymbolDict[kvp.Key].TwentySma
                where _SymbolDict[kvp.Key].FiveDayRSI < 45
                orderby _SymbolDict[kvp.Key].FiveDayRSI descending
                select kvp.Key).Take(10).ToList();

            foreach(var lPotentialTrade in lPotentialTrades)
            {
                var lTarget = 0.1;
                var lQuantity = CalculateOrderQuantity(lPotentialTrade, lTarget);

                if(! (ActiveSecurities[lPotentialTrade].Close * lQuantity > Portfolio.MarginRemaining) )
                {
                    SetHoldings(lPotentialTrade, 0.1);
                }
            }
        }

        public void WindowBarHandler(object sender, TradeBar windowBar)
        {
            if(!_WindowDict.ContainsKey(windowBar.Symbol))
            {
                _WindowDict[windowBar.Symbol] = new RollingWindow<TradeBar>(CONSTANTS.ROLLING_WINDOW_COUNT); 
            }

            _WindowDict[windowBar.Symbol].Add(windowBar);
        }

        public void WarmupWindow(Symbol aSymbol)
        {
            var lHistory = History<TradeBar>(aSymbol, CONSTANTS.ROLLING_WINDOW_COUNT, Resolution.Daily);
            if(!_WindowDict.ContainsKey(aSymbol))
            {
                _WindowDict[aSymbol] = new RollingWindow<TradeBar>(CONSTANTS.ROLLING_WINDOW_COUNT); 
            }

            foreach(var lBar in lHistory)
            {
                _WindowDict[aSymbol].Add(lBar);
            }
        }

        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.TryGetValue(security.Symbol, out var lSymbolData))
                {
                    lSymbolData.Dispose();
                    _SymbolDict.Remove(security.Symbol);
                }

                if(security.Fundamentals != null)
                {
                    var lCode = security.Fundamentals.AssetClassification.MorningstarIndustryCode;
                    if(_IndustryStocks.ContainsKey(lCode))
                    {
                        _IndustryStocks[lCode].Remove(security);
                    }
                }

                _WindowDict.Remove(security.Symbol);
            }

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

                    var consolidator = new TradeBarConsolidator(TimeSpan.FromDays(1));
                    consolidator.DataConsolidated += WindowBarHandler;

                    SubscriptionManager.AddConsolidator(security.Symbol, consolidator);

                    if(security.Fundamentals != null)
                    {
                        var lCode = security.Fundamentals.AssetClassification.MorningstarIndustryCode;
                        if(_IndustryStocks.ContainsKey(lCode))
                        {
                            _IndustryStocks[lCode].Add(security);
                        }
                    }
                }
            }
        }

        IEnumerable<Symbol> SelectCoarse(IEnumerable<CoarseFundamental> coarse)
        {
            if (Time.Month == _LastMonth)
            {
                return Universe.Unchanged;
            }

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

            return sortedByDollarVolume;
        }

        IEnumerable<Symbol> SelectFine(IEnumerable<FineFundamental> fine) 
        {
            var filteredFine =
                (from security in fine
                 where _ValidExchanges.Contains(security.CompanyReference.PrimaryExchangeID)
                 orderby security.MarketCap descending
                 select security.Symbol).ToList();

            _LastMonth = Time.Month;

            return filteredFine;
        }

        IEnumerable<Symbol> SelectNasdaq100Proxy(IEnumerable<FineFundamental> fine) 
        {
            var filteredFine =
                (from security in fine
                 where security.CompanyReference.PrimaryExchangeID == "NAS"
                 where security.AssetClassification.MorningstarSectorCode != MorningstarSectorCode.FinancialServices
                 orderby security.MarketCap descending
                 select security.Symbol).Take(100).ToList();

            _LastMonth = Time.Month;

            return filteredFine;
        }

        public bool DoesTickerExist(string ticker)
        {
            if(!ENABLE_TICKER_LIST)
            {
                return true;
            }

            return _TickerList.Contains(ticker);
        }
    }

    /// Handles Tracking Indicators and Warming Up the Data For Indicators as well as
    /// attaching to the Algorithm
    public class SymbolData
    {
        private Template _algorithm;
        private Symbol _symbol;
        private TradeBarConsolidator _consolidator;

        /// Daily Indicators
        public AverageTrueRange ATR;
        public RelativeStrengthIndex FiveDayRSI;
        public SimpleMovingAverage TwoHundredSma;
        public SimpleMovingAverage TwentySma;


        public SymbolData(Template algorithm, Symbol symbol)
        {
            _algorithm = algorithm;
            _symbol = symbol;

            // Create Indicators
            ATR = new AverageTrueRange(10);
            FiveDayRSI = new RelativeStrengthIndex(5);
            TwoHundredSma = new SimpleMovingAverage(200);
            TwentySma = new SimpleMovingAverage(20);

            // Create a consolidator to update the indicator
            _consolidator = new TradeBarConsolidator(TimeSpan.FromDays(1));
            _consolidator.DataConsolidated += DailyDataUpdate;

            // Register the consolidator to update the indicator
            algorithm.SubscriptionManager.AddConsolidator(symbol, _consolidator);

            // Warm Up Indicators
            algorithm.WarmUpIndicator(symbol, ATR, Resolution.Daily);
            algorithm.WarmUpIndicator(symbol, FiveDayRSI, Resolution.Daily);
            algorithm.WarmUpIndicator(symbol, TwoHundredSma, Resolution.Daily);
            algorithm.WarmUpIndicator(symbol, TwentySma, Resolution.Daily);

            // Prepare Window Of Data
            algorithm.WarmupWindow(symbol);
        }

        /// Updates Indicators that run on specifically at a daily resolution
        /// Updates Once a Day
        private void DailyDataUpdate(object sender, TradeBar consolidatedBar)
        {
            ATR.Update(consolidatedBar);
            FiveDayRSI.Update(consolidatedBar.EndTime, consolidatedBar.Close);
            TwoHundredSma.Update(consolidatedBar.EndTime, consolidatedBar.Close);
            TwentySma.Update(consolidatedBar.EndTime, consolidatedBar.Close);
        }

        // If you have a dynamic universe, remove consolidators for the securities removed from the universe
        public void Dispose()
        {
            _algorithm.SubscriptionManager.RemoveConsolidator(_symbol, _consolidator);
        }
    }
}