Overall Statistics
Total Trades
1461
Average Win
1.49%
Average Loss
-0.87%
Compounding Annual Return
915.575%
Drawdown
22.500%
Expectancy
0.395
Net Profit
961.742%
Sharpe Ratio
7.768
Probabilistic Sharpe Ratio
98.893%
Loss Rate
48%
Win Rate
52%
Profit-Loss Ratio
1.70
Alpha
5.097
Beta
0.273
Annual Standard Deviation
0.662
Annual Variance
0.439
Information Ratio
7.453
Tracking Error
0.666
Treynor Ratio
18.875
Total Fees
$2602.97
Estimated Strategy Capacity
$6500000.00
Lowest Capacity Asset
LLY R735QTJ8XC9X
//Copyright HardingSoftware.com. Granted to the public domain.
//Use entirely at your own risk.
using System;
using System.Collections.Generic;
using System.Linq;
using QuantConnect.Data.Market;
using QuantConnect.Data.UniverseSelection;

namespace QuantConnect.Algorithm.CSharp
{
    public class Flock : QCAlgorithm
    {
		List<StockData> HighDollarVolumeStocks = new List<StockData>();
		int TotalHighDollarVolumeStocks = 50;
		int TotalStocksToHold = 5;
		//Resolution Resolution = Resolution.Daily;
		int Period = 3;
		decimal Leverage = 0.99m;
		decimal Threshold = 0.0m;
		public DateTime LastTime = new DateTime();
		
        public override void Initialize()
        {
            UniverseSettings.Resolution = Resolution.Minute;

            SetStartDate(2021, 01, 01);
            SetCash(10000);

            AddUniverse(coarse =>
            {
            	return (from stock in coarse
            			where stock.Symbol.ToString().Substring(0, 3) != "GME"
            			where stock.Symbol.ToString().Substring(0, 3) != "AMC"
            			where stock.Symbol.ToString().Substring(0, 4) != "UVXY"
            			//where stock.HasFundamentalData == false
            			orderby stock.DollarVolume descending  
            			select stock.Symbol).Take(TotalHighDollarVolumeStocks);
            });
        }
        
        public void OnEndOfDay()
		{
		    foreach (StockData stockData in HighDollarVolumeStocks)
            {
				stockData.DailyCandles = Group(stockData.MinuteCandles, 390);
            }
		}

        public void OnData(TradeBars data)
        {
            foreach (StockData stockData in HighDollarVolumeStocks)
            {
            	if (data.ContainsKey(stockData.Symbol))
            	{
	            	TradeBar bar = data[stockData.Symbol];
	            	stockData.MinuteCandles.Add(bar);
	            	if (stockData.MinuteCandles.Count > Period * 390)
	            	{
	            		stockData.MinuteCandles.RemoveAt(0);
	            	}
            	}
            }
            
            if (Time.Day == LastTime.Day)
            {
            	return;
            }
            LastTime = Time;
            
            foreach (StockData stockData1 in HighDollarVolumeStocks)
            {
        	    List<TradeBar> candles1 = stockData1.DailyCandles;
                decimal[] prices1 = candles1.Select(x => x.Close).ToArray();
                decimal averagePrice1 = prices1.Average();
                decimal[] normalizedPrices1 = prices1.Select(x => x / averagePrice1).ToArray();
                decimal sumRatios = 0;
                foreach (StockData stockData2 in HighDollarVolumeStocks)
                {
                	if (stockData1 != stockData2)
                	{
	                    List<TradeBar> candles2 = stockData2.DailyCandles;
	                    decimal[] prices2 = candles2.Select(x => x.Close).ToArray();
	                    decimal averagePrice2 = prices2.Average();
	                    decimal[] normalizedPrices2 = prices2.Select(x => x / averagePrice2).ToArray();
	                    decimal[] differences = normalizedPrices1.Zip(normalizedPrices2, (x, y) => x - y).ToArray();
	                    decimal maxDifference = differences.Max();
	                    decimal minDifference = differences.Min();
	                    decimal differenceRange = maxDifference - minDifference;
	                    decimal currentDifference = normalizedPrices1.Last() - normalizedPrices2.Last();
	                    if (differenceRange != 0)
	                    {
		                    decimal ratio = currentDifference / differenceRange;
		                    sumRatios += ratio;
	                    }
                	}
                }
                stockData1.AverageRatio = sumRatios / (HighDollarVolumeStocks.Count - 1);
            }
            
            List<StockData> stocksToHold = HighDollarVolumeStocks.OrderByDescending(x => Math.Abs(x.AverageRatio)).Take(TotalStocksToHold).ToList();
            
            foreach (var security in Portfolio.Values)
            {
            	if (Portfolio[security.Symbol].Invested)
            	{
	                if (stocksToHold.Exists(x => x.Symbol == security.Symbol) == false)
	                {
	                    Liquidate(security.Symbol);
	                }
            	}
            }
            
            foreach (StockData stockData in stocksToHold)
            {
                if (stockData.AverageRatio < -Threshold && Portfolio[stockData.Symbol].Quantity <= 0)
                {
                    SetHoldings(stockData.Symbol, Leverage / (decimal)TotalStocksToHold);
                }
                else if (stockData.AverageRatio > Threshold && Portfolio[stockData.Symbol].Quantity >= 0)
                {
					SetHoldings(stockData.Symbol, -Leverage / (decimal)TotalStocksToHold);
                }
            }

        }

		public class StockData
		{
			public Symbol Symbol;
			public List<TradeBar> MinuteCandles = new List<TradeBar>();
			public List<TradeBar> DailyCandles = new List<TradeBar>();
            public decimal AverageRatio;
		}

        public override void OnSecuritiesChanged(SecurityChanges changes)
        {
            foreach (var security in changes.RemovedSecurities)
            {
            	StockData stockData = HighDollarVolumeStocks.Find(x => x.Symbol == security.Symbol);
            	if (stockData != null)
            	{
                	HighDollarVolumeStocks.Remove(stockData);
            	}
            }
            foreach (var security in changes.AddedSecurities)
            {
            	StockData stockData = new StockData();
            	stockData.Symbol = security.Symbol;
            	stockData.MinuteCandles = History(stockData.Symbol, Period * 390, Resolution.Minute).ToList();
				stockData.DailyCandles = History(stockData.Symbol, Period, Resolution.Daily).ToList();
				HighDollarVolumeStocks.Add(stockData);
            }
        }
        
        public static List<TradeBar> Group(List<TradeBar> candles, int binWidth)
        {
            List<TradeBar> outCandles = new List<TradeBar>();
            for (int start = 0; start < candles.Count; start += binWidth)
            {
                List<TradeBar> group = candles.GetRange(start, binWidth);
                TradeBar newCandle = new TradeBar();
                newCandle.Time = group.First().Time;
                newCandle.Open = group.First().Open;
                newCandle.Close = group.Last().Close;
                newCandle.High = group.Select(x => x.High).Max();
                newCandle.Low = group.Select(x => x.Low).Min();
                newCandle.Volume = group.Select(x => x.Volume).Sum();
                outCandles.Add(newCandle);
            }
            return outCandles;
        }
        
    }
}