Overall Statistics
Total Trades
23008
Average Win
0.52%
Average Loss
-0.45%
Compounding Annual Return
322.531%
Drawdown
70.300%
Expectancy
0.157
Net Profit
110450.360%
Sharpe Ratio
3.194
Probabilistic Sharpe Ratio
93.394%
Loss Rate
47%
Win Rate
53%
Profit-Loss Ratio
1.16
Alpha
3.043
Beta
0.269
Annual Standard Deviation
0.96
Annual Variance
0.922
Information Ratio
3.079
Tracking Error
0.968
Treynor Ratio
11.38
Total Fees
$2756848.24
Estimated Strategy Capacity
$8000000.00
Lowest Capacity Asset
PINS X3RPXTZRW09X
#region imports
    using System;
    using System.Collections.Generic;
    using System.Linq;
    using QuantConnect.Algorithm.Framework.Selection;
    using QuantConnect.Util;
    using QuantConnect.Indicators;
    using QuantConnect.Data;
    using QuantConnect.Data.Fundamental;
    using QuantConnect.Data.Market;
    using QuantConnect.Data.UniverseSelection;
    using QuantConnect.Orders.Fees;
    using QuantConnect.Scheduling;
    using QuantConnect.Securities;
#endregion

namespace QuantConnect.Algorithm.CSharp
{
    public class Flock : QCAlgorithm
    {
		List<Symbol> HighDollarVolumeStocks = new List<Symbol>();
		int _coarse_count = 250;
		int _stocks_to_hold = 10;
		int _period = 5;
		decimal Leverage = 0.99m;
		decimal Threshold = 0.0m;
        private bool _selection_flag = false;

        private Dictionary<Symbol, RollingWindow<decimal>> _data = new Dictionary<Symbol, RollingWindow<decimal>>();
        private Dictionary<Symbol, float> _weight = new Dictionary<Symbol, float>();
        private Dictionary<Symbol, decimal> _price = new Dictionary<Symbol, decimal>();
		
        public override void Initialize()
        {
            UniverseSettings.Resolution = Resolution.Minute;

            SetStartDate(2018, 1, 1);
            SetCash(100000);

            AddUniverseSelection(new FineFundamentalUniverseSelectionModel(CoarseSelectionFunction, FineSelectionFunction));
            Symbol symbol = AddEquity("SPY", Resolution.Minute).Symbol;
            
            SetSecurityInitializer((Security security) => security.SetMarketPrice(GetLastKnownPrice(security)));

            // selection lambda function
            Schedule.On(DateRules.MonthStart(symbol), TimeRules.AfterMarketOpen(symbol), () =>
            {
                _selection_flag = true;
            });
        }

        public override void OnSecuritiesChanged(SecurityChanges changes)
        {
            foreach (Security security in changes.AddedSecurities){
                security.SetFeeModel(new CustomFeeModel());
                security.SetLeverage(10);
                security.SetMarketPrice(GetLastKnownPrice(security));
            }

            // liquidate first
            Symbol[] invested = (from x in Portfolio where x.Value.Invested select x.Key).ToArray();
            foreach (Symbol symbol in invested)
                if (!_weight.ContainsKey(symbol))
                    MarketOnOpenOrder(symbol, -Portfolio[symbol].Quantity);
                    // Liquidate(symbol);

            // trade execution
            foreach (KeyValuePair<Symbol, float> item in _weight)
            {
                // SetHoldings(item.Key, item.Value);
                decimal q = (Portfolio.TotalPortfolioValue * (decimal)item.Value) / _price[item.Key]; //#data[item.Key].Value;
                MarketOnOpenOrder(item.Key, q);
            }
            
            _weight.Clear();
            _price.Clear();
        }

        IEnumerable<Symbol> CoarseSelectionFunction(IEnumerable<CoarseFundamental> coarse)
        {
            // store daily stock prices
            foreach(CoarseFundamental stock in coarse){
                Symbol symbol = stock.Symbol;
                if (_data.ContainsKey(symbol))
                    _data[symbol].Add(stock.AdjustedPrice);
            }

            // if (!_selection_flag)
            //     return Universe.Unchanged;
            
            Symbol[] selected = coarse.Where(x=> x.Symbol.Value != "AMC" && 
                x.Symbol.Value != "GME" && x.Symbol.Value != "UVXY").OrderByDescending(x=>x.DollarVolume).Select(x=>x.Symbol).Take(_coarse_count).ToArray<Symbol>();
            
            foreach (Symbol symbol in selected){
                if (_data.ContainsKey(symbol))
                    continue;
                
                _data[symbol] = new RollingWindow<decimal>((int)_period);
                IEnumerable<TradeBar> history = History<TradeBar>(symbol, _period, Resolution.Daily);
                if (history.IsNullOrEmpty()){
                    Log($"Not enough data for {symbol} yet");
                    continue;
                }

                foreach (TradeBar bar in history) {
                   _data[symbol].Add(bar.Close);
                }
                
                // TEST
                // IEnumerable<TradeBar> m_history = History<TradeBar>(symbol, _period, Resolution.Minute);
            }

            return selected.Where(x=>_data[x].IsReady).ToList<Symbol>();
        }

        IEnumerable<Symbol> FineSelectionFunction(IEnumerable<FineFundamental> fine)
        {
            // List<Symbol> fine_symbols = fine.Select(x=>x.Symbol).ToList<Symbol>();

            Dictionary<Symbol, decimal> avg_ratio = new Dictionary<Symbol, decimal>();

            foreach (FineFundamental stock1 in fine)
            {
                Symbol symbol1 = stock1.Symbol;

                decimal[] prices1 = _data[symbol1].ToArray<decimal>();
                decimal averagePrice1 = prices1.Average();
                decimal[] normalizedPrices1 = prices1.Select(x => x / averagePrice1).ToArray();
                decimal sumRatios = 0;

                foreach (FineFundamental stock2 in fine)
                {
                    Symbol symbol2 = stock2.Symbol;

                    if (symbol1 != symbol2)
                    {
                        decimal[] prices2 = _data[symbol2].ToArray<decimal>();
                        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.First() - normalizedPrices2.First();
                        
                        if (differenceRange != 0)
                        {
                            decimal ratio = currentDifference / differenceRange;
                            sumRatios += ratio;
                        }
                    }
                }
                decimal avg_ratio_value = sumRatios / (fine.Count() - 1);
                if (avg_ratio_value != 0)
                {
                    avg_ratio[symbol1] = avg_ratio_value;
                    _price[symbol1] = prices1.First();
                }
            }
            
            Symbol[] _long = new Symbol[] {};

            if (avg_ratio.Count >= _stocks_to_hold)
            {
                _long = avg_ratio.OrderByDescending(x => Math.Abs(x.Value)).ToArray().Take(_stocks_to_hold).Select(x=>x.Key).ToArray<Symbol>();
                foreach(Symbol symbol in _long)
                {
                    if (avg_ratio[symbol] < 0)
                        _weight[symbol] = 1.0f / (float)(_long.Count());
                        
                    else if (avg_ratio[symbol] > 0)
                        _weight[symbol] = -1.0f / (float)(_long.Count());
                }
            }

            return _weight.Keys;
        }

        public void OnData(TradeBars data)
        {
            return;
            // liquidate first
            // Symbol[] invested = (from x in Portfolio where x.Value.Invested select x.Key).ToArray();
            // foreach (Symbol symbol in invested)
            //     if (!_weight.ContainsKey(symbol))
            //         Liquidate(symbol);

            // // trade execution
            // foreach (KeyValuePair<Symbol, float> item in _weight)
            //     if(data.ContainsKey(item.Key))
            //     {
            //         // SetHoldings(item.Key, item.Value);
            //         decimal q = (Portfolio.TotalPortfolioValue * (decimal)item.Value) / data[item.Key].Value;
            //         MarketOnOpenOrder(item.Key, q);
            //     }
            
            // _weight.Clear();
        }
    }
    
    public class CustomFeeModel : FeeModel {
        public override OrderFee GetOrderFee(OrderFeeParameters parameters) {
            // custom fee math
            var fee = parameters.Security.Price * parameters.Order.AbsoluteQuantity * 0.00005m;
            return new OrderFee(new CashAmount(fee, "USD"));
        }
    }
}