Overall Statistics
Total Orders
0
Average Win
0%
Average Loss
0%
Compounding Annual Return
0%
Drawdown
0%
Expectancy
0
Start Equity
100000
End Equity
100000
Net Profit
0%
Sharpe Ratio
0
Sortino Ratio
0
Probabilistic Sharpe Ratio
0%
Loss Rate
0%
Win Rate
0%
Profit-Loss Ratio
0
Alpha
0
Beta
0
Annual Standard Deviation
0
Annual Variance
0
Information Ratio
0
Tracking Error
0
Treynor Ratio
0
Total Fees
$0.00
Estimated Strategy Capacity
$0
Lowest Capacity Asset
Portfolio Turnover
0%
#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.Interfaces;
    using QuantConnect.Storage;
    using QuantConnect.Data.Custom.AlphaStreams;
    using QCAlgorithmFramework = QuantConnect.Algorithm.QCAlgorithm;
    using QCAlgorithmFrameworkBridge = QuantConnect.Algorithm.QCAlgorithm;
#endregion
namespace QuantConnect.Algorithm.CSharp
{
    public class SecuritiesFilter : QCAlgorithm
    {
		// === GENERAL PARAMETERS ===

		// day to start the run
        private readonly string start = "10/25/2023 00:00:00.000 AM";

		// day to start the run
        private readonly string end = "10/26/2023 00:00:00.000 AM";

		// whether to use adjusted data
		private readonly bool useAdjusted = false;

        // [enable] volume filtering. true = enabled, false = disabled
        private readonly bool useVolumeFiltering = true;

        // MIN volume for universe selection
        // in example this was 69M
        private readonly int minvol = 16300000;

        // MAX volume for universe selection
        // in example this was 70M
        private readonly int maxvol = 16500000;

        // [enable] open filtering. true = enabled, false = disabled
        private readonly bool useOpenFiltering = true;

        // averaging modulus parameter
        // will average $open every N days (1 for every day, 2 for every other day, etc.)
        private readonly int openAvgMod = 1;

        // this parameter will omit an $open values less than the provided amount from any averaging calculations
        private readonly decimal openAvgMin = 0;

        // MIN $OPEN value for filtering securities
        private readonly decimal minopen = 0;

        // MAX $OPEN value for filtering securities
        private readonly decimal maxopen = 1000000000;

		// === UNIVERSE PARAMETERS ===

        // lookback time (in days)
        private readonly int lookback = 2;

        // [enable] volume filtering for hoc/loc. true = enabled, false = disabled
        private readonly bool useVolumeFilteringHocLoc = false;

        // volume for counting hoc/loc
        private readonly int W = 100000;

        // [enable] $Open filtering for hoc/loc. true = enabled, false = disabled
        private readonly bool useOpenFilteringHocLoc = true;

        // $Open for counting hoc/loc
		private readonly int X = 0;

		// values for condition 2: HIGH <= OPEN + Y (% or Tick)
		private readonly bool useTick1 = false; 	// false = %; true = tick
		private readonly decimal Y = 0.004m;		    // 0.01 = 1%

        private readonly decimal percentLong = 0.70m;

		// values for condition 3: LOW >= OPEN - Z (% or Tick)
		private readonly bool useTick2 = false; 	// false = %; true = tick
		private readonly decimal Z = 0.004m;		    // 0.01 = 1%

        private readonly decimal percentShort = 0.30m;

        private DateTime startDate;
        private DateTime endDate;
        private decimal low = Decimal.MaxValue;

        List<Symbol> universe = new List<Symbol> {};
        
        public Dictionary<Symbol, decimal> volTotal = new Dictionary<Symbol, decimal>();
        public Dictionary<Symbol, int> volCount = new Dictionary<Symbol, int>();
        public Dictionary<Symbol, decimal[]> openValueAvgs = new Dictionary<Symbol, decimal[]>();
        public Dictionary<String, decimal[]> openValues;

		// Initialize the scanner
       	public override void Initialize()
        {
            Log("time,ticker,count,days,hoc,loc,hoc_adj,loc_adj,hoc_o,loc_o,hoc_change,loc_change,dd_max,pl,ot_quantity_avg,ot_price_avg,ot_avg,vol_avg"); // note hoc = high <= open count; loc = low >= open count.

            if(useAdjusted)
                UniverseSettings.DataNormalizationMode = DataNormalizationMode.Adjusted;
            else
                UniverseSettings.DataNormalizationMode = DataNormalizationMode.Raw;

            startDate = DateTime.Parse(start);
            endDate = DateTime.Parse(end);
            DateTime endDateM1 = endDate;

            SetStartDate(startDate.Year, startDate.Month, startDate.Day);
            SetEndDate(endDate.Year, endDate.Month, endDate.Day);
            SetCash(100000);

			// define universe
			AddUniverse(CoarseFilterFunction);

            // schedule event
            AddEquity("SPY", Resolution.Daily);

            // load all stored open values
            if(ObjectStore.ContainsKey("openvalues")) {
                openValues = ObjectStore.ReadJson<Dictionary<String, decimal[]>>("openvalues");
            } else
                openValues = new Dictionary<String, decimal[]>();

            // scan all data at end of looping for volume average conditional and $open conditional 
            Schedule.On(DateRules.On(endDateM1.Year, endDateM1.Month, endDateM1.Day), TimeRules.AfterMarketOpen("SPY", 0), Scan);
        }

        // loops through all securities in the list and filters based on average volume
        // then checks getOpen conditional for validation before perform HOC/LOC conditionals
        private void Scan() {
            List<Symbol> filter1 = new List<Symbol>();
            Dictionary<Symbol, IEnumerable<TradeBar>> filter2 = new Dictionary<Symbol, IEnumerable<TradeBar>>();

            // loop through all retrieved symbols in the coarse universe
            foreach(Symbol symbol in volCount.Keys) {
                
                // calculate average volume
                decimal vol_avg = 0.0m;
                if(volCount.ContainsKey(symbol) && volCount[symbol] > 0 && volTotal.ContainsKey(symbol)) {
                    vol_avg = volTotal[symbol] / volCount[symbol];
                }

                // check volume conditional
                if(useVolumeFiltering && (vol_avg < minvol || vol_avg > maxvol))
                    continue;

                filter1.Add(symbol);
            }

            // check for $open avg conditional in all values
            if(useOpenFiltering) {
                // loop through all symbols
                foreach(Symbol symbol in filter1) {
                    // get daily history for each ticker
                    var startDate = DateTime.Parse(start);
                    var endDate = DateTime.Parse(end);

                    decimal open_volTotal = 0.0m;
                    decimal open_price_total = 0.0m;
                    decimal open_total = 0.0m;
                    decimal open_count = 0.0m;

                    // loop through each bar in history
                    var history = History(symbol, startDate, endDate, Resolution.Daily);
                    int i = 0;
                    foreach(var bar in history) {
                        // check for day modulus
                        if(++i % openAvgMod == 0) {
                            // retrieve daily $Open value
                            decimal[] open = getOpen(symbol, bar.Time);

                            // determine if $open is less than openAvgMin
                            // if so, omit
                            if(open[2] < openAvgMin)
                                continue; 

                            open_volTotal += open[0];
                            open_price_total += open[1];
                            open_total += open[2];
                            open_count++;
                        }
                    }

                    // define open_avg values
                    decimal open_vol_avg = 0.0m;
                    decimal open_price_avg = 0.0m;
                    decimal open_avg = 0.0m;
                    if(open_count > 0) {
                        open_vol_avg = open_volTotal / open_count;
                        open_price_avg = open_price_total / open_count;
                        open_avg = open_total / open_count;
                        openValueAvgs[symbol] = new decimal[]{open_vol_avg, open_price_avg, open_avg};
                    } else {
                        openValueAvgs[symbol] = new decimal[]{0.0m, 0.0m, 0.0m};
                    }

                    // validate that $open is within provided range, if not then skip
                    if(open_avg < minopen || open_avg > maxopen)
                        continue;

                    // add symbol to values list
                    var enumerable = History(symbol, lookback, Resolution.Daily);
                    var LookbackHistory = enumerable.ToList();
                    filter2[symbol] = LookbackHistory;
                }
            } 
            
            // if no open filter, add all symbols to filter2

            else {
                foreach(Symbol symbol in filter1) {
                    // add symbol to values list
                    var enumerable = History(symbol, lookback, Resolution.Daily);
                    var LookbackHistory = enumerable.ToList();
                    filter2[symbol] = LookbackHistory;
                }
            }

            // call logging function
            Logging(filter2);
        }

        // returns stored value from object storage
        private decimal[] getOpenObjStore(String key) {
            // return value if it exists
            if(openValues.ContainsKey(key)) {
                return openValues[key];
            }

            // otherwise return null
            return null;
        }

        // save an object to object storage
        private void saveOpenObjStore(String key, decimal[] values) {
            // add values to map
            openValues[key] = values;
        }

        // formatting for open object in key storage
        private String formatObjStoreKey(Symbol symbol, DateTime day) {
            return $"{symbol.ToString()}-{day.Year}-{day.Month}-{day.Day}";
        }

        // retrieve open value based on symbol and date
        private decimal[] getOpen(Symbol symbol, DateTime day) {
            String key = formatObjStoreKey(symbol, day);

            // check to see if object store contains values, if so retrieve
            decimal[] open = getOpenObjStore(key);
            if(open != null)
                return open;

            // retrieve open
            DateTime start = new DateTime(day.Year, day.Month, day.Day, 9, 30, 0);
            DateTime end = new DateTime(day.Year, day.Month, day.Day, 9, 30, 1);
            var ticks = History<Tick>(symbol, start, end, Resolution.Tick);
            open = getOpen(ticks);

            // if open is null, retrieve the next 30 seconds of ticks for processing
            if(open == null) {
                // retrieve open
                start = new DateTime(day.Year, day.Month, day.Day, 9, 30, 1);
                end = new DateTime(day.Year, day.Month, day.Day, 9, 30, 30);
                ticks = History<Tick>(symbol, start, end, Resolution.Tick);
                open = getOpen(ticks);
            }

            // if still not found, set to 0
            if(open == null) {
                open = new decimal[]{0, 0, 0};
            }

            saveOpenObjStore(key, open);
            return open;
        }

        // used to retrieve the $open value
        private decimal[] getOpen(IEnumerable<Tick> ticks) {
            // retrieve tick data
            decimal quantity = 0;
            decimal price = 0.0m;
            List<Tick> openings = new List<Tick>();
                
            foreach(var tick in ticks) {
                if(tick.SaleCondition == "4000000" || tick.SaleCondition == "4000001") {
                    openings.Add(tick);
                }
            }

            if(openings.Count() == 0) {
                return null;
            }

            // find tick with largest trade price
            foreach(Tick t in openings) {
                if(t.Quantity > quantity) {
                    quantity = t.Quantity;
                    price = t.Price;
                }
            }

            return new decimal[]{quantity,price,quantity*price};
        }

        private void Logging(Dictionary<Symbol, IEnumerable<TradeBar>> values) {

            foreach(KeyValuePair<Symbol, IEnumerable<TradeBar>> pair in values) {

                var history = values[pair.Key].ToList();
                var symbol = pair.Key;

                // if no history is found 
                if(history.Count() == 0)
                    continue;

                // get number of days high <= open and low >= open
                int hoc = 0;
                int loc = 0;
                decimal hoc_o = 0.0m;
                decimal loc_o = 0.0m;
                int hoc_oc = 0;
                int loc_oc = 0;

                // DD/PNL parameters
                decimal dd = 0.0m;
                decimal dd_max = 0.0m;
                decimal pl = 0.0m;

                int days = 0;

                for(int i = 0; i < history.Count(); i++) {
                    if(i == 0)
                        continue;

                    var bar = history[i];

                    // check volume conditional
                    if(useVolumeFilteringHocLoc && (bar.Volume < W))
                        continue;

                    // check $open conditional
                    if(useOpenFilteringHocLoc) {
                        // retrieve daily $Open value and check conditional
                        var open = getOpen(symbol, bar.Time);

                        if(open[2] < X)
                            continue;
                    }

                    // increment volume days
                    days++;

                    // change values for HOC and LOC to be applied to PL and DD
                    decimal hoc_change = 0.0m, loc_change = 0.0m;
                    
                    // check for HOC
                    if(bar.High <= bar.Open + (useTick1 ? Y : bar.Open * Y)) {
                        hoc++;

                        // if not last day, retrieve o-(o+1)%
                        if(i != history.Count() - 1) {
                            var next = history[i + 1];
                            decimal change = (bar.Open - next.Open) / bar.Open;
                            hoc_o += change;
                            if(change > 0)
                                hoc_oc++;

                            hoc_change = -(next.Open - bar.Open) / bar.Open;
                        }
                    } else {
                        hoc_change = -(useTick1 ? Math.Abs(Y / bar.Open) : Y);
                    }

                    // check for LOC
                    if(bar.Low >= bar.Open - (useTick2 ? Z : bar.Open * Z)) {
                        loc++;

                         // if not last day, retrieve -(o-(o+1))%
                        if(i != history.Count() - 1) {
                            var next = history[i + 1];
                            decimal change = (next.Open - bar.Open) / bar.Open;
                            loc_o += change;
                            if(change > 0)
                                loc_oc++;

                            loc_change = (next.Open - bar.Open) / bar.Open;
                        }
                    } else {
                        loc_change = -(useTick2 ? Math.Abs(-Z / bar.Open) : Z);
                    }
                    
                    // apply percent long and percent short calculations
                    hoc_change *= percentShort;
                    loc_change *= percentLong;

                    // apply PL and DD changes
                    pl += (hoc_change + loc_change);
                    dd += (hoc_change + loc_change);

                    // check for if max
                    if(dd < dd_max)
                        dd_max = dd;
                    // check for if >0
                    if(dd > 0)
                        dd = 0.0m;
                }

                // if no days then skip
                if(days == 0)
                    continue;

                // retrieve last bar to get volume
                var last = history.Last();

                // retrieve volume average
                decimal vol_avg = 0.0m;
                if(volCount[symbol] > 0)
                    vol_avg = volTotal[symbol] / volCount[symbol];


                decimal[] open_values = new decimal[] {0.0m, 0.0m, 0.0m};
                if(openValueAvgs.ContainsKey(symbol)) 
                    open_values = openValueAvgs[symbol];

                int rounding = 2;
                Log($"{Time},{history.Last().Symbol.ToString().Split(" ")[0]},"+                                                                        // time, ticker,
                    $"{history.Count()},{days},{hoc},{loc},"+                                                                                           // days, hoc, loc,
                    $"{Math.Round((hoc*100.0)/days, rounding)},{Math.Round((loc*100.0)/days, rounding)},"+                                              // hoc_adj, loc_adj,
                    $"{Math.Round(hoc_o*100, rounding)},{Math.Round(loc_o*100, rounding)},"+                                                            // hoc_o, loc_o
                    $"{(hoc == 0 ? 0 : Math.Round((hoc_oc*100.0)/hoc, rounding))},{(loc == 0 ? 0 : Math.Round((loc_oc*100.0)/loc, rounding))},"+        // hoc_change, loc_change
                    $"{Math.Round(dd_max*100.0m, rounding)},{Math.Round(pl*100.0m/(days/252.0m), rounding)},"+                                          // dd_max, pl
                    $"{Math.Round(open_values[0], 2)},{Math.Round(open_values[1], 2)},{Math.Round(open_values[2], 2)},{Math.Round(vol_avg, 2)}");               // $open_vol_avg, $open_price_avg, $open_avg, vol_avg
            }

            // reset all lists
            values.Clear();

            // after finished save all open values
            Log("Saving open values...");
            ObjectStore.SaveJson<Dictionary<String, decimal[]>>("openvalues", openValues);
            Log("Open values have been saved.");
        }

		public IEnumerable<Symbol> CoarseFilterFunction(IEnumerable<CoarseFundamental> coarse) {

            foreach(CoarseFundamental c in coarse) {
                // add to volume total and to volume count
                if(!volTotal.ContainsKey(c.Symbol))
                    volTotal[c.Symbol] = 0;
                if(!volCount.ContainsKey(c.Symbol))
                    volCount[c.Symbol] = 0;
                volTotal[c.Symbol] += c.Volume;
                volCount[c.Symbol] += 1;
            }

            return Universe.Unchanged;
		}
    }
}