Overall Statistics
Total Trades
1237
Average Win
1.00%
Average Loss
-0.90%
Compounding Annual Return
28.225%
Drawdown
39.400%
Expectancy
0.218
Net Profit
247.573%
Sharpe Ratio
0.995
Probabilistic Sharpe Ratio
38.496%
Loss Rate
42%
Win Rate
58%
Profit-Loss Ratio
1.11
Alpha
0.118
Beta
1.246
Annual Standard Deviation
0.276
Annual Variance
0.076
Information Ratio
0.832
Tracking Error
0.179
Treynor Ratio
0.22
Total Fees
$24126.68
//Copyright Warren Harding 2020.
//Granted to the public domain.
//Use at your own risk.

namespace QuantConnect.Algorithm.CSharp
{
    public class FactorOfTheDay : QCAlgorithm
    {
		int TotalHighDollarVolumeStocks = 10;
		int TotalStocksToHold = 5;
		int FactorPeriod = 20;
		List<Symbol> LongSymbols = new List<Symbol>();
		List<decimal> FCFYieldFitnesses = new List<decimal>();
		List<decimal> BookYieldFitnesses = new List<decimal>();
		List<decimal> EarningYieldFitnesses = new List<decimal>();
		List<decimal> SalesYieldFitnesses = new List<decimal>();
		List<decimal> CFYieldFitnesses = new List<decimal>();
		
        public override void Initialize()
        {
            SetStartDate(2015, 11, 19);
            SetCash(1000000);
			AddUniverse(CoarseSelectionFunction, FineSelectionFunction);
        }
        
        public IEnumerable<Symbol> CoarseSelectionFunction(IEnumerable<CoarseFundamental> coarse)
        {
            return coarse
						.Where(x => x.HasFundamentalData)
						.OrderByDescending(x => x.DollarVolume)
						.Take(TotalHighDollarVolumeStocks)
            			.Select(x => x.Symbol);
        }
        
        public IEnumerable<Symbol> FineSelectionFunction(IEnumerable<FineFundamental> fine)
        {
        	List<StockData> stockDatas = new List<StockData>();
        	foreach (FineFundamental f in fine)
        	{
				List<TradeBar> history = History(f.Symbol, 2, Resolution.Daily).ToList();
				if (history != null && history.Count == 2)
				{
					StockData stockData = new StockData();
					stockData.Change = (history.Last().Close - history.First().Close) / history.First().Close;
					stockData.FCFYield = f.ValuationRatios.FCFYield;
					stockData.BookYield = f.ValuationRatios.BookValueYield;
					stockData.EarningYield = f.ValuationRatios.EarningYield;
					stockData.SalesYield = f.ValuationRatios.SalesYield;
					stockData.CFYield = f.ValuationRatios.CFYield;
					stockData.Symbol = f.Symbol;
					stockDatas.Add(stockData);
				}
        	}
        	
        	decimal[] changes = stockDatas.Select(x => x.Change).ToArray();
        	decimal[] fcfYields = stockDatas.Select(x => x.FCFYield).ToArray();
        	decimal[] bookYields = stockDatas.Select(x => x.BookYield).ToArray();
        	decimal[] earningYields = stockDatas.Select(x => x.EarningYield).ToArray();
        	decimal[] salesYields = stockDatas.Select(x => x.SalesYield).ToArray();
        	decimal[] cfYields = stockDatas.Select(x => x.CFYield).ToArray();
        	
        	decimal fcfYieldFitness = WeightedAverage(changes, fcfYields);
        	decimal bookYieldFitness = WeightedAverage(changes, bookYields);
        	decimal earningYieldFitness = WeightedAverage(changes, earningYields);
        	decimal salesYieldFitness = WeightedAverage(changes, salesYields);
        	decimal cfYieldFitness = WeightedAverage(changes, cfYields);
        	
        	FCFYieldFitnesses.Add(fcfYieldFitness);
        	BookYieldFitnesses.Add(bookYieldFitness);
        	EarningYieldFitnesses.Add(earningYieldFitness);
        	SalesYieldFitnesses.Add(salesYieldFitness);
        	CFYieldFitnesses.Add(cfYieldFitness);
        	
        	Remove0(FCFYieldFitnesses);
        	Remove0(BookYieldFitnesses);
        	Remove0(EarningYieldFitnesses);
        	Remove0(SalesYieldFitnesses);
        	Remove0(CFYieldFitnesses);
        	
        	decimal fcfYieldFitnessTMA = TriangularMovingAverage(FCFYieldFitnesses.ToArray());
        	decimal bookYieldFitnessTMA = TriangularMovingAverage(BookYieldFitnesses.ToArray());
        	decimal earningYieldFitnessTMA = TriangularMovingAverage(EarningYieldFitnesses.ToArray());
        	decimal salesYieldFitnessTMA = TriangularMovingAverage(SalesYieldFitnesses.ToArray());
        	decimal cfYieldFitnessTMA = TriangularMovingAverage(CFYieldFitnesses.ToArray());
        	
        	foreach (StockData stockData in stockDatas)
        	{
        		stockData.Fitness = stockData.FCFYield * fcfYieldFitnessTMA
        							+ stockData.BookYield * bookYieldFitnessTMA
        							+ stockData.EarningYield * earningYieldFitnessTMA
        							+ stockData.SalesYield * salesYieldFitnessTMA
        							+ stockData.CFYield * cfYieldFitnessTMA;
        	}

            LongSymbols = stockDatas.OrderByDescending(x => x.Fitness)
					.Take(TotalStocksToHold)
					.Select(x => x.Symbol)
					.ToList();
					
            return LongSymbols;
        }
        
        public void Remove0(List<decimal> list)
        {
        	if (list.Count > FactorPeriod)
        	{
        		list.RemoveAt(0);
        	}
        }

        public override void OnData(Slice data)
        {
			if (data.Time.DayOfWeek != DayOfWeek.Wednesday)
			{
				return;
			}
			foreach (var stock in Portfolio.Values)
			{
				if (stock.Invested)
				{
					if (LongSymbols.Exists(x => x == stock.Symbol) == false)
					{
						Liquidate(stock.Symbol);
					}
				}
			}
			
			decimal positionSize = 1m / TotalStocksToHold;
			foreach (Symbol symbol in LongSymbols)
			{
				if (Portfolio[symbol].Invested == false && data.ContainsKey(symbol))
				{
					SetHoldings(symbol, positionSize);
				}
			}
        }
        
        public class StockData
        {
        	public Symbol Symbol;
        	public decimal Change;
        	public decimal FCFYield;
        	public decimal BookYield;
        	public decimal EarningYield;
        	public decimal SalesYield;
        	public decimal CFYield;
        	public decimal Fitness;
        }
        
        public static decimal WeightedAverage(decimal[] values, decimal[] weights)
        {
        	decimal weightsSum = weights.Sum();
        	if (weightsSum != 0)
        	{
            	return values.Zip(weights, (x, y) => x * y).Sum() / weights.Sum();
        	}
        	else
        	{
        		return 0;
        	}
        }
        
        public static decimal TriangularMovingAverage(decimal[] values)
        {
            return WeightedAverage(values, TriangularWeightsDecimal(values.Length));
        }
        
        public static decimal[] TriangularWeightsDecimal(int length)
        {
            int[] intWeights = Enumerable.Range(1, length).ToArray();
            return intWeights.Select(x => Convert.ToDecimal(x)).ToArray();
        }
    }
}