Overall Statistics
Total Trades
52
Average Win
0.05%
Average Loss
-0.03%
Compounding Annual Return
-25.747%
Drawdown
0.400%
Expectancy
-0.344
Net Profit
-0.244%
Sharpe Ratio
-8.838
Probabilistic Sharpe Ratio
0%
Loss Rate
77%
Win Rate
23%
Profit-Loss Ratio
1.84
Alpha
-0.075
Beta
-0.136
Annual Standard Deviation
0.021
Annual Variance
0
Information Ratio
-6.048
Tracking Error
0.165
Treynor Ratio
1.362
Total Fees
$393.99
clr.AddReference('QuantConnect.Research')
from QuantConnect.Research import QuantBook


class TachyonMultidimensionalChamber(QCAlgorithm):

    def Initialize(self):
        self.SetStartDate(2020, 11, 15)  # Set Start Date
        self.SetEndDate(2020, 11, 17)  # Set End Date
        self.SetCash(400000)  # Set Strategy Cash
        self.AddUniverse(self.CoarseSelectionFunction)
        
        self.SetSecurityInitializer(self.SecurityInitializer)
        
        self.UniverseSettings.ExtendedMarketHours = True
        self.SetBrokerageModel(BrokerageName.InteractiveBrokersBrokerage, AccountType.Margin)
        self.UniverseSettings.Leverage = 4
        #self.UniverseSettings.Resolution = Resolution.Hour #can comment/change this out
        
        for s in self.Securities:
            self.Debug(self.Securities[s])
        
        #variables to keep track of
        self.sd = {} #all symbol data
        
        self.hod = {}
        self.lod = {}
        self.open = {}

        self.buffer = 0 #only enter trades after N buffer after open
        self.ema5_past = {}
        self.oh_past5 = {}
        
        #self.pmhigh = {}
        #self.pmlow = {}
        
        self.day = self.Debug(self.Time.weekday())



    def SecurityInitializer(self, security):
        security.SetLeverage(4)
    
    
    def CoarseSelectionFunction(self, universe):  
        selected = []
        for coarse in universe:  
            if coarse.Volume > 70000000 and coarse.Value > 10 and coarse.HasFundamentalData:
                symbol = coarse.Symbol
                selected.append(symbol)
        return selected #list of objects of type Symbol
    
    
    def OnSecuritiesChanged(self, changed):
        for security in changed.AddedSecurities:
            symbol = security.Symbol
            if symbol not in self.sd:
                self.sd[symbol] = SymbolData(self, symbol)
        for security in changed.RemovedSecurities:
            symbol = security.Symbol
            self.sd.pop(symbol, None)



    ################
    # my functions #
    ################
    #sizing
    def positionSize(self, stop, currPrice, dollarSize):
        nShares = int(dollarSize / abs(stop - currPrice))
        return nShares
    
        
    ###get stop
    #most recent of oh_past5 is index 0, furthest is [-1]
    def getStop(self, oh_past5, vwap, atr):
        #init and parse
        firstToCross = -1000
        low_postCross = 2000000000.0
        
        #get first candle that crossed
        for i in range(0, 5):
            high, low = oh_past5[i].split()
            high = float(high)
            if high > vwap:
                firstToCross = i
        
        #low of all candles post-cross
        for i in range(0, i):
            high, low = oh_past5[i].split()
            low = float(low)
            if low < low_postCross:
                low_postCross = low
    
        return low_postCross + (.5*atr)
    
    
        #reset VWAP 
        def OnEndOfDay(self):
            for s in self.sd:
                self.sd[s].vwap.Reset()
    
    
    ########### 
    # on data #
    ###########
    def OnData(self, data):
        '''OnData event is the primary entry point for your algorithm. Each new data point will be pumped in here.
            Arguments:
                data: Slice object keyed by symbol containing the stock data
        '''
        


        if self.IsWarmingUp:
            return

        tradeBars = data.Bars #OHLC of past time interval
    
        for s in self.sd:
            
            self.Debug(self.Time.weekday())
            #reset VWAP on new day
            if self.day != self.Time.weekday(): #new day
                self.day = self.Time.weekday()
                self.vwap = self.VWAP(s, 2000, Resolution.Minute)
            
            #highs and lows of candles in past 5 minutes (even premarket)
            if data.ContainsKey(s):
                if s not in self.oh_past5:
                    self.oh_past5[s] = [] 
                if len(self.oh_past5[s]) < 5:
                    self.oh_past5[s].append(str(tradeBars[s].High) + " " + str(tradeBars[s].Low))
                elif len(self.oh_past5[s]) == 5:
                    del self.oh_past5[s][-1]
                    self.oh_past5[s].insert(0, str(tradeBars[s].High) + " " + str(tradeBars[s].Low))
        
            if self.IsMarketOpen(s) and data.ContainsKey(s):
                #this block should not execute in practice since PM data should always be used
                if s not in self.hod:
                    self.hod[s] = -1.0
                    self.lod[s] = 2000000000.0
                    self.open[s] = -1.0
                
                
                #get hod and lod
                if tradeBars[s].High > self.hod[s]:
                    self.hod[s] = tradeBars[s].High
                if tradeBars[s].Low < self.lod[s]:
                    self.lod[s] = tradeBars[s].Low
                if self.open[s] == -1.0:
                    self.open[s] = tradeBars[s].Open
                
                
                ################
                # trade setups #
                ################
                ### consolidation above VWAP, short-term trend up, has not traveled much of a daily ATR, in upper part of daily range --> ORB up
                vwap = self.sd[s].vwap.Current.Value
                ema5 = self.sd[s].ema5.Current.Value
                ema9 = self.sd[s].ema9.Current.Value
                atr5 = self.sd[s].atr5.Current.Value
                #day_atr = self.atrDaily[d].Current.Value
                
                price = float(tradeBars[s].Close)
                hod = float(self.hod[s])
                lod = float(self.lod[s])
                ema5_past = self.ema5_past[s]
                day_range = hod - lod
                
                #position size in dollars
                size = 50.0
                
                if self.Portfolio[s].Invested == False and self.Portfolio.MarginRemaining > size and self.buffer >= 1:
                    if ema5 > vwap and (price > (lod + (.45 * day_range))) and ema5 > ema5_past and (price - vwap) < atr5 and self.Time.hour < 15: #and day_range <  day_atr and price > vwap:
                        #long
                        self.Debug(self.Time)
                        self.Debug("New long")
                        
                        stop = self.getStop(self.oh_past5[s], vwap, atr5)
                        shares = self.positionSize(stop, price, size)
                        
                        self.Debug("margin remaining: " + str(self.Portfolio.MarginRemaining))
                        self.Debug(s)
                        #self.Debug("nShares: " + str(shares))
                        self.Debug("currPrice: " + str(price))
                        self.Debug("stop: " + str(stop))
                        self.Debug("buying power used: " + str(shares*price))
                        self.Debug("VWAP: " +  str(vwap))
                        self.Debug("")
                        
                        self.MarketOrder(s, shares)
                        stop = -1*shares
                        self.StopMarketOrder(s, stop, lod)
                elif self.Portfolio[s].Invested == True:
                    #a "good" trade
                    if ema5 < ema9 and price < ema9 and ema5 < ema5_past:
                        self.Liquidate(s)
                        
                        self.Debug(self.Time)
                        self.Debug("Sold " + str(s))
                    #is failing, and stop out before hitting S/L
                    
                    #has consolidated for too long without moving up
                    
                    #take partials:
                        #HOD (or other significant S/R) and PM high at least are reasonable
                        #>daily ATR
                        
                
                #close all at 3:55 if any positions are open
                #should change this to time to market close
                    #use BeforeMarketClose()
                if self.Time.hour >= 15 and self.Time.minute >= 55: 
                    self.Liquidate()
                
                #increase buffer during market hours
                self.buffer += 1
                
            #not during market hours, reset various
            else:
                self.hod[s] = -1.0
                self.lod[s] = 2000000000.0
                self.open[s] = -1.0
                self.buffer = 0
            
            
            #past period ema5
            self.ema5_past[s] = self.sd[s].ema5.Current.Value
       

        
class SymbolData:
    def __init__(self, algorithm, symbol):
        #algorithm.VWAP(symbol, 10000, Resolution.Minute)
        self.vwap = algorithm.VWAP(symbol, 2000, Resolution.Minute) #60*24 = 1440 minutes in a day
        self.ema5 = algorithm.EMA(symbol, 5, Resolution.Minute)
        self.ema9 = algorithm.EMA(symbol, 9, Resolution.Minute)
        self.atr5 = algorithm.ATR(symbol, 5, Resolution.Minute)
        
        hist = algorithm.History(symbol, 10, Resolution.Minute).loc[symbol]
        for idx, bar in hist.iterrows():
            tradeBar = TradeBar(idx, symbol, bar.open, bar.high, bar.low, bar.close, bar.volume, timedelta(minutes=1))
            self.ema5.Update(idx, bar.close)
            self.ema9.Update(idx, bar.close)
            self.atr5.Update(tradeBar)
            self.vwap.Update(tradeBar)