Overall Statistics
Total Trades
1277
Average Win
0.90%
Average Loss
-0.67%
Compounding Annual Return
262.335%
Drawdown
26.900%
Expectancy
0.192
Net Profit
114.257%
Sharpe Ratio
3.321
Probabilistic Sharpe Ratio
79.718%
Loss Rate
49%
Win Rate
51%
Profit-Loss Ratio
1.34
Alpha
1.825
Beta
0.268
Annual Standard Deviation
0.554
Annual Variance
0.307
Information Ratio
3.189
Tracking Error
0.559
Treynor Ratio
6.866
Total Fees
$4406.12
Estimated Strategy Capacity
$9800000.00
Lowest Capacity Asset
NTRB XS9YHLR0G02T
//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 = 5;
		//20220130 - idk - so we have 1% leverage? or does this mean we use 99% of the available funds in the portfolio?
		decimal Leverage = 0.99m;
		decimal Threshold = 0.0m;
		public DateTime LastTime = new DateTime();
		
        public override void Initialize()
        {
            UniverseSettings.Resolution = Resolution.Minute;

            SetStartDate(2021, 06, 27);
            SetCash(100000);

            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 == true
            			orderby stock.DollarVolume descending  
            			select stock.Symbol).Take(TotalHighDollarVolumeStocks);
            });
        }
        //this sub is never called in the Algo - probably runs at the end of every day though
        //https://www.quantconnect.com/forum/discussion/315/api-update-onendofday-string-symbol/p1
        public override void OnEndOfDay()
		{
		    foreach (StockData stockData in HighDollarVolumeStocks)
            {
            	//if count of minute candles modulus 390 min == 0 then (do this every 6.5hrs, the stock exchange is open 6.5hrs a day)
            	//	group the candles to a daily candle
            	if (stockData.MinuteCandles.Count % 390 == 0)
            	{
					stockData.DailyCandles = Group(stockData.MinuteCandles, 390);
            	}
            }
		}

        public void OnData(TradeBars data)
        {
        	//loops through HighDollarVolumeStocks
            foreach (StockData stockData in HighDollarVolumeStocks)
            {
            	//checks if data contains the key stockData.Symbol
            	if (data.ContainsKey(stockData.Symbol))
            	{
	            	TradeBar bar = data[stockData.Symbol];
	            	stockData.MinuteCandles.Add(bar);
	            	//20220130 - idk - Period is one of the input Parameters
	            	//               - so every 5th day it is removing the the stocks MinuteCandles?
	            	if (stockData.MinuteCandles.Count > Period * 390)
	            	{
	            		stockData.MinuteCandles.RemoveAt(0);
	            	}
            	}
            }
            
            //20220130 - idk - strange way to end the Algo - this is checkng if Day is 'Now'
            if (Time.Day == LastTime.Day)
            {
            	return;
            }
            
            //20220130 - so we not at today's day so continue
            LastTime = Time;
            
            //20220130 - idk - loop through each HighDollarVolumeStocks StockData called stockData1 (we do this everyminute?)
            foreach (StockData stockData1 in HighDollarVolumeStocks)
            {
            	//20220130 - list of the DailyCandles for a stock 
        	    List<TradeBar> candles1 = stockData1.DailyCandles;
        	    //20220130 - idk - an arry of the close prices - what is (x => x.Close) doing? Maybe this is an
        	    //					array of all the closes in all the candles for this stock
                decimal[] prices1 = candles1.Select(x => x.Close).ToArray();
                decimal averagePrice1 = prices1.Average();
                //Get the normalizes price
                decimal[] normalizedPrices1 = prices1.Select(x => x / averagePrice1).ToArray();
                decimal sumRatios = 0;
                
                //20220130 - idk - looping through the HighDollarVolumeStocks again (a loop within a loop)
                //			This is actually comparing the current stockData1 to all the other stocks in HighDollarVolumeStocks
                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();
	                    //20220130 - idk - making an array with the difference between this stock and all other stocks
	                    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)
	                    {
	                    	//20220130 - idk - setting ratio - how would differenceRange == 0 if we comparing differenct stocks
		                    decimal ratio = currentDifference / differenceRange;
		                    sumRatios += ratio;
	                    }
                	}
                }
                
                stockData1.AverageRatio = sumRatios / (HighDollarVolumeStocks.Count - 1);
            }
            //20220130 - idk -getting list of stocks ordered by AverageRatio - desc order of how different the stock is to the group
            List<StockData> stocksToHold = HighDollarVolumeStocks.OrderByDescending(x => Math.Abs(x.AverageRatio)).Take(TotalStocksToHold).ToList();
            
            
            //20220130 - idk - is the stock is not in the sorted list remove it from the universe
            foreach (var security in Portfolio.Values)
            {
            	if (Portfolio[security.Symbol].Invested)
            	{
	                if (stocksToHold.Exists(x => x.Symbol == security.Symbol) == false)
	                {
	                    Liquidate(security.Symbol);
	                }
            	}
            }
            
            //20220130 - idk - if the stockData show is lower then the group buy it
            //		            and if the stocData is higher then the group sell it
            //20220130 - idk - how is TotalStocksToHold being used to limit it to TotalStocksToHold
            //20220130 - idk - shouldn't this be done once a day?
            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;
		}
		

        //20220130 - idk - when we remove a security remove it from HighDollarVolumeStocks
        //					when we add a security add it and all its lists to StockData and HighDollarVolumeStocks
        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);
            }
        }
        
        
        //20220130 - idk - this is the list function used
        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;
        }
        
    }
}