Overall Statistics
using System;
using System.Collections.Concurrent;
using System.Collections.Generic;
using System.Linq;
using QuantConnect.Data.Fundamental;
using QuantConnect.Data.Market;
using QuantConnect.Data.UniverseSelection;
using QuantConnect.Indicators;

namespace QuantConnect.Algorithm.CSharp
{
    /// <summary>
    // Select stocka with: 
    // price > 10 dollars
    // fast EMA above slow EMA
    // EBITDAMargin.OneYear > 0.1 AND RevenueGrowth.OneYear > 0.1 AND DilutedContEPSGrowth.OneYear > 0.1 AND USA
    // Order by DilutedContEPSGrowth
    /// </summary>
    public class FundamentalAndMaAlgorithm : QCAlgorithm
    {
        private readonly decimal minGrossMargin = 0.4m;
        private readonly decimal minRevenueGrowth = 0.15m;
        private readonly decimal minDilutedContEPSGrowth = 0.2m;

        // minimal stock price in dollars
        decimal minStockPrice = 10;

        // EMA tolerance to prevent bouncing
        const decimal Tolerance = 0.02m;

        int numberOfStocks = 1;

        static int daysInSlowEma = 50;
        static int daysInFastEma = 10;

        private readonly int takeByDollarVolume = 200;

        // moving averages by stock symbol
        private readonly ConcurrentDictionary<Symbol, MovingAverages> averages = new ConcurrentDictionary<Symbol, MovingAverages>();

        List<Symbol> selectedByFundamentals = new List<Symbol>();

        private class MovingAverages
        {
            public readonly ExponentialMovingAverage Fast;
            public readonly ExponentialMovingAverage Slow;

            public MovingAverages()
            {
                Fast = new ExponentialMovingAverage(daysInFastEma);
                Slow = new ExponentialMovingAverage(daysInSlowEma);
            }

            // computes an object score of how much large the fast is than the slow
            public decimal ScaledDelta
            {
                get { return (Fast - Slow) / ((Fast + Slow) / 2m); }
            }

            // updates the EMA50 and EMA100 indicators, returning true when they're both ready
            public bool Update(DateTime time, decimal value)
            {
                return Fast.Update(time, value) && Slow.Update(time, value);
            }
        }

        Symbol benchmark = QuantConnect.Symbol.Create("SPY", SecurityType.Equity, Market.USA);
        Symbol bonds = QuantConnect.Symbol.Create("TLT", SecurityType.Equity, Market.USA);

        public override void Initialize()
        {
            UniverseSettings.Resolution = Resolution.Daily;

            SetStartDate(2017, 4, 1);
            SetEndDate(2018, 4, 1);
            SetCash(50000);

            AddUniverse(CoarseSelectionFunction, FineSelectionFunction);
            AddEquity(bonds.Value, Resolution.Daily);

            this.SetBenchmark(benchmark);
            this.Log("orderby DilutedContEPSGrowth");
            this.Log("minGrossMargin=" + minGrossMargin);
            this.Log("minRevenueGrowth=" + minRevenueGrowth);
            this.Log("minDilutedContEPSGrowth=" + minDilutedContEPSGrowth);
            this.Log("minStockPrice=" + minStockPrice);
            this.Log("Tolerance=" + Tolerance);
            this.Log("numberOfStocks=" + numberOfStocks);
            this.Log("daysInSlowEma=" + daysInSlowEma);
            this.Log("daysInFastEma=" + daysInFastEma);
            this.Log("takeByDollarVolume=" + takeByDollarVolume);
        }

        public IEnumerable<Symbol> CoarseSelectionFunction(IEnumerable<CoarseFundamental> coarse)
        {
            var selectedByEma = SelectByEma(coarse);

            return selectedByEma.OrderByDescending(stock => stock.DollarVolume).Take(this.takeByDollarVolume).Select(stock => stock.Symbol);
        }

        int numberOfBonds = 0;

        public IEnumerable<Symbol> FineSelectionFunction(IEnumerable<FineFundamental> fine)
        {
            this.numberOfBonds = 0;
            this.selectedByFundamentals.Clear();
            this.selectedByFundamentals.AddRange(SelectByFundamentals(fine).Select(stock => stock.Symbol));

            if (selectedByFundamentals.Count() < this.numberOfStocks)
            {
                // nothing to invest in, invest in bonds
                this.numberOfBonds = this.numberOfStocks - selectedByFundamentals.Count();
                selectedByFundamentals.Add(this.bonds);
            }

            return selectedByFundamentals;
        }

        private IEnumerable<FineFundamental> SelectByFundamentals(IEnumerable<FineFundamental> selectedByMa)
        {
            return (from stock in selectedByMa
                    where stock.CompanyReference.CountryId == "USA"
                    where !stock.CompanyReference.IsLimitedPartnership
                    where !stock.CompanyReference.IsREIT
                    where stock.OperationRatios.GrossMargin.OneYear > this.minGrossMargin
                    where stock.OperationRatios.RevenueGrowth.OneYear > this.minRevenueGrowth
                    where stock.EarningRatios.DilutedContEPSGrowth.OneYear > this.minDilutedContEPSGrowth
                    where stock.OperationRatios.GrossMargin5YrAvg > this.minGrossMargin
                    where stock.OperationRatios.GrossProfitAnnual5YrGrowth > this.minDilutedContEPSGrowth
                    orderby stock.EarningRatios.DilutedContEPSGrowth.OneYear descending
                    select stock).Take(this.numberOfStocks);
        }

        // select stocks with fundamental data and price > 10 dollars
        // fast EMA is above slow EMA
        private List<CoarseFundamental> SelectByEma(IEnumerable<CoarseFundamental> coarse)
        {
            List<CoarseFundamental> selected = new List<CoarseFundamental>();

            // select stocks with fast EMA above slow EMA

            foreach (CoarseFundamental stock in coarse)
            {
                if (stock.Price > this.minStockPrice)
                {
                    if (stock.HasFundamentalData)
                    {

                        // create or update averages for the stock
                        MovingAverages average = null;
                        if (this.averages.ContainsKey(stock.Symbol))
                        {
                            this.averages.TryGetValue(stock.Symbol, out average);
                        }
                        else
                        {
                            average = new MovingAverages();
                            this.averages.TryAdd(stock.Symbol, average);
                        }

                        // if indicators are ready
                        if (average.Update(stock.EndTime, stock.Price))
                        {
                            // if fast EMA is above slow EMA
                            if (stock.Price > average.Slow * (1 + Tolerance))
                            {
                                selected.Add(stock);
                            }
                        }
                    }
                }
            }

            return selected;
        }

        public void OnData(TradeBars data)
        {
            LiquidateNotSelected();

            InvestSelected();
        }

        private void InvestSelected()
        {
            // invest in securities that are in lists
            if (this.Portfolio.Cash > 0m)
            {
                if (this.selectedByFundamentals.Count() > 0)
                {
                    foreach (Symbol security in this.selectedByFundamentals)
                    {
                        // do not trade benchmark and bonds here
                        if (!security.Equals(this.benchmark) && !security.Equals(this.bonds))
                        {
                            if (this.Portfolio.Securities[security].IsTradable)
                            {
                                decimal holdings = 1m / this.numberOfStocks;
                                this.Log("Buy:" + security.Value + " " + holdings);

                                this.SetHoldings(security, holdings);
                            }
                        }
                    }

                    if (this.numberOfBonds > 0)
                    {
                        decimal holdings = (decimal)numberOfBonds / this.numberOfStocks;
                        this.Log("Buy bonds:" + this.bonds.Value + " " + holdings);

//                        this.SetHoldings(this.bonds, holdings);
                    }
                }
            }
        }

        private void LiquidateNotSelected()
        {
            // liquidate securities not in the list
            IEnumerable<Symbol> toLiquidate = this.Portfolio.Securities.Keys.Except(this.selectedByFundamentals);
            foreach (Symbol security in toLiquidate)
            {
                // do not trade benchmark
                if (!security.Equals(this.benchmark))
                {
                    this.Log("Liquidate:" + security.Value);
                    Liquidate(security);
                }
            }
        }
    }
}