Overall Statistics
Total Trades
602
Average Win
0.29%
Average Loss
-0.12%
Compounding Annual Return
33.029%
Drawdown
14.900%
Expectancy
0.911
Net Profit
37.952%
Sharpe Ratio
1.338
Loss Rate
44%
Win Rate
56%
Profit-Loss Ratio
2.42
Alpha
0.304
Beta
-0.331
Annual Standard Deviation
0.189
Annual Variance
0.036
Information Ratio
0.451
Tracking Error
0.218
Treynor Ratio
-0.764
Total Fees
$623.35
using MathNet.Numerics;
using MathNet.Numerics.LinearAlgebra;
using QuantConnect.Indicators;
using System;
using System.Linq;

//Copied from this forum:
//href https://www.quantconnect.com/forum/discussion/695/adjusted-slope--exponential-slope----annualized-slope--r-squuared--adjusted-slope/p1

namespace QuantConnect.Algorithm.CSharp.Helpers
{
    public class AnnualizedExponentialSlopeIndicator : WindowIndicator<IndicatorDataPoint>
    {
        /// <summary>
        /// Array representing the time.
        /// </summary>
        private readonly double[] t;

        public AnnualizedExponentialSlopeIndicator(int period)
            : base("AESI(" + period + ")", period)
        {
            t = Vector<double>.Build.Dense(period, i => i + 1).ToArray();
        }

        public AnnualizedExponentialSlopeIndicator(string name, int period)
            : base(name, period)
        {
            t = Vector<double>.Build.Dense(period, i => i + 1).ToArray();
        }
        protected override decimal ComputeNextValue(IReadOnlyWindow<IndicatorDataPoint> window, IndicatorDataPoint input)
        {
            // Until the window is ready, the indicator returns the input value.
            if (window.Samples <= window.Size) return 0m;

            // Sort the window by time, convert the observations to double and transform it to an array
            var series = window
                .OrderBy(i => i.Time)
                .Select(i => Convert.ToDouble(Math.Log(Convert.ToDouble(i.Value))))
                .ToArray();
            // Fit OLS
            // solves y=a + b*x via linear regression
            // http://numerics.mathdotnet.com/Regression.html
            var ols = Fit.Line(x: t, y: series);
            var intercept = ols.Item1;
            var slope = ols.Item2;

            //Intercept.Update(input.Time, (decimal) ols.Item1);
            //Slope.Update(input.Time, (decimal) ols.Item2);

            // compute rsquared
            var rsquared = GoodnessOfFit.RSquared(t.Select(x => intercept + slope * x), series);

            // anything this small can be viewed as flat
            if (double.IsNaN(slope) || Math.Abs(slope) < 1e-25) return 0m;

            // trading days per year for us equities
            const int dayCount = 250;

            // annualize dy/dt
            var annualSlope = ((Math.Pow(Math.Exp(slope), dayCount)) - 1) * 100;

            // scale with rsquared
            annualSlope = annualSlope * Math.Pow(rsquared, 2);
            //annualSlope = annualSlope * rsquared;

            if (annualSlope >= (double)decimal.MaxValue || annualSlope <= (double)decimal.MinValue)
            {
                annualSlope = 0;
                //Debug("Negative slope due to arithmic overflow");
            }
            return Convert.ToDecimal(annualSlope);

        }
    }
}                        
using MathNet.Numerics;
using MathNet.Numerics.LinearAlgebra;
using QuantConnect.Data.Market;
using QuantConnect.Indicators;
using System;
using System.Collections.Generic;
using System.Linq;

//Copied from this forum:
//href https://www.quantconnect.com/forum/discussion/695/adjusted-slope--exponential-slope----annualized-slope--r-squuared--adjusted-slope/p1

namespace QuantConnect.Algorithm.CSharp.Helpers
{
    public class CustomMomentumIndicator : TradeBarIndicator
    {
        private Symbol _symbol;
        private int _windowSize;
        public readonly AnnualizedExponentialSlopeIndicator AnnualizedSlope;
        public readonly ExponentialMovingAverage MovingAverage;
        public readonly GapIndicator Gap;
        public readonly AverageTrueRange Atr;

        public CustomMomentumIndicator(Symbol symbol, int annualizedSlopeWindow, int movingAverageWindow, int gapWindow, int atrWindow) : base($"CMI({symbol}, {annualizedSlopeWindow}, {movingAverageWindow}, {gapWindow})")
        {
            _symbol = symbol;
            AnnualizedSlope = new AnnualizedExponentialSlopeIndicator(annualizedSlopeWindow);
            MovingAverage = new ExponentialMovingAverage(movingAverageWindow);
            Gap = new GapIndicator(gapWindow);
            Atr = new AverageTrueRange(atrWindow);

            _windowSize = (new int[] { movingAverageWindow, annualizedSlopeWindow, gapWindow, atrWindow }).Max();
        }
        public Symbol Symbol { get { return _symbol; } }

        public override void Reset()
        {
            AnnualizedSlope.Reset();
            MovingAverage.Reset();
            Gap.Reset();
            Atr.Reset();
            base.Reset();
        }

        protected override decimal ComputeNextValue(TradeBar input)
        {
            AnnualizedSlope.Update(input.EndTime, input.Value);
            MovingAverage.Update(input.EndTime, input.Value);
            Gap.Update(input.EndTime, input.Value);
            Atr.Update(input);

            return AnnualizedSlope;
        }
        /// <summary>
        /// Are the indicators ready to be used?
        /// </summary>
        public override bool IsReady
        {
            get { return AnnualizedSlope.IsReady && MovingAverage.IsReady && Gap.IsReady && Atr.IsReady; }
        }
        /// <summary>
        /// Returns the Window of the indicator required to warm up indicator
        /// </summary>
        public int Window
        {
            get {return _windowSize;}
        }
        public new string ToDetailedString()
        {
            return $"Symbol:{_symbol} Slope:{AnnualizedSlope.ToDetailedString()} Average:{MovingAverage.ToDetailedString()} Gap:{Gap.ToDetailedString()} Atr:{Atr.ToDetailedString()} IsReady:{IsReady}";
        }
    }
}                        
using MathNet.Numerics;
using MathNet.Numerics.Statistics;
using QuantConnect.Indicators;
using System;
using System.Linq;

/// <summary>
///  Indictor to indicate the percentage (0.10 = 10%) of which a security gapped over the last period;
/// </summary>
namespace QuantConnect.Algorithm.CSharp.Helpers
{
    public class GapIndicator : WindowIndicator<IndicatorDataPoint>
    {
        public GapIndicator(int period)
            : base("GAP(" + period + ")", period)
        {
        }

        public GapIndicator(string name, int period)
            : base(name, period)
        {
        }
        public override bool IsReady
        {
            get { return Samples >= Period; }
        }

        protected override decimal ComputeNextValue(IReadOnlyWindow<IndicatorDataPoint> window, IndicatorDataPoint input)
        {
            if (window.Count < 3) return 0m;

            var diff = new double[window.Count];

            // load input data for regression
            for (int i = 0; i < window.Count - 1; i++)
            {
                diff[i] = (double)((window[i + 1] - window[i]) / (window[i] == 0 ? 1 : window[i].Value));
            }

            return (decimal) diff.MaximumAbsolute();
        }
    }
}                        
using MathNet.Numerics;
using MathNet.Numerics.Statistics;
using System;
using System.Linq;
using QuantConnect.Data.Market;
using QuantConnect.Data.UniverseSelection;
using QuantConnect.Indicators;
using QuantConnect.Securities;
using System.Collections.Generic;
using QuantConnect.Data.Fundamental;
using QuantConnect.Brokerages;
using QuantConnect.Algorithm.CSharp.Helpers;
using System.Net;
using System.Text.RegularExpressions;

namespace QuantConnect.Algorithm.CSharp
{
    /*
    Momentum strategy according to Andreas F. Clenow book 
    'Stocks on the Move Beating the Market with Hedge Fund Momentum Strategies'
    
    ** Objective of the algorithm: **
    
    The algorithm selects from the SP500 universe the stocks with fastest rising stocks with limited volatility.
    Every week the risk of the portfolio is balanced. Once a month the stocks are rebalanced.
    The algo only buys if the market is in a high trend (Bulish);
    
    ** Trade rules: **
    
    The rules are:
    - Rule 00: SP500 stocks
    - Rule 01: Trade on Wednesdays to limit number of trades;
    - Rule 02: Twice a month reset the positions sizes (risk);
    - Rule 03: Rebalance every week;
    - Rule 04: If the stock is not in the top 100/ 20% ranking, sell it;
    - Rule 05: If the stock is below its 100 days moving average, sell it;
    - Rule 06: If the stock gapped > 15%, sell it;
    - Rule 07: If the stock left the index, sell it;
    - Rule 08: If the SP500 is above the 200 days moving average we buy stocks, otherwise not;
    - Rule 09: Calculate the position sizes, based on 10 basis points using ATR formula;
    - Rule 10: Momentum is calculated based on 90 past days annualized exponential regression slope;
    - Rule 11: Momentum is weighted for volatility adjustment (r^2);
    - Rule 12: Position Size = (portfolioSize*0,001/ ATR)= #Shares;
    - Rule 13: Trade maximum 30 stocks;
	
	** Change history: **
    20180102_01: QT, created algo based on cloned algorithm. Refactoring algo.
    20180103_01: QT, seems that universe selecting may not have own calculations.
	*/
    /// <summary>
    /// Momentum strategy according to Andreas F. Clenow book 
    /// Stocks on the Move Beating the Market with Hedge Fund Momentum Strategies'	
    /// </summary>
    public class MomentumStrategyStockUniverseAlgorithm : QCAlgorithm
    {
        private const string Spy = "SPY";

        //https://www.barchart.com/
        private readonly List<string> _sp500FixedStockList = new List<string>() { "A", "AAL", "AAP", "AAPL", "ABBV", "ABC", "ABT", "ACN", "ADBE", "ADI", "ADM", "ADP", "ADS", "ADSK", "AEE", "AEP", "AES", "AET", "AFL", "AGN", "AIG", "AIV", "AIZ", "AJG", "AKAM", "ALB", "ALGN", "ALK", "ALL", "ALLE", "ALXN", "AMAT", "AMD", "AME", "AMG", "AMGN", "AMP", "AMT", "AMZN", "ANDV", "ANSS", "ANTM", "AON", "AOS", "APA", "APC", "APD", "APH", "APTV", "ARE", "ARNC", "ATVI", "AVB", "AVGO", "AVY", "AWK", "AXP", "AYI", "AZO", "BA", "BAC", "BAX", "BBT", "BBY", "BDX", "BEN", "BF.B", "BHF", "BHGE", "BIIB", "BK", "BLK", "BLL", "BMY", "BRK.B", "BSX", "BWA", "BXP", "C", "CA", "CAG", "CAH", "CAT", "CB", "CBG", "CBOE", "CBS", "CCI", "CCL", "CDNS", "CELG", "CERN", "CF", "CFG", "CHD", "CHK", "CHRW", "CHTR", "CI", "CINF", "CL", "CLX", "CMA", "CMCSA", "CME", "CMG", "CMI", "CMS", "CNC", "CNP", "COF", "COG", "COL", "COO", "COP", "COST", "COTY", "CPB", "CRM", "CSCO", "CSRA", "CSX", "CTAS", "CTL", "CTSH", "CTXS", "CVS", "CVX", "CXO", "D", "DAL", "DE", "DFS", "DG", "DGX", "DHI", "DHR", "DIS", "DISCA", "DISCK", "DISH", "DLR", "DLTR", "DOV", "DPS", "DRE", "DRI", "DTE", "DUK", "DVA", "DVN", "DWDP", "DXC", "EA", "EBAY", "ECL", "ED", "EFX", "EIX", "EL", "EMN", "EMR", "EOG", "EQIX", "EQR", "EQT", "ES", "ESRX", "ESS", "ETFC", "ETN", "ETR", "EVHC", "EW", "EXC", "EXPD", "EXPE", "EXR", "F", "FAST", "FB", "FBHS", "FCX", "FDX", "FE", "FFIV", "FIS", "FISV", "FITB", "FL", "FLIR", "FLR", "FLS", "FMC", "FOX", "FOXA", "FRT", "FTI", "FTV", "GD", "GE", "GGP", "GILD", "GIS", "GLW", "GM", "GOOG", "GOOGL", "GPC", "GPN", "GPS", "GRMN", "GS", "GT", "GWW", "HAL", "HAS", "HBAN", "HBI", "HCA", "HCN", "HCP", "HD", "HES", "HIG", "HII", "HLT", "HOG", "HOLX", "HON", "HP", "HPE", "HPQ", "HRB", "HRL", "HRS", "HSIC", "HST", "HSY", "HUM", "IBM", "ICE", "IDXX", "IFF", "ILMN", "INCY", "INFO", "INTC", "INTU", "IP", "IPG", "IQV", "IR", "IRM", "ISRG", "IT", "ITW", "IVZ", "JBHT", "JCI", "JEC", "JNJ", "JNPR", "JPM", "JWN", "K", "KEY", "KHC", "KIM", "KLAC", "KMB", "KMI", "KMX", "KO", "KORS", "KR", "KSS", "KSU", "L", "LB", "LEG", "LEN", "LH", "LKQ", "LLL", "LLY", "LMT", "LNC", "LNT", "LOW", "LRCX", "LUK", "LUV", "LYB", "M", "MA", "MAA", "MAC", "MAR", "MAS", "MAT", "MCD", "MCHP", "MCK", "MCO", "MDLZ", "MDT", "MET", "MGM", "MHK", "MKC", "MLM", "MMC", "MMM", "MNST", "MO", "MON", "MOS", "MPC", "MRK", "MRO", "MS", "MSFT", "MSI", "MTB", "MTD", "MU", "MYL", "NAVI", "NBL", "NCLH", "NDAQ", "NEE", "NEM", "NFLX", "NFX", "NI", "NKE", "NLSN", "NOC", "NOV", "NRG", "NSC", "NTAP", "NTRS", "NUE", "NVDA", "NWL", "NWS", "NWSA", "O", "OKE", "OMC", "ORCL", "ORLY", "OXY", "PAYX", "PBCT", "PCAR", "PCG", "PCLN", "PDCO", "PEG", "PEP", "PFE", "PFG", "PG", "PGR", "PH", "PHM", "PKG", "PKI", "PLD", "PM", "PNC", "PNR", "PNW", "PPG", "PPL", "PRGO", "PRU", "PSA", "PSX", "PVH", "PWR", "PX", "PXD", "PYPL", "QCOM", "QRVO", "RCL", "RE", "REG", "REGN", "RF", "RHI", "RHT", "RJF", "RL", "RMD", "ROK", "ROP", "ROST", "RRC", "RSG", "RTN", "SBAC", "SBUX", "SCG", "SCHW", "SEE", "SHW", "SIG", "SJM", "SLB", "SLG", "SNA", "SNI", "SNPS", "SO", "SPG", "SPGI", "SRCL", "SRE", "STI", "STT", "STX", "STZ", "SWK", "SWKS", "SYF", "SYK", "SYMC", "SYY", "T", "TAP", "TDG", "TEL", "TGT", "TIF", "TJX", "TMK", "TMO", "TPR", "TRIP", "TROW", "TRV", "TSCO", "TSN", "TSS", "TWX", "TXN", "TXT", "UA", "UAA", "UAL", "UDR", "UHS", "ULTA", "UNH", "UNM", "UNP", "UPS", "URI", "USB", "UTX", "V", "VAR", "VFC", "VIAB", "VLO", "VMC", "VNO", "VRSK", "VRSN", "VRTX", "VTR", "VZ", "WAT", "WBA", "WDC", "WEC", "WFC", "WHR", "WLTW", "WM", "WMB", "WMT", "WRK", "WU", "WY", "WYN", "WYNN", "XEC", "XEL", "XL", "XLNX", "XOM", "XRAY", "XRX", "XYL", "YUM", "ZBH", "ZION", "ZTS"};
        /// <summary>
        /// Only used in backtest for caching the file results
        /// </summary>
        private Dictionary<DateTime, List<string>> _sp500List;

        /// Rule 00: SP500 stocks
        private static int _universeSelectMaxStocks = 500;

        /// Rule 10: Momentum is calculated based on 90 past days annualized exponential regression slope;
        private int _annualizedSlopeWindow = 90;
        /// <summary>
        /// Rule 05: If the stock is below its 100 days moving average, sell it;
        /// </summary>
        private int _movingAverageWindow = 100;
        /// <summary>
        /// ATR window
        /// </summary>
        private int _atrWindow = 20;
        /// <summary>
        /// Risk of the contract on the portfolio. Typically 1-5%. Take notice of contract size of (futures * price)
        /// </summary>
        private const decimal RiskPerContractOnPortfolio = 0.015m;

        private ExponentialMovingAverage _spyMovingAverage;
        private BollingerBands _spyBollingerBands;

        /// <summary>
        /// Holds our indicators per symbol
        /// </summary>
        private Dictionary<Symbol, CustomMomentumIndicator> _customIndicators = new Dictionary<QuantConnect.Symbol, CustomMomentumIndicator>(_universeSelectMaxStocks);
       
        // Rule 08: If the SP500 is above the 200 days moving average we buy stocks, otherwise not;
        private int _trendfilter = 200;

        // Rule 04: If the stock is not in the top 100/ 20% ranking, sell it;
        private int _topNStockOfSp500 = 100;
        /// <summary>
        /// Rule 06: If the stock gapped > 15% over period, sell it: Maximum Gap in percentage
        /// </summary>
        private decimal _maximumGap = 0.15m;
        /// <summary>
        /// Rule 06x: minimum annualized slope before buying stock.
        /// </summary>
        private decimal _minimumAnnualizedSlope = 0.0005m;
        /// <summary>
        /// Rule 06: If the stock gapped > 15% over period, sell it: Gap window
        /// </summary>
        private int _gapWindow = 90;

        // Rule 13: Trade maximum 30 stocks;
        private int _maxStockInPortfolio = 20;

        private bool _isDebugging = true;
        private bool _isPlotting = true;
        private int _isLogSpyMovingAveragePivot = 0;

        /// <summary>
        /// Is debugging set, speed up processing
        /// </summary>
        public bool IsDebugging { get { return _isDebugging; } }
        /// <summary>
        /// Is plotting set
        /// </summary>
        public bool IsPlotting { get { return _isPlotting; } }
        /// <summary>
        /// Log messages only if IsDebugging
        /// </summary>
        /// <param name="message"></param>
        public new void Log(string message)
        {
            if(IsDebugging)
                base.Log(message);
        }
        /// <summary>
        /// Initialise the data and resolution required, as well as the cash and start-end dates for your algorithm. All algorithms must initialized.
        /// </summary>
        public override void Initialize()
        {
            _isDebugging = false;
            //Set trading window
            SetStartDate(2017, 1, 1);
            SetEndDate(2018, 2, 14);
            //SetEndDate(DateTime.Now);

            //Set brokermodel
            SetCash(100000);
            SetBrokerageModel(BrokerageName.InteractiveBrokersBrokerage, AccountType.Margin);

            //Set moving average on SPY and benchmark
            var spy = AddEquity(Spy);
            SetBenchmark(spy.Symbol);
            _spyMovingAverage = EMA(spy.Symbol, _trendfilter, Resolution.Daily);
            _spyBollingerBands = BB(spy.Symbol, 21, 2, MovingAverageType.DoubleExponential, Resolution.Daily);

            //Warm up indicator
            IEnumerable<TradeBar> history = History(spy.Symbol, _trendfilter, Resolution.Daily);
            foreach (TradeBar tradeBar in history)
            {
                _spyMovingAverage.Update(tradeBar.EndTime, tradeBar.Close);
                _spyBollingerBands.Update(tradeBar.EndTime, tradeBar.Close);
            }
            //set warm up algorithm to avoid premature trading
            SetWarmUp(_trendfilter);

            //setup universe with filters for coarse and fine: https://www.quantconnect.com/docs#Universes
            UniverseSettings.Resolution = Resolution.Daily;
            AddUniverse(CoarseSelectionFunction, FineSelectionFunction);

            //Rule 01: Trade on Wednesdays to limit number of trades;
            //trade only on wednesdays at opening after 10 minutes
            Schedule.On(DateRules.Every(DayOfWeek.Wednesday, DayOfWeek.Wednesday),
                TimeRules.AfterMarketOpen(Spy, 10), ScheduledOnWednesDay10MinutesAfterMarketOpen);

            if (IsDebugging)
                base.Log("*** DEBUGGING: TAKE CARE OF PARAMETERS ***");
        }

        private void ScheduledOnWednesDay10MinutesAfterMarketOpen()
        {
            if (IsWarmingUp) return;

            if(_customIndicators == null || _customIndicators.Count == 0)
            {
                base.Log($"*** No custom indicators! *** ");
                return;
            }

            //if (Time.DayOfWeek != DayOfWeek.Wednesday)
            //    return;

            //#- Rule 10: Momentum is calculated based on self.momentum_window (90) past days annualized exponential regression slope;
            //#- Rule 11: Momentum is weighted for volatility adjustment (r^2);
            var sortedEquityListBySlope = _customIndicators.Where(
                x => x.Value.IsReady
                && x.Value.AnnualizedSlope > _minimumAnnualizedSlope
                && x.Value.Gap < _maximumGap
                && Securities[x.Key].Price > x.Value.MovingAverage
                ).OrderByDescending(x => x.Value.AnnualizedSlope)
                .Take(_topNStockOfSp500).ToList();

            if (sortedEquityListBySlope.Count == 0)
                base.Log($"sortedEquityListBySlope is empty. There where {_customIndicators.Count} custom Indicators.");

            //Liquidate that is not in buylist
            foreach (var security in Portfolio.Values)
            {
                if (!sortedEquityListBySlope.Exists(x => x.Value.Symbol == security.Symbol))
                {
                    if (security.Invested)
                    {
#if JO_DEBUG
                        Log($"Liquidate {security.Symbol} from Holdings");
#endif
                        Liquidate(security.Symbol);

                        if (_customIndicators.ContainsKey(security.Symbol))
                            _customIndicators.Remove(security.Symbol);
                    }
                }
            }
#if JO_DEBUG
            foreach (var security in sortedEquityListBySlope)
                Log($"Slope: {security.Value.AnnualizedSlope},indicator: {security.Value.ToDetailedString()}");
#endif

            // Do we have cash to trade?
            if (Portfolio.Cash > 0)
            {
                decimal estimatedPortfolioCashBalance = Portfolio.Cash;
                foreach (var data in sortedEquityListBySlope)
                {
                    CustomMomentumIndicator customIndicator = data.Value;
                    var symbol = customIndicator.Symbol;

                    if (!customIndicator.IsReady)
                        base.Log($"!customerIndicator[{customIndicator}].IsReady");

                    //- Rule 08: If the SP500 is above the 200 days moving average we buy stocks, otherwise not;
                    if (Securities[Spy].Price > _spyMovingAverage)
                    {                       
                        var risk = Portfolio.TotalPortfolioValue * RiskPerContractOnPortfolio;
                        var weight = (risk / customIndicator.Atr) * Securities[symbol].Price / Portfolio.TotalPortfolioValue * 100;
                        //^TO-DO: not working.

#if JO_DEBUG
                        Log($"BUY >> {symbol} weight: {weight}, price: {Securities[symbol].Price}");
#endif

                        SetHoldings(symbol, 1 / (double)_maxStockInPortfolio);
                    }
                }
            }
            //plot fir custom indicator
            if(IsPlotting)
            {
                var customIndicator = sortedEquityListBySlope.FirstOrDefault();
                Plot("FirstIndicator", "Price", Securities[customIndicator.Key].Close);
                Plot("FirstIndicator", "MovingAverage", customIndicator.Value.MovingAverage);
                Plot("FirstIndicator", "Gap", customIndicator.Value.Gap);
                Plot("FirstIndicator", "AnnualizedSlope", customIndicator.Value.AnnualizedSlope);
                Plot("FirstIndicator", "Atr", customIndicator.Value.Atr);
            }
        }
#region data selection
        /// <summary>
        /// Select S&P500 stocks only:
        /// Market capitalization must be greater than or equal to $6.1 billion USD
        /// Annual dollar value traded to float-adjusted market capitalization is greater than 1.0
        /// Minimum monthly trading volume of 250,000 shares in each of the six months leading up to the evaluation date
        /// </summary>
        /// <param name="coarse"></param>
        /// <returns></returns>
        public IEnumerable<Symbol> CoarseSelectionFunction(IEnumerable<CoarseFundamental> coarse)
        {
            //const decimal MarketCapitalization = 2100000000m; 
            //^not supported by Quantconnect;

            var sp500List = GetSP500SymbolList(Time.Date);

            const decimal DollarVolume = 21000000m;

            //if (!_rebalanceFlag) return Enumerable.Empty<Symbol>();

            // Market capitalization must be greater than or equal to $6.1 billion USD, Traded shares x Price
            var filtered = from x in coarse
                           where x.HasFundamentalData
                           where x.DollarVolume >= DollarVolume
                           //where x.Volume > 250000                             
                           where x.Price > 10
                           join SP500 in sp500List on x.Symbol.Value equals SP500
                           select x;

            var topCoarse = filtered.Take(_universeSelectMaxStocks);

            return topCoarse.Select(x => x.Symbol);

        }

        //Select S&P500 stocks only:
        //Not possible at quantconnect so try to select common stock, primary share and dividend share
        public IEnumerable<Symbol> FineSelectionFunction(IEnumerable<FineFundamental> fine)
        {
            //if (!_rebalanceFlag) return Enumerable.Empty<Symbol>();

            var filtered = from x in fine
                           where x.SecurityReference.SecurityType == "ST00000001" &&
                               x.SecurityReference.IsPrimaryShare                               
                           select x;

            return filtered.Select(x => x.Symbol);

        }
#endregion data selection

        // this event fires whenever we have changes to our universe
        public override void OnSecuritiesChanged(SecurityChanges changes)
        {
            if (changes.AddedSecurities.Count > 0)
            {
#if JO_DEBUG
                Log("Securities added: " + string.Join(",", changes.AddedSecurities.Select(x => x.Symbol.Value)));
#endif
                foreach (Security security in changes.AddedSecurities)
                {
                    if (!_customIndicators.ContainsKey(security.Symbol))
                    {
                        var customIndicator = new CustomMomentumIndicator(security.Symbol, _annualizedSlopeWindow, _movingAverageWindow, _gapWindow, _atrWindow);
                        //warm up indicator
                        var history = History(security.Symbol, customIndicator.Window, Resolution.Daily);
                        foreach (TradeBar tradeBar in history)
                            customIndicator.Update(tradeBar);

                        if (customIndicator.IsReady)
                        {
                            _customIndicators.Add(security.Symbol, customIndicator);
                            RegisterIndicator(security.Symbol, customIndicator, Resolution.Daily);
                            //Log($"_securityChanges.AddedSecurities, Security: {security} with warmup of {customIndicator.Window} and indicator {customIndicator}.");
                        }
                        else
                            base.Log($"*** _securityChanges.AddedSecurities, Security: {security} not ready!");

                    }
                }
            }
            if (changes.RemovedSecurities.Count > 0)
            {
#if JO_DEBUG
                Log("Securities removed: " + string.Join(",", changes.RemovedSecurities.Select(x => x.Symbol.Value)));
#endif
                foreach (var security in changes.RemovedSecurities)
                {
                    if (security.Invested)
                    {
                        Liquidate(security.Symbol);
                        if (_customIndicators.ContainsKey(security.Symbol))
                            _customIndicators.Remove(security.Symbol);

#if JO_DEBUG
                        Log(string.Format("Security.Invested: Securities removed: {0} ", security));
#endif
                    }
                }
            }
        }
        /// <summary>
        /// Get SP500 symbols through the years
        /// Credits to AutomatedMachine
        /// </summary>
        /// <param name="dateTime"></param>
        /// <returns></returns>
        public List<string> GetSP500SymbolList(DateTime dateTime)
        {
            if (LiveMode)
                return _sp500FixedStockList;

            if (_sp500List == null)
            {
                _sp500List = new Dictionary<DateTime, List<string>>();
                //url with sp500 csv file
                string url = @"https://docs.google.com/spreadsheets/d/12lUYUHYWNYhLBvJGcsXp2ZBXdJ7g3-Sspwvg0L62NAk/gviz/tq?tqx=out:csv&sheet=Backtest_CSV";
                using (var client = new WebClient())
                {
                    // fetch the file from dropbox
                    var file = client.DownloadString(url);
                    file = Regex.Replace(file, "\"", string.Empty);
                    // split the file into lines and add to our cache
                    foreach (var line in file.Split(new[] { '\n', '\r' }, StringSplitOptions.RemoveEmptyEntries))
                    {
                        var csv = line.ToCsv();
                        var date = DateTime.ParseExact(csv[0], "yyyy-MM-dd", null); //2017-09-18
                        var symbols = csv.Skip(1).ToList();
                        _sp500List[date] = symbols;
                    }
                }
            }
            var result =  _sp500List.OrderByDescending(x => x.Key).FirstOrDefault(x => x.Key < dateTime).Value;
            if(result == null)
                result = _sp500List.OrderBy(x => x.Key).FirstOrDefault().Value;

            return result;
        }
        // Fire plotting events once per day:
        public override void OnEndOfDay()
        {
            if (Portfolio.TotalPortfolioValue > 0)
            {
                var accountLeverage = Portfolio.TotalAbsoluteHoldingsCost / Portfolio.TotalPortfolioValue;
                Plot("Leverage", "Leverage", accountLeverage);
            }

            if (!Securities.ContainsKey(Spy))
            {
                base.Log(string.Format("!data.ContainsKey({0}) ", Spy));
                return;
            }
#if JO_DEBUG
            //- Rule 08: If the SP500 is above the 200 days moving average we buy stocks, otherwise not;
            if (Securities[Spy].Price <= _spyMovingAverage)
            {
                //$TODO: buy T-note/ gold/ silver?
                if (_isLogSpyMovingAveragePivot >= 0)
                {
                    Log(string.Format("Spy in downtrend: {0} < {1}", Securities[Spy].Price, _spyMovingAverage));
                    _isLogSpyMovingAveragePivot = -1;
                }
            }
            else
            {
                if (_isLogSpyMovingAveragePivot <= 0)
                {
                    Log(string.Format("Spy in uptrend: {0} > {1}", Securities[Spy].Price, _spyMovingAverage));
                    _isLogSpyMovingAveragePivot = 1;
                }
            }
#endif

            ///Plotting
            //Assuming daily mode,dont chart in a smaller res and kill quota
            if (IsPlotting)
            {
                if (Securities.ContainsKey(Spy))
                {
                    Plot(Spy, "Price", Securities[Spy].Close);
                    if (_spyMovingAverage.IsReady)
                    {
                        Plot(Spy, _spyMovingAverage);                        
                    }
                    if(_spyBollingerBands.IsReady)
                    {
                        Plot(Spy, _spyBollingerBands.UpperBand, _spyBollingerBands.MiddleBand, _spyBollingerBands.LowerBand);
                    }
                }
            }
        }
    }
}