Overall Statistics
Total Trades
4
Average Win
0%
Average Loss
-0.79%
Compounding Annual Return
-61.935%
Drawdown
2.000%
Expectancy
-1
Net Profit
-1.575%
Sharpe Ratio
-4.905
Probabilistic Sharpe Ratio
1.125%
Loss Rate
100%
Win Rate
0%
Profit-Loss Ratio
0
Alpha
-0.509
Beta
0.147
Annual Standard Deviation
0.112
Annual Variance
0.013
Information Ratio
-1.367
Tracking Error
0.203
Treynor Ratio
-3.726
Total Fees
$20.00
Estimated Strategy Capacity
$350000.00
Lowest Capacity Asset
YETI WYZEPGZ8HZ6T
#region imports
from AlgorithmImports import *
import pandas as pd

#endregion
class ParticleTransdimensionalAutosequencers(QCAlgorithm):
    def Initialize(self):
        self.SetStartDate(2023, 1, 15)
        self.SetEndDate(2023, 1, 20)
        self.SetCash(50000)
        self.cash = 50000
        self.daily_stop = 0.02
        # List of equities that would cause trouble when running the algo and needed to be manually added at initialization
        self.equityls = ['SPY','HCM','MWGP','EXPD','PLAY','PRGS','ADPT','CDW','SIX','TWOU','DHR']
        for symbol in self.equityls:
            self.AddEquity(symbol, Resolution.Minute, extendedMarketHours=True)
        self.AddUniverseSelection(CoarseFundamentalUniverseSelectionModel(self.CoarseSelectionFunction))
        self.UniverseSettings.Resolution = Resolution.Minute
        self.UniverseSettings.extendedMarketHours = True

        # Track open price of the day
        self.Schedule.On(
            self.DateRules.EveryDay("SPY"),
            self.TimeRules.At(9, 31),
            self.TrackOpen
        )
        # Select stocks with highest RVOL in the first 15 mins of trading
        self.Schedule.On(
            self.DateRules.EveryDay("SPY"),
            self.TimeRules.At(9, 35),
            self.SelectUniverse
        )

        # Liquidate all position before market close
        self.Schedule.On(self.DateRules.EveryDay("SPY"), self.TimeRules.BeforeMarketClose("SPY", 1), self.ClosePositions)

        # List or dictionary to keep track of values for the universe selection
        self.universe = []
        self.volume_by_symbol = {}
        self.open_by_symbol = {}
        self.gap = {}
        self.vol_after_gap = {}
        self.rvol_by_symbol = {}
        self.logged = False
        # Essential to have this automatic warmup, otherwise indicators are all zero and indicator.is_ready = False
        self.EnableAutomaticIndicatorWarmUp = True
        
        # List of dictionary to keep track of indicators that determines entry and exit criteria
        self.green_vol = {}
        self.red_vol = {}
        self.green_red_df = {}
        self.vwap_dict = {}
        self.vwap_df = {}
        self.vwap_cross_flag = {}
        self.vwap_above_flag = {}
        self.high_vol_flag = {}
        self.green_red_flag = {}
        self.can_trade_flag = False
        
    # Coarse Selection Filter executed at 00:00 on every day (once only) based on data of the previous trading day
    def CoarseSelectionFunction(self, coarse):
        self.volume_by_symbol = {c.Symbol: 0 for c in coarse if c.Price > 30 and c.Price < 400 and c.Volume > 2000000 and c.HasFundamentalData}
        self.Debug(f"Universe size before volume filter: {len(self.volume_by_symbol)} at {self.Time}")
        self.green_vol = self.volume_by_symbol.copy()
        self.red_vol = self.volume_by_symbol.copy()
        # initialize df and various flags
        self.can_trade_flag = False
        for symbol in self.volume_by_symbol.keys():
            self.green_red_df[symbol] = pd.DataFrame([],columns = ['volume', 'green_red'])
            self.vwap_df[symbol] = pd.DataFrame([],columns = ['vwap', 'close','high'])
            self.vwap_cross_flag[symbol] = False
            self.green_red_flag[symbol] = 0
            self.high_vol_flag[symbol] = False
            self.vwap_above_flag[symbol] = 0.0
        #for symbol, volume in self.volume_by_symbol.items():
        #    try:
        #        self.vwap_dict[symbol] = SymbolData(symbol, self)
        #    except:
        #        self.Debug(f'{symbol} cannot log vwap data')
        return list(self.volume_by_symbol.keys())

    # OnData executed whenever there is new data arriving. In this case, new data arrive at 9:30 every day at minute interval (due to Resolution.Minute)
    def OnData(self, data):
        
        # register VWAP indicator at 9:30
        if self.Time.hour == 9 and self.Time.minute == 30:
            for symbol in self.volume_by_symbol.keys():
                self.vwap_dict[symbol] = SymbolData(symbol, self)
        
        # Every minute, data got pumped in for many securities. We record the minute volume data for stocks that passes our coarse selection filter
        for symbol in self.volume_by_symbol.keys():
            if data.ContainsKey(symbol):
                try: 
                    #self.Log(f'{symbol} has minute volume of {data[symbol].Volume}')
                    self.volume_by_symbol[symbol] += data[symbol].Volume
                    
                    # if the bar is green, record the volume in a seperate dictionary
                    if data[symbol].Close > data[symbol].Open:
                        self.green_vol[symbol] += data[symbol].Volume
                        self.green_red_df[symbol].loc[self.Time] = [data[symbol].Volume, "Green"]
                    if data[symbol].Close < data[symbol].Open:
                        self.red_vol[symbol] += data[symbol].Volume
                        self.green_red_df[symbol].loc[self.Time] = [data[symbol].Volume, "Red"]

                    self.vwap_dict[symbol].Update(data[symbol].EndTime, data[symbol].Close)
                except:
                    self.Log(f'{symbol} has data[symbol] as non-type')
        
        # When volume_by_symbol is cleared (it is cleared at 9:35 by a scheduled event), it will print the length of the final universe to be traded
        if len(self.volume_by_symbol) == 0:
            if not self.logged:
                self.logged = True
                self.Debug(f"Universe size after volume filter: {len(self.universe)} at {self.Time}")
            #return 
        
        # from 9:35 onwards, only keep track of indicators for selected universe
        # Track green vs red volume every min
        for symbol in self.universe:
            if data.ContainsKey(symbol):
                try:
                    # Track green and red bar volume
                    if data[symbol].Close > data[symbol].Open:
                        #self.green_vol[symbol] += data[symbol].Volume
                        self.green_red_df[symbol].loc[self.Time] = [data[symbol].Volume, "Green"]
                    if data[symbol].Close < data[symbol].Open:
                        #self.red_vol[symbol] += data[symbol].Volume
                        self.green_red_df[symbol].loc[self.Time] = [data[symbol].Volume, "Red"]
                except:
                    self.Log(f'{symbol} has data[symbol] as non-type')


        # Track green vs red volume in last 10 bars
        for symbol in self.universe:
            if data.ContainsKey(symbol):
                data_symbol = self.green_red_df[symbol] 
                length_of_data = data_symbol.shape[0]
                if length_of_data <= 10:
                    last10barvol = data_symbol[-length_of_data:]
                else:
                    last10barvol = data_symbol[-10:]
                green_vol_last10 = last10barvol.loc[last10barvol['green_red'] == "Green", "volume"].sum()
                red_vol_last10 = last10barvol.loc[last10barvol['green_red'] == "Red", "volume"].sum()
                try:
                    bullishness_last10 = green_vol_last10 / (green_vol_last10 + red_vol_last10)
                except:
                    self.Debug(f'{symbol} has green vol of {green_vol_last10} and red vol of {red_vol_last10}')
                    bullishness_last10 = 0.5
                if bullishness_last10 >= 0.75:
                    self.green_red_flag[symbol] = 1
                elif bullishness_last10 <= 0.25:
                    self.green_red_flag[symbol] = -1
                else:
                    self.green_red_flag[symbol] = 0

        # Track VWAP and whether price crosses VWAP
        for symbol in self.universe:
            self.vwap_cross_flag[symbol] = False # reset flag to 0 every bar
            self.vwap_above_flag[symbol] = 0.0 # reset flag to 0 every bar
            if data.ContainsKey(symbol):
                # Track vwap
                #self.vwap_dict[symbol].Update(data[symbol].EndTime, data[symbol].Close)
                try:
                    vwap = self.vwap_dict[symbol].vwap.Current.Value
                    # vwap cross flag
                    if (data[symbol].High > vwap and data[symbol].Low < vwap) and (data[symbol].Close > data[symbol].Open):
                        self.vwap_cross_flag[symbol] = 1
                    elif (data[symbol].High > vwap and data[symbol].Low < vwap) and (data[symbol].Close < data[symbol].Open):
                        self.vwap_cross_flag[symbol] = -1
                    else:
                        self.vwap_cross_flag[symbol] = False
                    # vwap df tracking
                    self.vwap_df[symbol].loc[self.Time] = [vwap, data[symbol].Close, data[symbol].High]
                    no_of_bars = self.vwap_df[symbol].shape[0]
                    no_of_bars_above_vwap = len(self.vwap_df[symbol][self.vwap_df[symbol]['close'] > self.vwap_df[symbol]['vwap']])
                    perc_of_bars_above_vwap = no_of_bars_above_vwap /  no_of_bars
                    self.vwap_above_flag[symbol] = perc_of_bars_above_vwap
                except:
                    self.vwap_cross_flag[symbol] = False
                    self.vwap_above_flag[symbol] = 0.0
            
            #if self.vwap_cross_flag[symbol] == 1: #and self.Time.hour >= 13 and self.Time.minute >= 25:
            #    self.Debug(f"{symbol} has vwap of {self.vwap_dict[symbol].vwap.Current.Value} vs open price of {data[symbol].Open} and close price of {data[symbol].Close} and vwap flag of {self.vwap_cross_flag[symbol]} at {self.Time}")
            #    self.Debug(f"{symbol} has cross vwap at {self.Time}")

        # Track volume of current bar vs the past 20 bars
        for symbol in self.universe:
            self.high_vol_flag[symbol] = False
            try:
                if data.ContainsKey(symbol):
                    vol_last_20_bar = self.SMA(symbol, 20, Resolution.Minute, Field.Volume)
                    if data[symbol].Volume > 2.0 * vol_last_20_bar.Current.Value:
                        self.high_vol_flag[symbol] = True
            except:
                self.high_vol_flag[symbol] = False
            #if self.high_vol_flag[symbol] == True: #and self.Time.hour >= 13 and self.Time.minute >= 25:
            #    self.Debug(f"{symbol} has volume of {data[symbol].Volume} vs avg of last 10 bar of {vol_last_10_bar.Current.Value} and high_vol_flag of {self.high_vol_flag[symbol]} at {self.Time}")            

        # Buying criteria
        for symbol in self.universe:
            if data.ContainsKey(symbol):
                invested = [x.Symbol.Value for x in self.Portfolio.Values if x.Invested]
                if (self.Time.hour == 15 and self.Time.minute >=50) or (self.Time.hour == 16):
                    self.can_trade_flag == False
                if self.high_vol_flag[symbol] == True and self.vwap_cross_flag[symbol] == 1 and self.green_red_flag[symbol] == 1 and symbol not in invested and self.can_trade_flag == True and self.vwap_above_flag[symbol] >= 0.5:
                    risk_quantum = self.daily_stop * self.cash 
                    stop_loss = self.vwap_dict[symbol].vwap.Current.Value - self.ATR(symbol, 20, Resolution.Daily).Current.Value*2
                    risk = self.ATR(symbol, 20, Resolution.Daily).Current.Value*2
                    take_profit = self.vwap_dict[symbol].vwap.Current.Value + risk * 3
                    quantity = 1000
                    self.MarketOrder(symbol, quantity)
                    self.StopMarketOrder(symbol, -quantity, stop_loss)
                    self.LimitOrder(symbol, -quantity, take_profit)
                    #self.SetHoldings(symbol, 0.33)
                    self.Debug(f"Buy {symbol} at {self.Time} with market entry, stop loss at {stop_loss}, risk at {risk} atr at {self.ATR(symbol, 20, Resolution.Daily).Current.Value} and take profit at {take_profit} because its vol flag is {self.high_vol_flag[symbol]}, vwap_cross_flag is {self.vwap_cross_flag[symbol]}, vwap_above_flag is {self.vwap_above_flag[symbol]} and green_red flag is {self.green_red_flag[symbol]}")

        """
        for symbol in self.universe:
            if self.vwap_cross_flag[symbol] == 1 and self.high_vol_flag[symbol] == True:
                self.Debug(f"{symbol} meet both vwap cross and high vol criteria at {self.Time}.")

        for symbol in self.universe:
            if self.green_red_flag[symbol] == 1:
                self.Debug(f"{symbol} has more green volume than red in the past 10 bars at {self.Time}.")
            if self.green_red_flag[symbol] == -1:
                self.Debug(f"{symbol} has more red volume than green in the past 10 bars at {self.Time}.")         
        """

        # Check if variables are tracked throughout the session by looking at various dict at 15:49 every day
        #if self.Time.hour == 9 and self.Time.minute > 34 and self.Time.minute <= 45:
        if self.Time.hour == 15 and self.Time.minute == 59:
            self.Debug(f"it is {self.Time} now.")
            for symbol in self.universe:
                #bullishness = self.green_vol[symbol] / (self.green_vol[symbol] + self.red_vol[symbol])
                data = self.green_red_df[symbol]
                df_green_vol = data.loc[data['green_red'] == "Green", "volume"].sum()
                df_red_vol = data.loc[data['green_red'] == "Red", "volume"].sum()
                self.Debug(f"According to dataframe, {symbol} has green volume of {df_green_vol} and red volume of {df_red_vol} at {self.Time}")
                #self.Debug(f"{symbol} has vwap of {self.vwap_dict[symbol].vwap.Current.Value} vs open price of {data[symbol].Open} and close price of {data[symbol].Close} at {self.Time}") 

    # TrackOpen is executed everyday at 9:31 and track the opening price of the current trading day. Slight descrepency with actual opening price but close enough
    def TrackOpen(self):
        for symbol, volume in self.volume_by_symbol.items():
            historyDataMin = self.History(symbol,1,Resolution.Minute)
            try:
                open_price_sym = historyDataMin['open'][-1]
            except:
                self.Debug(f"Opening price data for current day unavailable for {symbol.Value}")
            self.open_by_symbol[symbol] = open_price_sym
            #self.Debug(f'{symbol} has open price of {open_price_sym} at {self.Time}')
        # Calculate gap%
        for symbol, volume in self.volume_by_symbol.items():
            historyData = self.History(symbol,2,Resolution.Daily)
            try:
                #openDayAfterEarnings = historyData['open'][0]
                openDayAfterEarnings = self.open_by_symbol[symbol]
                closeDayBeforeEarnings = historyData['close'][-1]
            except:
                self.Debug(f"History data unavailable for {symbol.Value}")
                continue
            priceGap = openDayAfterEarnings - closeDayBeforeEarnings
            percentGap = priceGap / closeDayBeforeEarnings
            if (percentGap > 0.03):
                self.gap[symbol] = percentGap
                self.Debug(f'{symbol} closes at {closeDayBeforeEarnings} previously and opens at {openDayAfterEarnings} now, it has gap percentage of {percentGap} at {self.Time}')
                self.vol_after_gap[symbol] = volume
                #self.vwap_dict[symbol] = SymbolData(symbol, self)
        self.Debug(f'Universe size after gap filter: {len(self.gap)}')
     
    # SelectUniverse is executed at 9:35 everyday to calculate gap %, filter by +-5%, then calculate rvol, sort the narrowed list after gap filter by rvol, then output the top 10    
    def SelectUniverse(self):
        self.universe = []
        # Calculate rvol and sort that. Get top 10 stocks
        for symbol, gap in self.gap.items():
            volume = self.vol_after_gap[symbol]
            symbol_sma = self.SMA(symbol, 20, Resolution.Daily, Field.Volume)
            symbol_rvol = self.volume_by_symbol[symbol] / self.SMA(symbol, 20, Resolution.Daily, Field.Volume).Current.Value
            self.rvol_by_symbol[symbol] = symbol_rvol        
        sorted_rvol_ls = sorted(self.rvol_by_symbol.items(), key=lambda x:x[1], reverse=True)
        temp_ls = []
        for tuple in sorted_rvol_ls:
            if tuple[1] > 0:
                temp_ls.append(tuple[0])
        self.universe = temp_ls[:10]
        for symbol in self.universe:
            self.Debug(f'{symbol} has avg daily ATR of {self.ATR(symbol, 20, Resolution.Daily).Current.Value}, avg daily volume of {self.SMA(symbol, 20, Resolution.Daily, Field.Volume)} and intraday volume of {self.volume_by_symbol[symbol]} and rvol of {self.rvol_by_symbol[symbol]} and gap of {self.gap[symbol]} and green vol of {self.green_vol[symbol]} and red vol of {self.red_vol[symbol]} at {self.Time}')
            #self.vwap_dict[symbol] = SymbolData(symbol, self)
            
        # Clear all dictionary to start fresh the next day
        self.volume_by_symbol.clear()
        self.rvol_by_symbol.clear()
        self.open_by_symbol.clear()
        self.gap.clear()
        self.vol_after_gap.clear()
        self.logged = False
        self.can_trade_flag = True

    def ClosePositions(self):        
        if self.Portfolio.Invested:
            self.Liquidate()

class SymbolData:
    def __init__(self,symbol,algo):
        self.algo = algo
        self.symbol = symbol
        self.vwap = algo.VWAP(self.symbol)

    def Update(self,time,close):
        self.vwap.Update(time,close)