Overall Statistics |
Total Orders 44 Average Win 0.06% Average Loss -0.03% Compounding Annual Return -0.185% Drawdown 0.400% Expectancy -0.014 Start Equity 1000000 End Equity 999895 Net Profit -0.010% Sharpe Ratio -2.924 Sortino Ratio -3.877 Probabilistic Sharpe Ratio 37.540% Loss Rate 64% Win Rate 36% Profit-Loss Ratio 1.71 Alpha -0.025 Beta 0.016 Annual Standard Deviation 0.008 Annual Variance 0 Information Ratio -0.961 Tracking Error 0.088 Treynor Ratio -1.477 Total Fees $44.00 Estimated Strategy Capacity $200000.00 Lowest Capacity Asset GOOCV XC7Z2QQWKEFA|GOOCV VP83T1ZUHROL Portfolio Turnover 10.21% |
# region imports from AlgorithmImports import * # endregion class Magnificent7(QCAlgorithm): def initialize(self): self.set_start_date(2020, 1, 10) self.set_end_date(2020, 1, 30) # Set End Date self.set_cash(1000000) self.underlying_tickers = ['GOOG', 'AAPL', 'AMZN', 'META', 'MSFT', 'NVDA', 'TSLA'] # Initialize variables self.option_symbol = {} # Dictionary to store symbols self.sma_dict = {} # Dictionary to store the SMA for each symbol self.current_IV_dict = {} self.IVR_52W_low_dict = {} self.IVR_52W_high_dict = {} self.min_IV_52W_low = {} self.max_IV_52W_high = {} self.SetBenchmark("SPY") # Set window for simple moving average self.sma_window = 100 for ticker in self.underlying_tickers: # Add equity to universe equity = self.add_equity(ticker) # Add equity option contracts to universe and create list option = self.add_option(ticker) # Filters the option chain so that only # strikes that are +$100 above current underlying price and # strikes $0 below current underlying price option and # within 0 to 45 days to expiration (DTE) of current date # are available. (Speeds up code!) option.set_filter(0, 100, timedelta(0), timedelta(45)) self.option_symbol[ticker] = option.symbol # Add SMA indicator to dictionary based on ticker symbol self.sma_dict[ticker] = self.sma(equity.Symbol, self.sma_window) # Dictionaries to keep track of 52 week high and low IV and current IV self.IVR_52W_low_dict[ticker] = [] self.IVR_52W_high_dict[ticker] = [] self.current_IV_dict[ticker] = [] # Set initial values for max and min values of IV self.min_IV_52W_low[ticker] = 0 self.max_IV_52W_high[ticker] = 0 # Run function every day after market closes to record daily high and low IV self.schedule.on(self.date_rules.every_day("GOOG"), self.time_rules.before_market_close("GOOG", -1), self.after_market_close) def on_data(self, slice: Slice) -> None: for ticker in self.underlying_tickers: if not self.is_market_open(self.option_symbol[ticker]): return bar = slice.bars.get(ticker) chain = slice.option_chains.get_value(self.option_symbol[ticker]) if chain is None: continue # two ways to check # Sort the contracts to find at the money (ATM) contract with closest expiration contracts = sorted(sorted(sorted(chain, \ key = lambda x: abs(chain.underlying.price - x.strike)), \ key = lambda x: x.expiry, reverse=False), \ key = lambda x: x.right, reverse=True) # if found, trade it if len(contracts) == 0: return current_IV = contracts[0].ImpliedVolatility if not self.current_IV_dict[ticker]: self.current_IV_dict[ticker] = [current_IV]*2 else: current_IV_low = self.current_IV_dict[ticker][0] current_IV_high = self.current_IV_dict[ticker][1] if (current_IV < current_IV_low): self.current_IV_dict[ticker][0] = current_IV elif (current_IV > current_IV_high): self.current_IV_dict[ticker][1] = current_IV if self.max_IV_52W_high[ticker] and self.min_IV_52W_low[ticker]: IVR = (current_IV-self.min_IV_52W_low[ticker]) / (self.max_IV_52W_high[ticker]-self.min_IV_52W_low[ticker]) * 100 else: IVR = None if bar and IVR: if not self.portfolio.invested and (bar.Close < self.sma_dict[ticker].Current.Value) and (IVR > 30): self.log(f"Current IVR for {ticker}: {IVR}. Buy 100 shares and sell covered call.") chain = slice.option_chains.get(self.option_symbol[ticker]) if chain is None: return # we sort the contracts to identify call options call = [x for x in chain if x.Right == OptionRight.Call] # we sort the contracts by closest expiration contracts = sorted(call, key = lambda x: x.expiry, reverse=False) # we sort the contracts based on given delta delta_contracts = sorted(contracts, key = lambda x: abs(x.Greeks.Delta - 0.25)) # if found, trade it if len(delta_contracts) == 0: return self.market_order(ticker, 100) self.market_order(delta_contracts[0].symbol,-1) self.market_on_close_order(ticker, -100) self.market_on_close_order(delta_contracts[0].symbol, 1) def after_market_close(self) -> None: # self.log(f"Fired at: {self.time}") for ticker in self.underlying_tickers: if not self.current_IV_dict[ticker]: continue if len(self.IVR_52W_high_dict[ticker]) > 52*5: self.IVR_52W_high_dict[ticker].pop(0) if len(self.IVR_52W_low_dict[ticker]) > 52*5: self.IVR_52W_low_dict[ticker].pop(0) self.IVR_52W_low_dict[ticker].append(self.current_IV_dict[ticker][0]) self.IVR_52W_high_dict[ticker].append(self.current_IV_dict[ticker][1]) self.max_IV_52W_high[ticker] = max(self.IVR_52W_high_dict[ticker]) self.min_IV_52W_low[ticker] = min(self.IVR_52W_low_dict[ticker]) if ticker == self.underlying_tickers[0]: current_avg_IV = 0.5 * (self.current_IV_dict[ticker][0]+self.current_IV_dict[ticker][1]) avg_IVR = (current_avg_IV-self.min_IV_52W_low[ticker]) / (self.max_IV_52W_high[ticker]-self.min_IV_52W_low[ticker]) * 100 self.plot("IVR", "Avg IVR", avg_IVR) self.current_IV_dict[ticker]=[]