Overall Statistics
Total Trades
0
Average Win
0%
Average Loss
0%
Compounding Annual Return
0%
Drawdown
0%
Expectancy
0
Net Profit
0%
Sharpe Ratio
0
Probabilistic Sharpe Ratio
0%
Loss Rate
0%
Win Rate
0%
Profit-Loss Ratio
0
Alpha
0
Beta
0
Annual Standard Deviation
0
Annual Variance
0
Information Ratio
0.518
Tracking Error
0.174
Treynor Ratio
0
Total Fees
$0.00
Estimated Strategy Capacity
$0
Lowest Capacity Asset
#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 TopGainerStudy: QCAlgorithm
    {
        bool ENABLE_TICKER_LIST = false;
        const string BENCHMARK = "QQQ";
        const string BORDER = "=====================================================================";

        /// Used For Universe Updates
        private int _lastMonth = -1;
        private const int ROLLING_WINDOW_COUNT = 21;
        private const int WARMUP = 252;

        /// Minimum To Enter Universe
        private const int MINIMUM_ENTRY_CAP = 0;
        private const long MAXIMUM_ENTRY_CAP = 6000000000;

        private string[] _TickerList = {"BTU", "CEIX"};

        /// Exchanges
        /// ASE - AMEX
        /// NYS - NYSE
        /// NAS - NASDAQ
        private string[] _ValidExchanges = {"ASE", "NYS", "NAS"};

        /// Tracks Symbol Indicators
        Dictionary<Symbol, SymbolData> _SymbolDict = new Dictionary<Symbol, SymbolData>();

        /// Track Daily Bars Of Data
        Dictionary<Symbol, RollingWindow<TradeBar>> _WindowDict = new Dictionary<Symbol, RollingWindow<TradeBar>>();

        /// List Storage
        Dictionary<Symbol, DateTime> _Leaders = new Dictionary<Symbol, DateTime>();

        /// Structures Used to Track MorningStar Industry Group Specifics
        Dictionary<int, string> _Industries = new Dictionary<int, string>();
        Dictionary<int, List<Security>> _IndustryStocks = new Dictionary <int, List<Security>>();

        /// Date Range
        private DateTime _StartDate = new DateTime(2021,1,1);

        /// Set End Date To Trigger Date
        private DateTime _EndDate = new DateTime(2021,1,15);

        public override void Initialize()
        {
            SetStartDate(_StartDate); // Set Start Date, Provide 1 Month Buffer For Warmup
            SetEndDate(_EndDate); // Set Start Date
            SetCash(5000000); // Set Strategy Cash

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

            AddUniverseSelection(new FineFundamentalUniverseSelectionModel(SelectCoarse, SelectFine));
            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>();
            }

            /// Prepare Indicators
            SetWarmUp(new TimeSpan(WARMUP,0,0,0));
            Log(BORDER);

            Schedule.On(Schedule.DateRules.WeekStart(security.Symbol),
               Schedule.TimeRules.AfterMarketOpen(security.Symbol, 1), PrintTopGainers);
            
        }

        public override void OnEndOfAlgorithm()
        {   
            Log(BORDER);
            Log("Number Of Stocks in Universe = " + ActiveSecurities.Keys.Count());
            Log("Printing Top Gainer Results For Trading Day Prior To " + Time.Date.ToShortDateString());
            Log("Number Of Symbols = " + _Leaders.Count);
            Log(BORDER);

            var lLeaderList = (from pair in _Leaders
                where ActiveSecurities.ContainsKey(pair.Key)
                where ActiveSecurities[pair.Key].Fundamentals != null
                where ActiveSecurities[pair.Key].Fundamentals.AssetClassification != null
                orderby pair.Value ascending
                select pair.Key).ToList();

            /// Pipe Symbol Info To Log
            foreach(var stock in lLeaderList)
            {
                string lIndustryGroup = "Industry Data Not Available";

                /// Label Industry Group if Available
                if(_Industries.ContainsKey(ActiveSecurities[stock].Fundamentals.AssetClassification.MorningstarIndustryCode))
                {
                    lIndustryGroup = _Industries[ActiveSecurities[stock].Fundamentals.AssetClassification.MorningstarIndustryCode];
                }
                
                string lTickerName = stock.Value;
                string lLogString = "Industry - " + lIndustryGroup + " | Stock - " + lTickerName + " | Entry Date - " + _Leaders[stock].ToShortDateString();
                Log(lLogString);
            }

            Log(BORDER);

            base.OnEndOfAlgorithm();
        }

        public void PrintTopGainers()
        {
            if(IsWarmingUp)
            {
                return;
            }

            /// Value Metrics
            /// Defining These Locally For Simplicity Purposes, Could Be Global to avoid Redefinitions
            decimal lMinimumClosingPrice = 0;

            decimal lMinimumAverageVolume = 500000;
            decimal lMinimumAverageDollarVolume = 1000000;
            decimal lOneWeekGainPercent = 20;

            /// Get Top Gainer Scan Today

            var lTopGainerStocks = (from symbolSecurityPair in ActiveSecurities

                /// *** Initial Validation ***
                where !_Leaders.ContainsKey(symbolSecurityPair.Key)
                
                /// Must Be In Universe
                where _SymbolDict.ContainsKey(symbolSecurityPair.Key)

                /// Must Meet At Least One RateOfChangePercent Metric
                && (_SymbolDict[symbolSecurityPair.Key].OneWeekGain(symbolSecurityPair.Value.High) > lOneWeekGainPercent)

                && symbolSecurityPair.Value.Close < _SymbolDict[symbolSecurityPair.Key].TwoHundredEma

                /// Must Have Properties Like Market Cap and Industry Group (Removes ETF's and some Data Issues)
                where symbolSecurityPair.Value.Fundamentals != null
                where symbolSecurityPair.Value.Fundamentals.AssetClassification != null

                /// *** Value Validation **
                where symbolSecurityPair.Value.Close > lMinimumClosingPrice
                /// where symbolSecurityPair.Value.Fundamentals.MarketCap > lMinimumMarketCap
                where _SymbolDict[symbolSecurityPair.Key].AverageVolume > lMinimumAverageVolume
                where _SymbolDict[symbolSecurityPair.Key].AverageDollarVolume > lMinimumAverageDollarVolume

                orderby symbolSecurityPair.Value.Fundamentals.AssetClassification.MorningstarIndustryCode

                /// Get The Symbol's
                select symbolSecurityPair.Key).ToList();

            foreach(var leader in lTopGainerStocks)
            {
                _Leaders[leader] = Time;
            }
        }

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

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

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

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

                    _WindowDict[security.Symbol] = new RollingWindow<TradeBar>(ROLLING_WINDOW_COUNT);
                    
                    var lBarHistory = History<TradeBar>(security.Symbol, WARMUP, Resolution.Daily).Reverse().ToArray();
                    foreach(var lBar in lBarHistory)
                    {
                        _WindowDict[security.Symbol].Add(lBar);
                    }

                    SubscriptionManager.AddConsolidator(security.Symbol, consolidator);

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

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

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

        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 x in fine
                 where (_ValidExchanges.Contains(x.CompanyReference.PrimaryExchangeID))
                 
                 where x.MarketCap < MAXIMUM_ENTRY_CAP

                 where x.AssetClassification.MorningstarSectorCode != MorningstarSectorCode.Healthcare
                 /// where x.MarketCap > MINIMUM_ENTRY_CAP

                 orderby x.MarketCap descending
                 select x.Symbol).ToList();

            _lastMonth = Time.Month;

            return filteredFine;
        }

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

        	bool tickerExists = false;
        	foreach(var symbol in _TickerList)
        	{
        		if(ticker == symbol)
                {
                    tickerExists = true;
                    break;
                }
        	}
            return tickerExists;
        }

    class SymbolData
    {
        public Symbol MySymbol;
        QCAlgorithm myAlgo;

        /// Indicators
        public SimpleMovingAverage AverageVolume;
        public SimpleMovingAverage AverageDollarVolume;

        /// Special Var for ADR Percent
        public SimpleMovingAverage HighLowRatioAverage;
        public Minimum OneMonthLow;
        public Minimum ThreeMonthLow;
        public Minimum SixMonthLow;
        public Minimum TwelveMonthLow;
        public Minimum OneWeekLow;

        public Minimum EightWeekLow;
        public ExponentialMovingAverage TwoHundredEma;
        

        public SymbolData(Symbol symbol, QCAlgorithm algorithm)
        {
            MySymbol = symbol;
            myAlgo = algorithm;

            /// Indicator Setup

            AverageVolume = algorithm.SMA(symbol, 20, Resolution.Daily, tradeBar => ((TradeBar)tradeBar).Volume);
            AverageDollarVolume = algorithm.SMA(symbol, 20, Resolution.Daily, tradeBar => ((TradeBar)tradeBar).Volume * ((TradeBar)tradeBar).Close);
            
            OneMonthLow = algorithm.MIN(symbol, 21, Resolution.Daily, tradeBar => ((TradeBar)tradeBar).Low);
            ThreeMonthLow = algorithm.MIN(symbol, 63, Resolution.Daily, tradeBar => ((TradeBar)tradeBar).Low);
            SixMonthLow = algorithm.MIN(symbol, 126, Resolution.Daily, tradeBar => ((TradeBar)tradeBar).Low);
            TwelveMonthLow = algorithm.MIN(symbol, 252, Resolution.Daily, tradeBar => ((TradeBar)tradeBar).Low);

            EightWeekLow = algorithm.MIN(symbol, 42, Resolution.Daily, tradeBar => ((TradeBar)tradeBar).Low);

            OneWeekLow = algorithm.MIN(symbol, 5, Resolution.Daily, tradeBar => ((TradeBar)tradeBar).Low);

            HighLowRatioAverage = algorithm.SMA(symbol, 20, Resolution.Daily, tradeBar => ((TradeBar)tradeBar).High / ((TradeBar)tradeBar).Low);

            TwoHundredEma = algorithm.EMA(symbol, 200);
        }

        private decimal GetRateChange(decimal aPeriodLowPrice, decimal aCurrentClosingPrice)
        {
            if(aPeriodLowPrice < 0.1m || aCurrentClosingPrice < 0.1m)
            {
                return 0;
            }

            return Math.Round(100 * ((aCurrentClosingPrice / aPeriodLowPrice) - 1), 2);
        }

        /// ROCP Method, To-Do Optimize

        public decimal OneWeekGain(decimal aCurrentClosingPrice)
        {
            return GetRateChange(OneWeekLow, aCurrentClosingPrice);
        }

        public decimal OneMonthROCP(decimal aCurrentClosingPrice)
        {
            return GetRateChange(OneMonthLow, aCurrentClosingPrice);
        }

        public decimal EightWeekGain(decimal aCurrentClosingPrice)
        {
            return GetRateChange(EightWeekLow, aCurrentClosingPrice);
        }

        public decimal ThreeMonthROCP(decimal aCurrentClosingPrice)
        {
            return GetRateChange(ThreeMonthLow, aCurrentClosingPrice);
        }

        public decimal SixMonthROCP(decimal aCurrentClosingPrice)
        {
            return GetRateChange(SixMonthLow, aCurrentClosingPrice);
        }

        public decimal TwelveMonthROCP(decimal aCurrentClosingPrice)
        {
            return GetRateChange(TwelveMonthLow, aCurrentClosingPrice);
        }

        public decimal ADRPercent
        {
            get
            {   
                decimal lAdrPercent = 100 * (HighLowRatioAverage - 1);
                return Math.Round(lAdrPercent, 2);
            }
        }
    }

    }
}