Overall Statistics
class VolatilityRiskPremiumEffect(QCAlgorithm):

    def Initialize(self):
        self.SetBrokerageModel(BrokerageName.InteractiveBrokersBrokerage)

        self.SetStartDate(2010, 1, 1)
        self.SetCash(100000)
        
        underlying = self.AddEquity("SPY", Resolution.Minute)
        self.symbol = underlying.Symbol
        
        option = self.AddOption("SPY", Resolution.Minute)
        option.SetFilter(-20, 20, 25, 35)
        
        self.last_day = -1
        
    def OnData(self,slice):
        # Check once a day.
        if self.Time.day == self.last_day:
            return
        self.last_day = self.Time.day
            
        for i in slice.OptionChains:
            chains = i.Value

            # checks if the portfolio is empty, if yes then it sells options
            if not self.Portfolio.Invested:
                # divide option chains into call and put options 
                calls = list(filter(lambda x: x.Right == OptionRight.Call, chains))
                puts = list(filter(lambda x: x.Right == OptionRight.Put, chains))
                
                # if lists are empty return
                if not calls or not puts: return
            
                # get the underlying price
                underlying_price = self.Securities[self.symbol].Price
                
                # determine expiration date nearly one month
                expiries = [i.Expiry for i in puts]
                expiry = min(expiries, key=lambda x: abs((x.date()-self.Time.date()).days-30))
                
                # determine at-the-money strike
                strikes = [i.Strike for i in puts]
                strike = min(strikes, key=lambda x: abs(x-underlying_price))
        
                # select the exact options we want
                atm_call = [i for i in calls if i.Expiry == expiry and i.Strike == strike]
                atm_put = [i for i in puts if i.Expiry == expiry and i.Strike == strike]
        
                if atm_call and atm_put:
                    # calculate number of contracts to sell
                    options_q = int(self.Portfolio.MarginRemaining / (underlying_price * 100))
                    
                    # sell at-the-money straddle
                    self.Sell(atm_call[0].Symbol, options_q)
                    self.Sell(atm_put[0].Symbol, options_q)
            
            # after assigment, it liquidates our SPY position.
            invested = [x.Key for x in self.Portfolio if x.Value.Invested]
            if len(invested) == 1:
                self.Liquidate(self.symbol)