Overall Statistics
Total Trades
228
Average Win
0.15%
Average Loss
-0.21%
Compounding Annual Return
5.856%
Drawdown
2.600%
Expectancy
0.018
Net Profit
0.495%
Sharpe Ratio
0.545
Probabilistic Sharpe Ratio
43.821%
Loss Rate
40%
Win Rate
60%
Profit-Loss Ratio
0.71
Alpha
0
Beta
0
Annual Standard Deviation
0.081
Annual Variance
0.007
Information Ratio
0.545
Tracking Error
0.081
Treynor Ratio
0
Total Fees
$556.07
Estimated Strategy Capacity
$17000000.00
Lowest Capacity Asset
AAPL R735QTJ8XC9X
from AlgorithmImports import *

class IntradayBreakout(QCAlgorithm):

    def Initialize(self):
        self.UniverseSettings.Resolution = Resolution.Second
        self.SetStartDate(2022, 3, 18)
        self.SetEndDate(2022, 4, 18)
        self.SetWarmUp(timedelta(days = 1))
        
        
        self.SetCash(100000)
        self.SetTimeZone("America/New_York")
        self.SetBrokerageModel(BrokerageName.InteractiveBrokersBrokerage, AccountType.Margin)

        self.stopLossPercentLong = .998
        self.stopLossPercentLong2 = .999
        self.takeProfitPercentLong = 1.003
        self.takeProfitPercentLong2 = 1.003
       
        self.stopLossPercentShort = 1.005
        self.stopLossPercentShort2 = 1.0015
        self.takeProfitPercentShort = .996
        self.takeProfitPercentShort2 = .995
        
        self.timeCheck = False
        
        
        
        
        self.symbolData = {}

        tickers = ["AAPL"] #just one ticker for ease of debugging
        for ticker in tickers:
            symbol = self.AddEquity(ticker, Resolution.Second).Symbol
            self.symbolData[symbol] = SymbolData(self, symbol)

        everyday = self.DateRules.EveryDay(symbol)
        
        self.Schedule.On(everyday, self.TimeRules.BeforeMarketClose(symbol, 10), self.Liquidate)
        self.Schedule.On(everyday, self.TimeRules.BeforeMarketClose(symbol, 30), self.timeCheckNight)
        self.Schedule.On(everyday, self.TimeRules.AfterMarketOpen(symbol, 5), self.timeCheckMorning)
        
        
    def timeCheckMorning(self):
        self.timeCheck = True
    
    def timeCheckNight(self):
        self.timeCheck = False
        
        
    
    def OnEndOfDay(self):
        
        for data in self.symbolData.values():
            data.longTrade = True
            data.shortTrade = True
            data.killswitch = False
            data.killswitch2 = False
            data.killswitch3 = False
            data.vwap.Reset()
   
    
    
    
    def OnData(self, slice):
       
       
       
        if not all([data.IsReady for data in self.symbolData.values()]):
            return
        
        for symbol, data in self.symbolData.items():
            if slice.Bars.ContainsKey(symbol):
                data.window15sec.Add(slice.Bars[symbol])
                #####self.Debug(f'{symbol} test')
       
        
        
        
        for symbol, data in self.symbolData.items():
            price = self.Securities[symbol].Price
            stddeviationwindowlong = (3 * data.stdcalc.Current.Value)
            #when I debug, clearly the calculation is wrong, because it should take 15 seconds to recieve a new bar, but it updates more frequently than that, and sometimes returns 0.00
            #self.Debug(f'{symbol} {stddeviationwindowlong} stdtest')
            
            
            if data.window15sec.IsReady:
                windowDelta = data.window15sec[0].Close - data.window15sec[14].Open
                # if I reverse the greater than operator, the algo will not place orders, presently, the algo places the same orders if lines 85-88 are commented out
                # I **want** it to place orders if the windowDelta is greater than 3* the std deviation of my 15 second ranges created in symbolData
                if windowDelta < stddeviationwindowlong:  
                    if data.rsi.Current.Value < 25:
                        if data.vwap.Current.Value / price > 1.003: 
                            if data.longTrade: 
                                if self.timeCheck:
                                    marketorderticket = self.SetHoldings(symbol, 1.0)
                                    data.stopmarketorderticket = self.StopMarketOrder(symbol, -self.Portfolio[symbol].Quantity, price * self.stopLossPercentLong)
                                    data.limitorderticket = self.LimitOrder(symbol, round(-self.Portfolio[symbol].Quantity / 2), price * self.takeProfitPercentLong)
                                    data.longTrade = False
        
        #worry about shorts later
        '''for symbol, data in self.symbolData.items():
            price = self.Securities[symbol].Price
            stddeviationwindowshort = (-3 * data.stdcalc.Current.Value)
            
            if (data.smaVol15.Current.Value / data.smaVol390.Current.Value) > 2.0:
                if price < data.vwap.Current.Value:
                    if  data.window15min.IsReady:
                        windowDelta = data.window15min[0].Close - data.window15min[4].Open
                        if windowDelta < stddeviationwindowshort: 
                            if data.shortTrade:
                                if self.timeCheck:
                                    marketorderticketshort = self.SetHoldings(symbol, -0.15)
                                    data.stopmarketorderticketshort = self.StopMarketOrder(symbol, -self.Portfolio[symbol].Quantity, price * self.stopLossPercentShort)
                                    data.limitorderticketshort = self.LimitOrder(symbol, round(-self.Portfolio[symbol].Quantity / 2), price * self.takeProfitPercentShort)
                                    data.shortTrade = False'''
                    
                    
                    
                    
        
               
            
            
    def OnOrderEvent(self, orderEvent):
        
        data = self.symbolData.get(orderEvent.Symbol, None)
        if not data or orderEvent.Status != OrderStatus.Filled:
            return
        
        for symbol, data in self.symbolData.items():
            price = self.Securities[symbol].Price
            
            if orderEvent.Status != OrderStatus.Filled:
                return
        
            
            if data.stopmarketorderticket != None and data.stopmarketorderticket.OrderId == orderEvent.OrderId:
                if data.killswitch2 == True:
                    data.killswitch3 = True
            
            if data.stopmarketorderticket != None and data.stopmarketorderticket.OrderId == orderEvent.OrderId:
                if data.killswitch == True:
                    data.killswitch2 = True
            
            
            
            if data.stopmarketorderticket != None and data.stopmarketorderticket.OrderId == orderEvent.OrderId:
                data.limitorderticket.Cancel()
                data.longTrade = True
                data.killswitch = True
                
                 
            
            if data.limitorderticket != None and data.limitorderticket.OrderId == orderEvent.OrderId:
                data.stopmarketorderticket.Cancel()
                data.limitorderticket2 = self.LimitOrder(symbol, -self.Portfolio[symbol].Quantity, price * self.takeProfitPercentLong2)
                data.stopmarketorderticket2 = self.StopMarketOrder(symbol, -self.Portfolio[symbol].Quantity, price * self.stopLossPercentLong2)
                data.killswitch = False
                
            if data.limitorderticket2 != None and data.limitorderticket2.OrderId == orderEvent.OrderId:
                data.stopmarketorderticket2.Cancel()
                data.longTrade = True
                  
                  
            if data.stopmarketorderticket2 != None and data.stopmarketorderticket2.OrderId == orderEvent.OrderId:
                data.limitorderticket2.Cancel()
                data.longTrade = True
            
            
            
            if data.stopmarketorderticketshort != None and data.stopmarketorderticketshort.OrderId == orderEvent.OrderId:
                data.limitorderticketshort.Cancel()
               
                
            if data.limitorderticketshort != None and data.limitorderticketshort.OrderId == orderEvent.OrderId:
                data.stopmarketorderticketshort.Cancel()
                data.limitorderticketshort2 = self.LimitOrder(symbol, -self.Portfolio[symbol].Quantity, price * self.takeProfitPercentShort2)
                data.stopmarketorderticketshort2 = self.StopMarketOrder(symbol, -self.Portfolio[symbol].Quantity, price * self.stopLossPercentShort2)
                
                
            if data.limitorderticketshort2 != None and data.limitorderticketshort2.OrderId == orderEvent.OrderId:
                  data.stopmarketorderticketshort2.Cancel()  
                  
                  
            if data.stopmarketorderticketshort2 != None and data.stopmarketorderticketshort2.OrderId == orderEvent.OrderId:
                data.limitorderticketshort2.Cancel()
            


class SymbolData:
    def __init__(self, algorithm, symbol):
        
        
        fifteensecConsolidator = TradeBarConsolidator(timedelta(seconds = 15))
        self.subscribe = algorithm.SubscriptionManager.AddConsolidator(symbol, fifteensecConsolidator)
        self.consolidateddd = algorithm.Consolidate(symbol, timedelta(seconds = 15), self.fifteensecbarhandler)
        
        self.high = algorithm.SMA(symbol, 1, Resolution.Minute, Field.High)
        self.low = algorithm.SMA(symbol, 1, Resolution.Minute, Field.Low)
        self.RegisterIndicator = algorithm.RegisterIndicator(symbol, self.high, fifteensecConsolidator)
        self.RegisterIndicator = algorithm.RegisterIndicator(symbol, self.low, fifteensecConsolidator)
        
        
        self.range = IndicatorExtensions.Minus(self.high,self.low)
        period = 1560 #15sec x 4 = 1 min, 390 min in session
        self.std = StandardDeviation(period)
        
        # should calculate the std deviation of the range of the 15 second bars and return updated calculation every 15 seconds when a new bar arrives and the oldest is discarded
        self.stdcalc = IndicatorExtensions.Of(self.range,self.std) 
        
        # window that updates every second, the idea is that at any point, the change in price over the most recent 15 seconds could be statistically outside of normal range
        self.window15sec = RollingWindow[TradeBar](15)
        
        
        
        self.vwap = algorithm.VWAP(symbol, 390, Resolution.Minute)
        self.rsi = algorithm.RSI(symbol, 20, Resolution.Minute)
        
        #one trade per security at a time
        self.longTrade = True
        self.shortTrade = True
        
        self.stopmarketorderticket = None
        self.stopmarketorderticket2 = None
        self.limitorderticket = None
        self.limitorderticket2 = None
        
        self.stopmarketorderticketshort = None
        self.stopmarketorderticketshort2 = None
        self.limitorderticketshort = None
        self.limitorderticketshort2 = None
    
            
    def fifteensecbarhandler(self, consolidated):
        pass
        
        
    
       
    @property 
    def IsReady(self):
        
         return all
         ([stdcalc.IsReady for stdcalc in self.stdcalc.values()])
         ([window15sec.IsReady for window15 in self.window15sec.values()])