Overall Statistics
Total Trades
10149
Average Win
0.58%
Average Loss
-0.53%
Compounding Annual Return
59.129%
Drawdown
67.700%
Expectancy
0.053
Net Profit
185.779%
Sharpe Ratio
1.109
Probabilistic Sharpe Ratio
38.361%
Loss Rate
50%
Win Rate
50%
Profit-Loss Ratio
1.09
Alpha
0.647
Beta
0.49
Annual Standard Deviation
0.67
Annual Variance
0.448
Information Ratio
0.818
Tracking Error
0.67
Treynor Ratio
1.515
Total Fees
$48987.54
Estimated Strategy Capacity
$6700000.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.Consolidators;
using QuantConnect.Data.Market;
using QuantConnect.Data.UniverseSelection;
using QuantConnect.Securities;

namespace QuantConnect.Algorithm.CSharp
{
    public class Flock : QCAlgorithm
    {
        List<StockData> HighDollarVolumeStocks = new List<StockData>();
        int TotalHighDollarVolumeStocks = 250;
        int TotalStocksToHold = 10;
        Resolution Resolution = Resolution.Daily;
        int Period = 5;
        decimal Leverage = 0.99m;
        decimal Threshold = 0.0m;

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

            SetStartDate(2019, 9, 27);
            SetCash(100000);          

            AddUniverse(coarse =>
            {
                return (from stock in coarse
                        where stock.Symbol.Value.Substring(0, stock.Symbol.Value.Length) != "GME"
                        where stock.Symbol.Value.Substring(0, stock.Symbol.Value.Length) != "AMC"
                        where stock.Symbol.Value.Substring(0, stock.Symbol.Value.Length) != "UVXY"
                        //where stock.HasFundamentalData == false
                        orderby stock.DollarVolume descending
                        select stock.Symbol).Take(TotalHighDollarVolumeStocks);
            });
        }

        public void OnConsolidatedDaily(TradeBar bar)
        {
            StockData stockData = HighDollarVolumeStocks.Find(x => x.Symbol == bar.Symbol);

            stockData.Candles.Add(bar);
            if (stockData.Candles.Count > Period)
            {
                stockData.Candles.RemoveAt(0);
            }
        }

        public void OnData(TradeBars data)
        {
            if (this.Time.Hour == 9 && this.Time.Minute == 31)
                Rebalance(data);
        }

        public void Rebalance(TradeBars data)
        {
            foreach (StockData stockData1 in HighDollarVolumeStocks)
            {
                if (stockData1.Candles.Count == 0)
                    continue;

                List<TradeBar> candles1 = stockData1.Candles;
                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 && stockData2.Candles.Count > 0)
                    {
                        List<TradeBar> candles2 = stockData2.Candles;
                        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);
            }

            // We only want to get tradeable stocks
            List<StockData> tradeableStocks = HighDollarVolumeStocks.Where(x => x.Security.IsTradable && data.ContainsKey(x.Symbol)).ToList();
            List<StockData> stocksToHold = tradeableStocks.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 Security Security;
            public List<TradeBar> Candles = new List<TradeBar>();
            public decimal AverageRatio;
            public QCAlgorithm _algo;
            public IDataConsolidator Consolidator1d;

            public StockData(Symbol symbol, QCAlgorithm algo)
            {
                _algo = algo;
                Symbol = symbol;
                Consolidator1d = algo.Consolidate(symbol, Resolution.Daily, ((Flock)algo).OnConsolidatedDaily);
            }
        }

        public override void OnSecuritiesChanged(SecurityChanges changes)
        {
            foreach (var security in changes.RemovedSecurities)
            {
                StockData stockData = HighDollarVolumeStocks.Find(x => x.Symbol == security.Symbol);
                if (stockData != null)
                {
                    SubscriptionManager.RemoveConsolidator(stockData.Symbol, stockData.Consolidator1d);
                    HighDollarVolumeStocks.Remove(stockData);
                }
            }
            foreach (var security in changes.AddedSecurities)
            {
                StockData stockData = new StockData(security.Symbol, this);
                stockData.Symbol = security.Symbol;
                stockData.Security = security;
                // Get daily data for calculations
                stockData.Candles = History(stockData.Symbol, Period, Resolution).ToList();
                // Warmup Minute data so we don't get errors
                History(stockData.Symbol, 390, Resolution.Minute);
                HighDollarVolumeStocks.Add(stockData);
            }
        }

    }
}