Overall Statistics
Total Trades
310
Average Win
0.77%
Average Loss
-1.00%
Compounding Annual Return
33.320%
Drawdown
7.000%
Expectancy
0.153
Net Profit
28.879%
Sharpe Ratio
1.822
Probabilistic Sharpe Ratio
77.186%
Loss Rate
35%
Win Rate
65%
Profit-Loss Ratio
0.77
Alpha
0.228
Beta
-0.023
Annual Standard Deviation
0.126
Annual Variance
0.016
Information Ratio
1.367
Tracking Error
0.246
Treynor Ratio
-9.833
Total Fees
$587.22
Estimated Strategy Capacity
$940000.00
Lowest Capacity Asset
SNTG XPY9HNNNXVFP
#region imports
    using System;
    using System.Collections;
    using System.Collections.Generic;
    using System.Linq;
    using System.Globalization;
    using System.Drawing;
    using QuantConnect;
    using QuantConnect.Algorithm.Framework;
    using QuantConnect.Algorithm.Framework.Selection;
    using QuantConnect.Algorithm.Framework.Alphas;
    using QuantConnect.Algorithm.Framework.Portfolio;
    using QuantConnect.Algorithm.Framework.Execution;
    using QuantConnect.Algorithm.Framework.Risk;
    using QuantConnect.Parameters;
    using QuantConnect.Benchmarks;
    using QuantConnect.Brokerages;
    using QuantConnect.Util;
    using QuantConnect.Interfaces;
    using QuantConnect.Algorithm;
    using QuantConnect.Indicators;
    using QuantConnect.Data;
    using QuantConnect.Data.Consolidators;
    using QuantConnect.Data.Custom;
    using QuantConnect.DataSource;
    using QuantConnect.Data.Fundamental;
    using QuantConnect.Data.Market;
    using QuantConnect.Data.UniverseSelection;
    using QuantConnect.Notifications;
    using QuantConnect.Orders;
    using QuantConnect.Orders.Fees;
    using QuantConnect.Orders.Fills;
    using QuantConnect.Orders.Slippage;
    using QuantConnect.Scheduling;
    using QuantConnect.Securities;
    using QuantConnect.Securities.Equity;
    using QuantConnect.Securities.Future;
    using QuantConnect.Securities.Option;
    using QuantConnect.Securities.Forex;
    using QuantConnect.Securities.Crypto;   
    using QuantConnect.Securities.Interfaces;
    using QuantConnect.Storage;
    using QuantConnect.Data.Custom.AlphaStreams;
    using QCAlgorithmFramework = QuantConnect.Algorithm.QCAlgorithm;
    using QCAlgorithmFrameworkBridge = QuantConnect.Algorithm.QCAlgorithm;
#endregion
namespace QuantConnect.Algorithm.CSharp
{
    /// Types of Default Covers
    public enum COVER_TYPE
    {
        COVER_AT_NEXT_OPEN,
        COVER_AT_CLOSE
    }

    /// Used For Grouping Indicator/Price Exit Types For Cleaner Adjustment/Configuration
    /// Doing this to avoid commenting code out becoming overwhelming
    public enum EXIT_TYPE
    {
        HOURLY_EMA_EXIT,
        VWAP_PERCENTAGE_EXTENSIONS,
        VWAP_ATR_EXTENSIONS,
        PERCENT_EXTENSION_FROM_OPEN,
        RELATIVE_ATR_EXTENSION_FROM_OPEN,
    }

    public enum ENTRY_TYPE
    {
        IMMEDIATE_ENTRY,
        SPECIALIZED_OPEN_ENTRY,
    }

    /// Ways in Which We Dynamically Adjust Our Stop
    /// Doing this to avoid commenting code out becoming overwhelming
    public enum DYNAMIC_TIMING_ADJUSTMENT
    {
        PRICE_GREATER_THAN_OPEN, /// Only Adjust at Some Time Point When Price Above Open(BEST INITIAL RESULTS WHEN TESTING SINGLE PARAMETER)
        ALWAYS_ADJUST, /// Always Adjust at Some Time Point
        NEVER /// Never Adjust
    }

    public class GapUpShort : QCAlgorithm
    {
        /** REFERENCE LINKS **/
        // https://www.quantconnect.com/forum/discussion/4010/pre-market-scanning/p1
        // https://www.quantconnect.com/docs/v2/writing-algorithms/reality-modeling/trade-fills/key-concepts
        // https://www.quantconnect.com/docs/v2/writing-algorithms/reality-modeling/trade-fills/supported-models
        // https://www.quantconnect.com/docs/v2/writing-algorithms/reality-modeling/slippage/key-concepts
        // https://www.lean.io/docs/v2/lean-engine/class-reference/FillModel_8cs_source.html
        // https://github.com/QuantConnect/Lean/blob/master/Algorithm.CSharp/CustomModelsAlgorithm.cs
        // https://www.quantconnect.com/forum/discussion/4797/stopmarketorder-fill-price/p1

        /** CONSTANTS **/
        private const string BENCHMARK = "SPY";
        public const string BUY_TAG = "BUY";
        public const string SELL_TAG = "SELL";
        private const decimal MINIMUM_GAP_UP_PERCENTAGE = 29;
        private const decimal MAXIMUM_MARKET_CAP = 1_000_000_000;
        private const decimal MINIMUM_PREMARKET_VOLUME = 500_000;
        private const decimal UNIVERSE_ENTRY_CAP = MAXIMUM_MARKET_CAP * 2;
        private const decimal MINIMUM_OPEN_PRICE = 2;
        private const decimal PERCENT_EXPOSURE_PER_POSITION = 6;
        private const decimal STOP_LOSS_PERCENT = 50;
        const bool ENABLE_TESTING = true;

        /** DATA TRACKERS **/
        /// Indicator Data Tracking
        List<Symbol> _symbolTracker = new List<Symbol>();
        Dictionary<Symbol, SymbolData> _symbolDict = new Dictionary<Symbol, SymbolData>();
        Dictionary<Symbol, TradeStruct> _tradeInfo = new Dictionary<Symbol, TradeStruct>();

        /** PRIVATE VARS **/
        string[] _TestShortsListNov2022 = 
            {"FRZA", "VRAX", "PKBO", "SNTG", "GCT", "MACK", "SNAL", "QH", "PXMD"}; /// Test Period | Nov 1 2022 - Nov 20 2022
        ///string[] _TestShortsList = {"AFAQ", "PDSB", "IMMX", "CELZ", "LIXT"};

       string[] _TestShortsList = {"LIXT", "MYNZ", "EAR", "ABSI", "TSRI", "IMMX", "SBEV", "BSFC", "APM", "MDJH", "OLB", "VLDR", "RHE", "IPW", "HOOK", "NRGV", "TEN", "INDO", "HUSA", "USEG", "TMC", "USWS", "MARPS", "NINE", "ENSV", "SBFM", "DRCT", "HYMC", "HYMC", "PIK", "CELZ", "IGMS", "ADGI", "CLVS", "LGVN", "MOBQ", "MNTS", "UUU", "BDSX", "STSS", "CYN", "VLON", "VIVK", "COSM", "IDAI", "FNCH", "ZYME", "SNOA", "RVSN", "SIDU", "BBIG", "NURO", "STON", "SPRC", "AMLX", "JAN", "CYRN", "EFOI", "AUVI", "COGT", "KAVL", "EVOK", "ADN", "FSTX", "FSTX", "TBLT", "KZR", "ASPN", "NRSN", "RPID", "IINN", "RFP", "SRG", "HSTO", "GOEV", "HSTO", "GOEV", "USEA", "HUSN", "BWV", "BTTX", "PRPB", "AYLA", "ILAG", "MDIA", "PSTX", "APDN", "XCUR", "GRNA", "NNVC", "SOPA", "NVIV", "IONM", "CLWT", "VCSA", "ARHS", "VEEE", "FRZA", "ARTL", "HIL", "BRSH", "VLCN", "AERI", "SSY", "MGAM", "BRPM", "PXMD", "BIAF", "PIXY", "ETNB", "AKRO", "VRAX", "ATXI", "ABOS", "MOTS", "FXLV", "AIMD", "LITM", "CLAQ", "BEAT", "LUCY", "LASE", "DICE", "HPCO", "IMRA", "RMED", "AVEO", "AKUS", "TSHA", "AGFS", "USER", "NUVL", "BNFT", "SNTG", "IGNY", "MACK", "SNAL", };

        /// NYS = NYSE
        /// NAS = NASDAQ
        /// ASE = AMEX
        /// See: https://www.quantconnect.com/forum/discussion/12234/exchange-id-mapping/p1
        /// See: https://www.quantconnect.com/forum/discussion/11121/how-to-backtest-and-live-trade-on-chinese-stocks/p1
        string[] _ValidExchanges = {"NYS", "NAS", "ASE"};

        /// *** CONFIGURATION VARIABLES *** ///

        private DateTime _startDate = new DateTime(2022, 1, 1);
        private DateTime _endDate = new DateTime(2022, 11, 20);
        private int _accountStartSize = 20_000;
        /// Counts Number of Trades That Are Covered using hourly ema exit, when using hourly ema
        private int _hourlyEmaCoverCounter = 0;

        /// Defaults to How Algorithm Will Exit Short Position
        private COVER_TYPE _coverType = COVER_TYPE.COVER_AT_CLOSE;
        private ENTRY_TYPE _entryType = ENTRY_TYPE.IMMEDIATE_ENTRY;

        /// Used So That It's easy to configure which exit(s) to test against
        /// To-Do For Possible Optimizations would be to include a mapping to value, so for instance 
        // define both HourlyEma as the exit type, and 5 for 5 Hour Ema.
        EXIT_TYPE[] _AvailableExits = {  EXIT_TYPE.PERCENT_EXTENSION_FROM_OPEN }; //,

        private DYNAMIC_TIMING_ADJUSTMENT _timingAdjustmentMode = DYNAMIC_TIMING_ADJUSTMENT.PRICE_GREATER_THAN_OPEN;
        /// Used For Consolidating Open/Close Price For Custom Time Ranges
        private const int CUSTOM_TIME_PERIOD_IN_MINUTES = 5;

        /// --

        /// Initialize and Prepare Algo
        /// Warm-up Data and Indicators
        public override void Initialize()
        {
            /// Date Setup
            SetStartDate(_startDate);
            SetEndDate(_endDate);

            /// Account Setup
            SetCash(_accountStartSize);

            /// Benchmark Setup
            var lBenchmarkSecurity = AddEquity(BENCHMARK, Resolution.Minute);
            SetBenchmark(BENCHMARK);

            /// Universe Setup
            UniverseSettings.Resolution = Resolution.Minute;
            UniverseSettings.DataNormalizationMode = DataNormalizationMode.SplitAdjusted;
            UniverseSettings.ExtendedMarketHours = true;
            EnableAutomaticIndicatorWarmUp = true;

            /// SetBrokerageModel(BrokerageName.InteractiveBrokersBrokerage);
            AddUniverseSelection(new FineFundamentalUniverseSelectionModel(SelectCoarse, SelectFine));

            /// Market Defaults
            var lMarketOpenHour = 9;
            var lMarketOpenMinute = 30;

            /// If Taking a Specialized Entry, We Check for Entry One Minute after Market Open instead of immediate open entry
            /// Additionally, Pending Orders are cancelled at 11am
            if(_entryType == ENTRY_TYPE.SPECIALIZED_OPEN_ENTRY)
            {
                lMarketOpenMinute += CUSTOM_TIME_PERIOD_IN_MINUTES;

                Schedule.On(Schedule.DateRules.EveryDay(),
                    Schedule.TimeRules.At(new TimeSpan(11, 0, 0)), CancelPendingOrders);
            }

            Schedule.On(Schedule.DateRules.EveryDay(),
                Schedule.TimeRules.At(new TimeSpan(lMarketOpenHour, lMarketOpenMinute, 0)), OnEachMarketOpen);

            Schedule.On(Schedule.DateRules.EveryDay(),
                Schedule.TimeRules.BeforeMarketClose(BENCHMARK, 1), OnEachMarketClose);

            /// Warmup One Week Worth Of Data
            SetWarmUp(new TimeSpan(2, 0, 0, 0));
        }
        
        public void OnData(TradeBars aTradeBars)
        {
            /// QuantConnect Seemingly Has a Bug where using StopMarketOrders with
            /// a ticker that has the same Open High Low Close, causes erroneous fills
            /// so instead I use a direct Liquidation once the high exceeds the stop

            foreach(var lKeyValuePair in Portfolio)
            {
                Symbol lSymbol = lKeyValuePair.Key;

                if (aTradeBars.ContainsKey(lSymbol))
                {
                    var lTradeBar = aTradeBars[lSymbol];
 
                    if(lKeyValuePair.Value.Invested)
                    {
                        if(_tradeInfo.ContainsKey(lSymbol))
                        {
                            /// Update Highs
                            _tradeInfo[lSymbol].HighPrice = Math.Max(lTradeBar.High, _tradeInfo[lSymbol].HighPrice);

                            AdjustForDynamicTiming(lTradeBar);

                            if(lTradeBar.High > _tradeInfo[lSymbol].StopLoss
                              || HaveExitConditionsBeenMet(lTradeBar)
                            )
                            {
                                Liquidate(lSymbol, tag: BUY_TAG);
                                Log("Closing Order On: " + lSymbol);
                                /// Log("AskPrice: " + ActiveSecurities[lSymbol].AskPrice);
                                /// Log("BidPrice: " + ActiveSecurities[lSymbol].BidPrice);

                                _tradeInfo.Remove(lSymbol);
                            }
                        }
                    }
                }
            }
        }

        /// Called To Cancel Any Pending Sell Stops
        /// Used For Dynamic Time Adjusting Logic
        public void CancelPendingOrders()
        {
            if(IsWarmingUp) return;

            Transactions.CancelOpenOrders();
        }

        public void OnEachMarketClose()
        {
            /// Cover All Positions At Open
            /// Using This Logic In Place of Liquidation to Avoid Cancellation Issues
            foreach(var position in _tradeInfo)
            {
                if(ActiveSecurities[position.Key].Invested)
                {
                    switch(_coverType) 
                    {
                        case COVER_TYPE.COVER_AT_NEXT_OPEN:
                            MarketOnOpenOrder(position.Key, position.Value.ShareQuantity, tag: BUY_TAG);
                            break;
                        case COVER_TYPE.COVER_AT_CLOSE:
                            MarketOrder(position.Key, position.Value.ShareQuantity, tag: BUY_TAG);
                            break;
                        default:
                            MarketOrder(position.Key, position.Value.ShareQuantity, tag: BUY_TAG);
                            break;
                    }
                   
                    Log("Closing Order On: " + position.Key);
                }
            }
            _tradeInfo.Clear();
        }

        /// Called On Each Open To Execute Gap Up Short Criteria
        public void OnEachMarketOpen()
        {
            /// Ignore Warmup Periods
            if(IsWarmingUp) return;

            /// Ignore Mondays
            if(Time.Day == (int)DayOfWeek.Monday) return;

            decimal lGapPercentMultiplier = (100 + MINIMUM_GAP_UP_PERCENTAGE) / 100;

            /// Get All Potential Setups
            List<Symbol> lPotentialShortSetups = (from pair in ActiveSecurities
                where _symbolDict.ContainsKey(pair.Key)
                where pair.Value.Fundamentals != null
                where pair.Value.Fundamentals.MarketCap <= MAXIMUM_MARKET_CAP
                /// where pair.Value.Close > MINIMUM_OPEN_PRICE
                select pair.Key).ToList();

            /// Final Selection Process = Check Premarket Volume, We
            /// do this last as we have to do a full history request and that is more taxing performance wise
            var lPreMarketStartHour = 4;
            DateTime lStartTime = new DateTime(Time.Year, Time.Month, Time.Day, lPreMarketStartHour, 0, 0);

            List<Symbol> lShortSetups = new List<Symbol>();

            foreach(var lSymbol in lPotentialShortSetups)
            {
                CustomConsolidatedBar lBar = GetCustomTradeBar(lSymbol);
                decimal lPriceAtOpen = _entryType == ENTRY_TYPE.IMMEDIATE_ENTRY ? lBar.Open : lBar.Close;

                bool lMinimumPriceMet;
                if(_entryType == ENTRY_TYPE.IMMEDIATE_ENTRY)
                {
                    lMinimumPriceMet = (lBar.Close > MINIMUM_OPEN_PRICE);
                }
                else
                {
                    lMinimumPriceMet = (lBar.Open > MINIMUM_OPEN_PRICE);
                }

                if(!lMinimumPriceMet)
                {
                    continue;
                }

                /// Get Historical Data For Checking Prior Highs + Gaps
                bool lProperPricing = false;
                var lPriorDayBarHistory = History<TradeBar>(lSymbol, new TimeSpan(1,0,0,0), Resolution.Daily);

                if(!lPriorDayBarHistory.IsNullOrEmpty())
                {
                    TradeBar lPriorDayBar = lPriorDayBarHistory.First();
                    
                    /// decimal lPriceAtOpen = _entryType == ENTRY_TYPE.IMMEDIATE_ENTRY ? ActiveSecurities[lSymbol].Close : ActiveSecurities[lSymbol].Open;

                    if(lPriceAtOpen > lPriorDayBar.High && 
                       lPriceAtOpen > lPriorDayBar.Close * lGapPercentMultiplier)
                    {
                        lProperPricing = true;
                    }
                }

                if(!lProperPricing)
                {
                    continue;
                }

                /// Get All Premarket Data For Volume Purposes
                var lTradeBars = History<TradeBar>(lSymbol, lStartTime, Time);

                decimal lTotalVolume = 0;
                foreach(var lTradebar in lTradeBars)
                {
                    lTotalVolume += lTradebar.Volume;
                }

                if(lTotalVolume > MINIMUM_PREMARKET_VOLUME)
                {
                    lShortSetups.Add(lSymbol);
                }
            }

            decimal lCurrentPortfolioValue = Portfolio.TotalPortfolioValue;

            foreach(var lSymbolToShort in lShortSetups)
            {
                /// No Duplicate Trades
                if(!ActiveSecurities[lSymbolToShort].Invested)
                {
                    HandlePotentialShortTrade(lSymbolToShort, lCurrentPortfolioValue);

                    /*
                    decimal lPriceAtOpen = _entryType == ENTRY_TYPE.IMMEDIATE_ENTRY ? ActiveSecurities[lSymbolToShort].Close : ActiveSecurities[lSymbolToShort].Open;

                    /// Calculate Stop
                    decimal lStopLoss = lPriceAtOpen * ConvertPercentToPositiveMultiplier(STOP_LOSS_PERCENT);
                    
                    /// Calculate Exposure
                    var lNumberOfSharesToShort = GetNumberOfSharesToShort(lCurrentPortfolioValue, lSymbolToShort);

                    /// Track Stop & Number Of Shares to Short
                    _tradeInfo[lSymbolToShort] = new TradeStruct(lStopLoss, lNumberOfSharesToShort, lPriceAtOpen);

                    /// Place Order Immediately At Open
                    MarketOrder(lSymbolToShort, -lNumberOfSharesToShort, false, SELL_TAG);

                    Log("Opening Trade on - " + lSymbolToShort);

                    if(!_symbolTracker.Contains(lSymbolToShort))
                    {
                        _symbolTracker.Add(lSymbolToShort);
                    }
                    */
                }
            }
        }

        public CustomConsolidatedBar GetCustomTradeBar(Symbol aSymbol)
        {
            var lOpeningFiveMinutes = History<TradeBar>(aSymbol, CUSTOM_TIME_PERIOD_IN_MINUTES, Resolution.Minute).ToList();
            if(lOpeningFiveMinutes.Count <= 1)
            {
                return new CustomConsolidatedBar(ActiveSecurities[aSymbol].Open, ActiveSecurities[aSymbol].Close);
            }

            CustomConsolidatedBar lBar;

            if(_entryType == ENTRY_TYPE.IMMEDIATE_ENTRY)
            {
                lBar = new CustomConsolidatedBar(ActiveSecurities[aSymbol].Open, ActiveSecurities[aSymbol].Close);
            }
            else
            {
                lBar = new CustomConsolidatedBar(lOpeningFiveMinutes[0].Open, lOpeningFiveMinutes[lOpeningFiveMinutes.Count - 1].Close);
            }

            return lBar;
        }

        public void HandlePotentialShortTrade(Symbol aSymbol, decimal aTotalPortfolioValue)
        {
            CustomConsolidatedBar lBar = GetCustomTradeBar(aSymbol);
            
            /*decimal lEntryPrice = ActiveSecurities[aSymbol].Close;
            decimal lPriceAtOpen = _entryType == ENTRY_TYPE.IMMEDIATE_ENTRY ? ActiveSecurities[aSymbol].Close : ActiveSecurities[aSymbol].Open;*/
            decimal lEntryPrice = lBar.Close;
            decimal lPriceAtOpen = _entryType == ENTRY_TYPE.IMMEDIATE_ENTRY ? lBar.Close : lBar.Open;
            bool lDownClose = lBar.Close < lBar.Open;

            switch(_entryType)
            {
                case ENTRY_TYPE.SPECIALIZED_OPEN_ENTRY:

                    /// Down Bar in First Minute = Immediate Short
                    /// if(ActiveSecurities[aSymbol].Close < ActiveSecurities[aSymbol].Open)
                    if(lDownClose)
                    {
                        /// lEntryPrice = ActiveSecurities[aSymbol].Close;
                    }
                    /// Up Bar In First Minute = Add a 'Stretch Condition' Lasting Until 11AM
                    else
                    {
                        /// Apply a Percentage Stretch
                        lEntryPrice = ActiveSecurities[aSymbol].Close * 1.35m;
                        /// lEntryPrice = ActiveSecurities[aSymbol].Close + (_symbolDict[aSymbol].ATR * 5m);
                    }

                    break;
                case ENTRY_TYPE.IMMEDIATE_ENTRY:

                    /// For Immediate Entry At Open. Take 9:30am Pricing
                    /// lEntryPrice = ActiveSecurities[aSymbol].Close
                default:
                    break;
            }

            /// Calculate Stop
            decimal lStopLoss = lEntryPrice * ConvertPercentToPositiveMultiplier(STOP_LOSS_PERCENT);

            /// Calculate Exposure
            var lNumberOfSharesToShort = GetNumberOfSharesToShort(aTotalPortfolioValue, aSymbol, lEntryPrice);

            /// Track Stop & Number Of Shares to Short
            _tradeInfo[aSymbol] = new TradeStruct(lStopLoss, lNumberOfSharesToShort, lEntryPrice);

            if(_entryType == ENTRY_TYPE.IMMEDIATE_ENTRY || 
               /// ( (_entryType == ENTRY_TYPE.SPECIALIZED_OPEN_ENTRY) && ActiveSecurities[aSymbol].Close < ActiveSecurities[aSymbol].Open ) )
               ( (_entryType == ENTRY_TYPE.SPECIALIZED_OPEN_ENTRY) && lDownClose ) )
            {
                /// Place Order Immediately
                MarketOrder(aSymbol, -lNumberOfSharesToShort, false, SELL_TAG);
            }
            else
            {
                /// Put in Sell Stop Order
                StopMarketOrder(aSymbol, -lNumberOfSharesToShort, lEntryPrice, SELL_TAG);
            }

            /// Tracking Symbols Meeting Criteria
            if(!_symbolTracker.Contains(aSymbol))
            {
                _symbolTracker.Add(aSymbol);
            }

        }

        /// Called At Exit of Algorithm
        public override void OnEndOfAlgorithm()
        {
            string lSymbolsString = "string[] _TestShortsList = {";
            foreach(var symbol in _symbolTracker)
            {
                lSymbolsString += "\"" + symbol.Value + "\", ";
            }
            lSymbolsString += " };";

            Log(lSymbolsString);

            /// Log("Number Of Hourly Ema Covers = " + _hourlyEmaCoverCounter);

            base.OnEndOfAlgorithm();
        }

        /// Called Based On Changes To Current Stock Universe
        public override void OnSecuritiesChanged(SecurityChanges changes)
        {
            // If we have no changes, do nothing
            if (changes == SecurityChanges.None) return;

            foreach (var security in changes.AddedSecurities)
            {
                /// Set Leverage to 1 For Added Securities
                security.SetLeverage(1);

                /// Override QC Default Fill Model
                /*security.SetSlippageModel(new ConstantSlippageModel(0));
                security.SetFillModel(new LatestPriceFillModel());
                security.SetFeeModel(new ConstantFeeModel(0));*/
                //security.SetSlippageModel(new CustomSlippageModel(this));
                ///security.SetFeeModel(new CustomFeeModel(this));
                security.SetFillModel(new CustomFillModel(this));


                _symbolDict[security.Symbol] = new SymbolData(this, security.Symbol);
            }

            // If you have a dynamic universe, track removed securities
            foreach (var security in changes.RemovedSecurities)
            {
                if (_symbolDict.TryGetValue(security.Symbol, out var lSymbolData))
                {
                    lSymbolData.Dispose();
                    _symbolDict.Remove(security.Symbol);
                }
            }
        }

        /// Coarse Universe Selector
        IEnumerable<Symbol> SelectCoarse(IEnumerable<CoarseFundamental> coarse)
        {
            var lStocks = (from security in coarse
            where security.Volume > 0 && security.Price > 1
            where DoesTickerExist(security.Symbol.Value)
            select security.Symbol).ToList();

            return lStocks;
        }

        /// Fine Universe Selector
        IEnumerable<Symbol> SelectFine(IEnumerable<FineFundamental> fine) 
        {
            var filteredFine =
                (from security in fine
                 where _ValidExchanges.Contains(security.CompanyReference.PrimaryExchangeID)
                 where security.MarketCap < MAXIMUM_MARKET_CAP
                 orderby security.MarketCap descending
                 select security.Symbol).ToList();

            return filteredFine;
        }

        public bool HourlyEmaExitConditionsMet(TradeBar aBar)
        {
            bool lConditionsMet = false;

            /// Ema Breach = Conditions Met
            if(aBar.Low < _symbolDict[aBar.Symbol].HourlyEma)
            {
                lConditionsMet = true;
                /// DEBUG
                if(aBar.Low < _symbolDict[aBar.Symbol].HourlyEma)
                {
                    Log("Hourly TenEma = " + _symbolDict[aBar.Symbol].HourlyEma);
                    _hourlyEmaCoverCounter++;
                }
            }
            return lConditionsMet;
        }

        public bool PercentExtensionFromOpenExitConditionsMet(TradeBar aBar)
        {
            bool lConditionsMet = false;

            if(aBar.Low < _tradeInfo[aBar.Symbol].OpenPrice * 0.8m)
            {
                lConditionsMet = true;
            }

            return lConditionsMet;
        }

        /// If Testing Is Enabled. We Only Run The Code With Our Test List
        public bool DoesTickerExist(string ticker)
        {
            if(!ENABLE_TESTING)
            {
                return true;
            }

        	bool lTickerExists = false;
        	foreach(var symbol in _TestShortsList)
        	{
        		if(ticker == symbol)
                {
                    lTickerExists = true;
                    break;
                }
        	}
            return lTickerExists;
        }

        public decimal ConvertPercentToNegativeMultiplier(decimal aPercentage)
        {
            return aPercentage / 100;
        }

        public decimal ConvertPercentToPositiveMultiplier(decimal aPercentage)
        {
            return 1 + (aPercentage / 100);
        }

        public int GetNumberOfSharesToShort(decimal aCurrentPortfolioValueAtOpen, Symbol aSymbol, decimal aEntryPrice)
        {
            decimal lTotalDollarExposure = (aCurrentPortfolioValueAtOpen) * ConvertPercentToNegativeMultiplier(PERCENT_EXPOSURE_PER_POSITION);

            int lNumberOfSharesToShort = (int) Math.Round(lTotalDollarExposure / aEntryPrice);

            return lNumberOfSharesToShort;
        }

        /// Based On Configured Dynamic Time Stop Adjustment, Apply Changes
        public void AdjustForDynamicTiming(TradeBar aBar)
        {
            bool lPriceAboveOpen = aBar.High > _tradeInfo[aBar.Symbol].OpenPrice;
            bool lIsCorrectTimeOfDay = aBar.EndTime.Hour == 11 && aBar.EndTime.Minute == 31;

            switch(_timingAdjustmentMode)
            {
                case DYNAMIC_TIMING_ADJUSTMENT.ALWAYS_ADJUST:
                    if(lIsCorrectTimeOfDay)
                    {
                        _tradeInfo[aBar.Symbol].StopLoss = _tradeInfo[aBar.Symbol].HighPrice;
                        Log("Auto Adjusting Stop to High of Day");
                    }
                    break;
                case DYNAMIC_TIMING_ADJUSTMENT.PRICE_GREATER_THAN_OPEN:

                    /// Check For High > Open @ 11AM, Update Stop Loss To High If This Occurs
                    if(lPriceAboveOpen && lIsCorrectTimeOfDay)
                    {
                        _tradeInfo[aBar.Symbol].StopLoss = _tradeInfo[aBar.Symbol].HighPrice;
                        Log("High Above Open For " + aBar.Symbol.Value + ", Adjusting Stop Loss To High of Day");
                    }
                    break;
                case DYNAMIC_TIMING_ADJUSTMENT.NEVER:
                default:
                    break;
            }
        }

        /// On a Given Time Step, Given a Bar and the type of Exit to execute
        /// Determine if Exit Conditions Have Been Hit
        public bool HaveExitConditionsBeenMet(TradeBar aBar)
        {
            bool lConditionsMet = false;

            foreach(EXIT_TYPE lExitType in _AvailableExits)
            {
                switch(lExitType)
                {
                    case EXIT_TYPE.HOURLY_EMA_EXIT:
                        lConditionsMet = HourlyEmaExitConditionsMet(aBar);
                        break;
                    case EXIT_TYPE.PERCENT_EXTENSION_FROM_OPEN:
                        lConditionsMet = PercentExtensionFromOpenExitConditionsMet(aBar);
                        break;
                    case EXIT_TYPE.VWAP_PERCENTAGE_EXTENSIONS:
                        break;
                    case EXIT_TYPE.VWAP_ATR_EXTENSIONS:
                        break;
                    default:
                        break;
                }

                /// If Any Conditions = True, Move On
                if(lConditionsMet)
                {
                    break;
                }
            }

            return lConditionsMet;
        }
    }

    /// Custom Bar Data Tracker, Used to Get the Open and Close Range of a given time period
    public class CustomConsolidatedBar
    {
        private decimal _openPrice;
        private decimal _closePrice;

        public CustomConsolidatedBar(decimal aOpenPrice, decimal aClosePrice)
        {
            _openPrice = aOpenPrice;
            _closePrice = aClosePrice;
        }

        public decimal Open
        {
            get
            {
                return _openPrice;
            }
        }
        
        public decimal Close
        {
            get
            {
                return _closePrice;
            }
        }
    }

    public class TradeStruct
    {
        private decimal _stopLoss;
        private decimal _shareQuantity;
        private decimal _openPrice;
        private decimal _highPrice;
        public TradeStruct(decimal aStopLoss, decimal aShareQuantity, decimal aOpenPrice)
        {   
            _stopLoss = aStopLoss;
            _shareQuantity = aShareQuantity;
            _openPrice = aOpenPrice;
            _highPrice = aOpenPrice;
        }

        public decimal StopLoss
        {
            get
            {
                return _stopLoss;
            }
            set
            {
                _stopLoss = value;
            }
        }

        public decimal OpenPrice
        {
            get
            {
                return _openPrice;
            }
        }

        public decimal ShareQuantity
        {
            get
            {
                return _shareQuantity;
            }
        }

        public decimal HighPrice
        {
            get
            {
                return _highPrice;
            }
            set
            {
                _highPrice = value;
            }
        }
    }

    public class CustomFillModel : ImmediateFillModel
    {
        private readonly QCAlgorithm _algorithm;
        private readonly Random _random = new Random(387510346); // seed it for reproducibility
        private readonly Dictionary<long, decimal> _absoluteRemainingByOrderId = new Dictionary<long, decimal>();

        public CustomFillModel(QCAlgorithm algorithm)
        {
            _algorithm = algorithm;
        }

        public override OrderEvent MarketFill(Security asset, MarketOrder order)
        {
            // this model randomly fills market orders

            decimal absoluteRemaining;
            if (!_absoluteRemainingByOrderId.TryGetValue(order.Id, out absoluteRemaining))
            {
                absoluteRemaining = order.AbsoluteQuantity;
                _absoluteRemainingByOrderId.Add(order.Id, order.AbsoluteQuantity);
            }

            var fill = base.MarketFill(asset, order);

            /// Rather Than Any Weird QuantConnect Fills. Market Orders
            /// will be filled at bid or ask, or close and with minor touches
            switch (order.Direction)
            {
                case OrderDirection.Buy:
                    /// fill.FillPrice = _algorithm.ActiveSecurities[order.Symbol].AskPrice * 
                    fill.FillPrice = _algorithm.ActiveSecurities[order.Symbol].Close * 1.0035m;
                    break;
                case OrderDirection.Sell:
                    /// fill.FillPrice = _algorithm.ActiveSecurities[order.Symbol].BidPrice * 
                    fill.FillPrice = _algorithm.ActiveSecurities[order.Symbol].Close * 0.9965m;
                    break;
            }

            var absoluteFillQuantity = (int) (Math.Min(absoluteRemaining, _random.Next(0, 2*(int)order.AbsoluteQuantity)));
            fill.FillQuantity = Math.Sign(order.Quantity) * absoluteFillQuantity;

            if (absoluteRemaining == absoluteFillQuantity)
            {
                fill.Status = OrderStatus.Filled;
                _absoluteRemainingByOrderId.Remove(order.Id);
            }
            else
            {
                absoluteRemaining = absoluteRemaining - absoluteFillQuantity;
                _absoluteRemainingByOrderId[order.Id] = absoluteRemaining;
                fill.Status = OrderStatus.PartiallyFilled;
            }

            // _algorithm.Log($"CustomFillModel: {fill}");
            return fill;
        }
    }

    /// Handles Tracking Indicators and Warming Up the Data For Indicators as well as
    /// attaching to the Algorithm
    public class SymbolData
    {
        private QCAlgorithm _algorithm;
        private Symbol _symbol;
        private TradeBarConsolidator _consolidator;
        private TradeBarConsolidator _hourlyConsolidator;

        /// Daily Indicators
        public AverageTrueRange ATR;
        /// Hourly Indicators
        public ExponentialMovingAverage HourlyEma;

        public SymbolData(QCAlgorithm algorithm, Symbol symbol)
        {
            _algorithm = algorithm;
            _symbol = symbol;

            // Create Indicators
            ATR = new AverageTrueRange(10);
            HourlyEma = new ExponentialMovingAverage(6);

            // Create a consolidator to update the indicator
            _consolidator = new TradeBarConsolidator(TimeSpan.FromDays(1));
            _consolidator.DataConsolidated += DailyDataUpdate;

            _hourlyConsolidator = new TradeBarConsolidator(TimeSpan.FromHours(1));
            _hourlyConsolidator.DataConsolidated += HourlyDataUpdate;

            // Register the consolidator to update the indicator
            algorithm.SubscriptionManager.AddConsolidator(symbol, _consolidator);
            algorithm.SubscriptionManager.AddConsolidator(symbol, _hourlyConsolidator);

            // Warm Up Indicators
            algorithm.WarmUpIndicator(symbol, ATR, Resolution.Daily);
            algorithm.WarmUpIndicator(symbol, HourlyEma, Resolution.Hour);
        }

        /// Updates Indicators that run on specifically at a daily resolution
        /// Updates Once a Day
        private void DailyDataUpdate(object sender, TradeBar consolidatedBar)
        {
            ATR.Update(consolidatedBar);
        }

        private void HourlyDataUpdate(object sender, TradeBar consolidatedBar)
        {
            /// 10 Ema Data Should Only Be Updated Based On The Past 10
            /// Hours of Market Time Data, No Premarket or After Hour Session Data
            /// This is a workaround for the data pipeline being piped in
            if(consolidatedBar.EndTime.Hour < 10 ||
               consolidatedBar.EndTime.Hour > 16 ||
               (consolidatedBar.EndTime.Hour == 10 && consolidatedBar.EndTime.Minute < 30) ||
               (consolidatedBar.EndTime.Hour == 16 && consolidatedBar.EndTime.Minute != 0))
            {
                return;
            }

            HourlyEma.Update(consolidatedBar.EndTime, consolidatedBar.Close);
        }

        // If you have a dynamic universe, remove consolidators for the securities removed from the universe
        public void Dispose()
        {
            _algorithm.SubscriptionManager.RemoveConsolidator(_symbol, _consolidator);
        }
    }
}