Overall Statistics
Total Orders
42
Average Win
7.60%
Average Loss
0%
Compounding Annual Return
11.410%
Drawdown
2.700%
Expectancy
0
Start Equity
100000
End Equity
115471
Net Profit
15.471%
Sharpe Ratio
1.069
Sortino Ratio
0.363
Probabilistic Sharpe Ratio
85.888%
Loss Rate
0%
Win Rate
100%
Profit-Loss Ratio
0
Alpha
0
Beta
0
Annual Standard Deviation
0.05
Annual Variance
0.002
Information Ratio
1.59
Tracking Error
0.05
Treynor Ratio
0
Total Fees
$29.00
Estimated Strategy Capacity
$1000.00
Lowest Capacity Asset
CAT Y05J8JWKMFL2|CAT R735QTJ8XC9X
Portfolio Turnover
0.03%
# region imports
from AlgorithmImports import *
# endregion

class Sellpowerhouseputs(QCAlgorithm):

    def Initialize(self):
        # Locally Lean installs free sample data, to download more data please visit https://www.quantconnect.com/docs/v2/lean-cli/datasets/downloading-data
        self.SetStartDate(2022, 1, 1)
        self.SetEndDate(2023, 5, 1)
        self.SetCash(100000)
        self.SMAS200 = {}
        self.SMAS50 = {}
        self.RSIS = {}
        self.RSISrollingWindows = {}
        self.SMAS200rollingWindows = {}
        self.SMAS50rollingWindows = {}
        self.sma_rolling_length = 10

        self.options = {}

        self.MACDS = {}

        self.sold_puts = {}
        self.sold_calls = {}
        self.WeakOrStrongs = {}
        self.DaysAboveSMA = {}
        self.DaysBelowSMA = {}

        self.LoggedToday = {}

        self.activeStocks = set()
        
        self.popular2023stocks = ["TSLA", "AAPL", "MSFT", "NVDA", "AMZN", "META", "AMD", "NFLX", "UNH", "GOOG", "BA", "PYPL", "JPM", "XON", "VZ", "BAC", 
                                  "DIS", "TMUS", "AVGO", "BRKB", "CRM", "SQ", "NKE", "TMO", "JNJ", "COST", "HD", "V", "CSCO", "PEP", "WMT", "CMCSA" 
                                  ,"SHOP", "INTU", "DHR", "DVN", "MU", "TXN", "SBUX", "PFE", "P", "SCH", "AMGN", "ACN", "COUP", "PXD", "CHEX", "CAT", "SNOW", 
                                  "CVS", "USB", "GS", "WFC", "BMY", "PCLN", "LLY", "GE", "AMAT", "UBER", "LRCX", "MDT", "CNTE", "CHTR", "DH", "F", "CI", "ALB", 
                                  "HUM", "PANW", "MPC", "DE", "GILD", "IBM", "FPL", "DG", "RIVN", "CMG", "MELI", "SLB", "ATH", "VLO", "EL", "LOW", "NOW", "MOH", 
                                  "LULU", "NEM", "D", "BLK", "ETSY", "MHP", "AMT", "ADI", "ORLY", "AXP", "EOG", "GM", "ACL", "RBLX", "TEAM", "CBRNA", "UPS", "LUV", 
                                  "ISRG", "AUD", "CEY", "WDAY", "CRWD", "MRVL", "PLUG", "MS", "UTX", "UNP", "HZNP", "ABT", "FDX", "KFT", "CEG", "AZO", "GPC", "REGN", 
                                  "SEDG", "FCX", "NOC", "FANG", "PINS", "MDB", "APD", "CAH", "ETR", "DLTR", "UAUA", "ABNB", "IDPH", "YUM", "CME", "TROW", "ALD", "HPE", 
                                  "PSX", "AR", "LVS", "NXPI", "DOCU", "ABX", "CF", "PCG", "TFC", "O", "WYNN", "KLAC", "COIN", "PM", "VRTX", "FSLR", "SO", "ZTS", "MMC", 
                                  "AAL", "APA", "DHI", "ZS", "TJX", "DAL", "PSA", "CCL", "EQT", "HAL", "ATVI", "HCA", "ON", "TWRS", "BDX", "ULTA", "PNC", "MMM", "DDOG", 
                                  "MCHP", "GIS", "MAR", "CSX", "INTC", "ENPH", "OXY", "QCOM", "PX", "KO", "ORCL", "LMT", "PG", "ADBE", "BX", "MA", "C", "ABBV", "MRK", "T", "MCD", "MRNA"]
        for stock in self.popular2023stocks[:60]:
            x = self.AddEquity(stock, Resolution.Daily)
            
            self.AddEquity(str(x.Symbol).split()[0], Resolution.Daily)
            self.activeStocks.add(x.Symbol)
            self.options[x.Symbol] = self.AddOption(str(x.Symbol).split(" ")[0])
            if x.Symbol not in self.SMAS200:
                self.SMAS200[x.Symbol] = self.SMA(x.Symbol, 200, Resolution.Daily)
                self.SMAS50[x.Symbol] = self.SMA(x.Symbol, 50, Resolution.Daily)
                self.RSIS[x.Symbol] = self.RSI(x.Symbol, 30, MovingAverageType.Simple, Resolution.Daily)
                self.RSISrollingWindows[x.Symbol] = RollingWindow[float](10)
                self.SMAS200rollingWindows[x.Symbol] = RollingWindow[float](self.sma_rolling_length)
                self.SMAS50rollingWindows[x.Symbol] = RollingWindow[float](self.sma_rolling_length)
                self.MACDS[x.Symbol] = self.macd(x.Symbol, 50, 200, 9, MovingAverageType.Exponential)
                
                # initialize SMA and RSI
                history = self.History[TradeBar](x.Symbol, 200, Resolution.Daily)
                for bar in history:
                    self.SMAS200[x.Symbol].Update(bar.EndTime, bar.close)
                    self.SMAS50[x.Symbol].Update(bar.EndTime, bar.close)
                    self.RSIS[x.Symbol].Update(bar.EndTime, bar.close)
                    self.MACDS[x.Symbol].Update(bar.EndTime, bar.close)
                    self.RSISrollingWindows[x.Symbol].Add(self.RSIS[x.Symbol].Current.Value)
                    self.SMAS200rollingWindows[x.Symbol].Add(self.SMAS200[x.Symbol].Current.Value)
                    self.SMAS50rollingWindows[x.Symbol].Add(self.SMAS50[x.Symbol].Current.Value)

                    
            if x.Symbol not in self.WeakOrStrongs:
                self.WeakOrStrongs[x.Symbol] = "Neutral"
                self.DaysAboveSMA[x.Symbol] = 0
                self.DaysBelowSMA[x.Symbol] = 0
        
        #self.AddUniverse(self.CoarseFilter, self.FineFilter)
        self.UniverseSettings.Resolution = Resolution.Daily
        #self.rebalanceTime = self.Time


        self.UniverseSettings.DataNormalizationMode = DataNormalizationMode.Raw
        self.set_brokerage_model(BrokerageName.InteractiveBrokersBrokerage, AccountType.Margin)


    def OnData(self, data: Slice):
        self.Log("len active: " + str(len(self.activeStocks)))
        for symbol in self.activeStocks:

            if not data.ContainsKey(symbol) or data[symbol] is None:
                self.Log("No data for symbol: " + str(symbol))
                return
            #self.Log("working on symbol in data: " + str(symbol) + "len active: " + str(len(actives)))
            
            
            if not self.SMAS200[symbol].IsReady or not self.RSIS[symbol].IsReady:
                self.Log("SMA or RSI not ready for symbol: " + str(symbol))
                return
            
            self.RSISrollingWindows[symbol].Add(self.RSIS[symbol].Current.Value)
            self.SMAS200rollingWindows[symbol].Add(self.SMAS200[symbol].Current.Value)
            self.SMAS50rollingWindows[symbol].Add(self.SMAS50[symbol].Current.Value)
            
            rsi_trend = "neutral"

            # check if RSI trend is upward or downward
            if self.RSISrollingWindows[symbol].IsReady:
                if self.RSISrollingWindows[symbol][0] > self.RSISrollingWindows[symbol][13]:
                    rsi_trend = "upward"
                    self.Log("RSI trend is upward for symbol: " + str(symbol))
                elif self.RSISrollingWindows[symbol][0] < self.RSISrollingWindows[symbol][13]:
                    rsi_trend = "downward"
                    self.Log("RSI trend is downward for symbol: " + str(symbol))
                else:
                    self.Log("RSI trend is neutral for symbol: " + str(symbol))

            recent_sma_cross = None

            # check if the 50 day SMA has crossed the 200 day SMA
            min_50 = min(self.SMAS50rollingWindows[symbol])
            max_50 = max(self.SMAS50rollingWindows[symbol])
            min_200 = min(self.SMAS200rollingWindows[symbol])
            max_200 = max(self.SMAS200rollingWindows[symbol])
            if min_50 < min_200 and max_50 > max_200:
                recent_sma_cross = True
                self.Log("50 day SMA has crossed the 200 day SMA for symbol: " + str(symbol))
            else:
                recent_sma_cross = False
                
            
            #only update on the end of day
            if self.time.hour == 0 and self.time.minute == 0:
                if self.SMAS200[symbol].Current.Value < data[symbol].Close and self.SMAS50[symbol].Current.Value < data[symbol].Close:
                    self.DaysAboveSMA[symbol] += 1
                    self.DaysBelowSMA[symbol] = 0
                elif self.SMAS200[symbol].Current.Value > data[symbol].Close and self.SMAS50[symbol].Current.Value > data[symbol].Close:
                    self.DaysBelowSMA[symbol] += 1
                    self.DaysAboveSMA[symbol] = 0
                else:
                    self.DaysBelowSMA[symbol] = 0
                    self.DaysAboveSMA[symbol] = 0


            if self.WeakOrStrongs[symbol] != "Weak" and (self.RSIS[symbol].Current.Value >= 65 or self.RSIS[symbol].Current.Value <=35) and self.DaysBelowSMA[symbol] >= 3 and rsi_trend == "downward" and self.SMAS50[symbol].Current.Value < self.SMAS200[symbol].Current.Value and self.MACDS[symbol].Current.Value < -1.5 and recent_sma_cross == True:
                self.Log("symbol: " + str(symbol.value) + " emits a weak signal")
                self.WeakOrStrongs[symbol] = "Weak"
            elif self.WeakOrStrongs[symbol] != "Strong" and self.RSIS[symbol].Current.Value >= 40 and self.RSIS[symbol].Current.Value <= 60 and self.DaysAboveSMA[symbol] >= 3 and rsi_trend == "upward" and self.SMAS50[symbol].Current.Value > self.SMAS200[symbol].Current.Value and self.MACDS[symbol].Current.Value > 1.5 and recent_sma_cross == True:
                self.Log("symbol: " + str(symbol.value) + " emits a strong signal")
                self.WeakOrStrongs[symbol] = "Strong"
            else:
                if self.time.hour == 9 and self.time.minute == 35:
                    self.Log("symbol: " + str(symbol.value) + "self.DaysBelowSMA: " + str(self.DaysBelowSMA[symbol]) + " self.DaysAboveSMA: " + str(self.DaysAboveSMA[symbol]) + " self.RSI: " + str(self.RSIS[symbol].Current.Value) + " self.WeakOrStrong: " + str(self.WeakOrStrongs[symbol]))

            self.Log("symbol: " + str(symbol) + " self.DaysBelowSMA: " + str(self.DaysBelowSMA[symbol]) + " self.DaysAboveSMA: " + str(self.DaysAboveSMA[symbol]) + " self.RSI: " + str(self.RSIS[symbol].Current.Value) + " self.WeakOrStrong: " + str(self.WeakOrStrongs[symbol]))    
            self.Log("      price: " + str(data[symbol].Close) + " SMA200: " + str(self.SMAS200[symbol].Current.Value) + " SMA50: " + str(self.SMAS50[symbol].Current.Value))
            if self.WeakOrStrongs[symbol] == "Weak":
                # sell calls
                
                option = self.options[symbol]
                option.set_filter(min_strike=-7, max_strike=7, min_expiry=timedelta(days=30), max_expiry=timedelta(days=90))
            
                if symbol in self.sold_calls and len(self.sold_calls[symbol]) > 0:
                        return
                
                # determine the standard deviation of the underlying security
                history = self.History(symbol, 30, Resolution.Daily)
                if history.empty:
                    return
                returns = history["close"].dropna()
                std = returns.std()
                #self.Log("Standard deviation: " + str(std))
                low_end = std
                high_end = 1.5*std

                
                #strikes(low_end, high_end).
                chain = data.option_chains.get(option.Symbol)
                if chain:
                    #self.Log("Chain found for: " + str(self.symbol))
                    
                    # sell puts
            
                    expiry = max([x.expiry for x in chain])
                    self.Log("max epiry for symbol " + str(symbol) + " is " + str(expiry))
                    self.Log("min expiry for symbol " + str(symbol) + " is " + str(min([x.expiry for x in chain])))
                    chain = sorted([x for x in chain if x.expiry == expiry], key = lambda x: x.strike)
                    self.Log("min and max strikes: " + str(chain[0].strike) + " " + str(chain[-1].strike))
                        
                    call_contracts = [x for x in chain if x.right == OptionRight.CALL]
                        
                    if len(call_contracts) == 0:
                        return
                    
                    # sell the put with the strike closest to the high end of the range
                    contract = call_contracts[-1]

                    for i in range(20):
                        self.Sell(contract.Symbol, 1)
                    self.Log("*********     Sold call: " + str(contract.Symbol) + "strike: " + str(contract.Strike) + "expiry: " + str(contract.Expiry) + "current price: " + str(data[symbol].Close))
                    if symbol not in self.sold_calls:
                        self.sold_calls[symbol] = set()
                    self.sold_calls[symbol].add(contract.Symbol)
                
            elif self.WeakOrStrongs[symbol] == "Strong":
                option = self.options[symbol]
                option.set_filter(min_strike=-7, max_strike=7, min_expiry=timedelta(days=30), max_expiry=timedelta(days=90))
            
                if symbol in self.sold_puts and len(self.sold_puts[symbol]) > 0:
                        return
                
                #if self.RSIS[symbol].Current.Value >60:
                #    return

                # determine the standard deviation of the underlying security
                history = self.History(symbol, 30, Resolution.Daily)
                if history.empty:
                    return
                returns = history["close"].dropna()
                std = returns.std()
                #self.Log("Standard deviation: " + str(std))
                low_end = -1.5 * std
                high_end = -std

                #.strikes(low_end, high_end)
                chain = data.option_chains.get(option.Symbol)
                if chain:
                    #self.Log("Chain found for: " + str(self.symbol))
                    
                    # sell puts
            
                    expiry = max([x.expiry for x in chain])
                    chain = sorted([x for x in chain if x.expiry == expiry], key = lambda x: x.strike)

                    self.Log("max epiry for symbol " + str(symbol) + " is " + str(expiry))
                    self.Log("min expiry for symbol " + str(symbol) + " is " + str(min([x.expiry for x in chain])))
                    self.Log("min and max strikes: " + str(chain[0].strike) + " " + str(chain[-1].strike))                    

                    put_contracts = [x for x in chain if x.right == OptionRight.PUT]
                        
                    if len(put_contracts) == 0:
                        return
                    
                    # sell the put with the strike closest to the high end of the range
                    contract = put_contracts[0]

                    quantity = self.CalculateOrderQuantity(contract.Symbol, .25)

                    self.Log("symbol: " + str(symbol) + " quantity: " + str(quantity) + " contract: " + str(contract.Symbol) + " strike: " + str(contract.Strike) + " expiry: " + str(contract.Expiry) + " current price: " + str(data[symbol].Close))
                    for i in range(20):
                        self.Sell(contract.Symbol, 1)
                    self.Log("*********     Sold put: " + str(contract.Symbol) + "strike: " + str(contract.Strike) + "expiry: " + str(contract.Expiry) + "current price: " + str(data[symbol].Close))
                    if symbol not in self.sold_puts:
                        self.sold_puts[symbol] = set()
                    self.sold_puts[symbol].add(contract.Symbol)
      
        if symbol in self.sold_puts and len(self.sold_puts[symbol]) > 0:
            # if profit is 80% of the premium, buy back the put
            for contract in self.sold_puts[symbol]:
                if self.Portfolio[contract].UnrealizedProfitPercent > 0.8:
                    #self.SetHoldings(contract, 0)
                    self.sold_puts[symbol].remove(contract)
                    self.Log("Bought back put: " + str(contract.Symbol))
        if symbol in self.sold_calls and len(self.sold_calls[symbol]) > 0:
            # if profit is 80% of the premium, buy back the call
            for contract in self.sold_calls[symbol]:
                if self.Portfolio[contract].UnrealizedProfitPercent > 0.8:
                    self.SetHoldings(contract, 0)
                    #self.sold_calls[symbol].remove(contract)
                    self.Log("Bought back call: " + str(contract.Symbol))