Overall Statistics
class TransdimensionalPrism(QCAlgorithm):

    def Initialize(self):
        self.SetStartDate(2020, 1, 1)  # Set Start Date
        self.SetEndDate(2020, 4, 5)
        self.SetCash(10000)  # Set Strategy Cash
        self.SetWarmUp(15)
        #SPY DATA
        spy = self.AddEquity("SPY", Resolution.Minute)
        spy.SetDataNormalizationMode(DataNormalizationMode.Raw)
        self.spy = spy.Symbol
        self.spycontract = None
        
        #TQQQ DATA
        
        tqqq = self.AddEquity("TQQQ", Resolution.Minute)
        tqqq.SetDataNormalizationMode(DataNormalizationMode.Raw)
        self.tqqq = tqqq.Symbol
        self.tqqqcontract = None
        
        
        #Scheduled Events============================================
        self.Schedule.On(self.DateRules.MonthStart("SPY"), self.TimeRules.AfterMarketOpen("SPY", 1), self.monthlyRebalance)
        self.Schedule.On(self.DateRules.EveryDay("SPY"), self.TimeRules.AfterMarketOpen("SPY", 1), self.captureOpening)
        #Variables needed for stoploss
        self.stoplosshold = 0
        
        self.dailyOpen: Dict[Symbol,float] = {
            self.spy: 0,
        
        }

        self.weights: Dict[Symbol,float] = {
            self.spy: 0.9,
        
        }
        
        #Underlyings and Options dictionary
        self.uNos = {
            #self.spy  : self.spycontract,
            self.tqqq : self.tqqqcontract
        }

    def OnData(self, data): 
        
        if not self.Portfolio[self.spy].Invested:
            self.SetHoldings(self.spy, 0.9)
            
        # DO HEDGE
        self.getContract()
        
        self.timeDC()
        
        self.setContracts()
        
        self.exerciseContracts()
        
        
        
    def getContract(self):
        
        for underlying, contract in self.uNos.items():
            if contract is None:
                targetStrike = (self.Securities[underlying].Price * 0.6) - (self.Securities[underlying].Price * 0.6)%5
                xcontracts = self.OptionChainProvider.GetOptionContractList(underlying, self.Time)
                puts = [x for x in xcontracts if x.ID.OptionRight == OptionRight.Put]
                puts = sorted( sorted(puts, key = lambda x: x.ID.Date, reverse = True),
                       key = lambda x: x.ID.StrikePrice)
                puts = [x for x in puts if x.ID.StrikePrice == targetStrike]
                puts = [x for x in puts if 270 < (x.ID.Date - self.Time).days <= 420]
                if len(puts) == 0:
                    
                    return None
                self.AddOptionContract(puts[0], Resolution.Minute)
                self.uNos[underlying] = puts[0]
                
                x = 0 

    def timeDC(self):
        
        for underlying, contract in self.uNos.items():
            if contract is None:
                continue
            if (contract.ID.Date - self.Time).days < 180:
            #self.Log("Liquidated time 180")
                self.Liquidate(contract)
                self.RemoveSecurity(contract)
                self.uNos[underlying] = None
            return
    
    
    def setContracts(self):
        for underlying, contract in self.uNos.items():
            if contract is None:
                continue
            if not self.Portfolio[contract].Invested:
                self.SetHoldings(contract, 0.1)
                #self.Log("Set Holdings")
    
    
    def exerciseContracts(self):
        for underlying, contract in self.uNos.items():
            if contract is None:
                continue
            
            #self.Log(self.Portfolio[contract].UnrealizedProfit)
            #self.Log(self.Portfolio[contract].UnrealizedProfitPercent)
            #Exercise When reach strike price
            if self.Securities[underlying].Price < contract.ID.StrikePrice:
            #if self.Log(self.Portfolio[contract].UnrealizedProfit) > 1000:
                #self.Log("Liquidated StrikePrice")
                self.Liquidate(contract)
                #self.RemoveSecurity(contract)
                self.RemoveOptionContract(contract)
                self.uNos[underlying] = None
                x=0
                
                
                
                
    def monthlyRebalance(self):
        # Rebalance portfolio monthly 
        if self.IsWarmingUp:
            return
        for key,value in self.weights.items():
            self.SetHoldings(key,value)
            
        
    def captureOpening(self):
        #Grabs the daily opening price of spy for our stoploss method
        if self.IsWarmingUp:
            return
        for key, value in self.dailyOpen.items():
            if self.CurrentSlice.Bars.ContainsKey(key):
                self.dailyOpen[key] = self.CurrentSlice[key].Open
            self.stoplosshold = 0
            
            
    def stoploss(self, data):
        '''
        Stoploss logic:
            - If spy drops more than 5% liquidate entire equity portfolio
            - Change stoplosshold value to 1, this indicates that the portfolios SL has been hit 
            and were going to hold until the next trading day
        '''
        if self.IsWarmingUp:
            return
        
        
        for symbol, weight in self.weights.items():
            if self.Securities[symbol].Price == 0:
                continue
            open = self.dailyOpen[symbol]
            #See if any symbol has decreased more than 5% in a given day, liquidate if true and check next one... 
            if ((self.Securities[symbol].Price-open)/self.Securities[symbol].Price) < -.05:
                self.SetHoldings(symbol, 0)
                
                self.stoplosshold = 1
                #self.Log('HIT')
        
    def plotting(self):
        pass