Overall Statistics |
Total Trades 141 Average Win 1.42% Average Loss -4.38% Compounding Annual Return 5.418% Drawdown 22.200% Expectancy 0.088 Net Profit 30.228% Sharpe Ratio 0.435 Probabilistic Sharpe Ratio 7.195% Loss Rate 18% Win Rate 82% Profit-Loss Ratio 0.33 Alpha -0.016 Beta 0.508 Annual Standard Deviation 0.097 Annual Variance 0.009 Information Ratio -0.773 Tracking Error 0.095 Treynor Ratio 0.083 Total Fees $380.50 Estimated Strategy Capacity $29000.00 Lowest Capacity Asset SPY 31JKCSCR5IFVQ|SPY R735QTJ8XC9X |
#region imports from AlgorithmImports import * #endregion # Your New Python File def macd(macd, signal): m = macd s = signal if s > 0 and m > s: return 1 elif m >= 0 and m <= s: return 2 elif m < 0 and s >= 0: return 3 elif s < 0 and m <= s: return 4 elif s < m and m <= 0: return 5 elif s <= 0 and m > 0: return 6
#region imports from AlgorithmImports import * #endregion # In Module 5, we address strategy development. # We will transform informative features into actual investment, # by making sense of all the observations and formulating a general theory that explains them. # We will emphasize the economic mechanism that support the theory, such as behavioral bias, # asymmetric information, regulatory constraints. # Finally, we code the full algorithm as the prototype # We will start with a basic option selling strategy # We sell monthly SPY puts using 90% of our capital without leverage, # whenever our portfolio is all cash. We trade at a fixed time during the day. # We request Options Data Using data.OptionChains instead of OptionsChainProvider # It returns a list of options contracts for each date. # There is no indicators because by feature analysis we only know that # the probability of a OTM put to expire worthless is pretty high, > 90%. # If our options get assigned, we sell the stocks next day. # 11/13/22 Update the algo # 2/19/21 Complete the barebone algo # 2/25/21 Adding TA features such as MACD Rating according to WOO system import pandas as pd from labels import macd class BasicOptionTradingAlgo(QCAlgorithm): # In Initialize def Initialize(self): # In sample self.SetStartDate(2016, 1, 1) self.SetEndDate(2021, 1, 1) # Out of sample # self.SetStartDate(2020, 12, 1) # self.SetEndDate(2022, 9, 1) self.SetCash(400000) spy = self.AddEquity("SPY", Resolution.Daily) equity_symbol = self.AddEquity("SPY", dataNormalizationMode=DataNormalizationMode.Raw).Symbol # add tsla options option = self.AddOption(equity_symbol) option.SetFilter(-2, 2, timedelta(0), timedelta(30)) self.symbol = option.Symbol self.SetBenchmark("SPY") self.underlyings = ['SPY' ] # Initialize the dataframe for contracts and positions self.len_underlyings = len(self.underlyings) self.contracts = [str()]*self.len_underlyings # contract id self.shares = [0] * self.len_underlyings # number of shares of underlying assets self.label_macd = [0] * self.len_underlyings # MACD rating labels 1-6 by WOO system self.df = pd.DataFrame(index = self.underlyings, data = list(zip(self.contracts, self.shares, self.label_macd)), \ columns = ['Contract','Number of Shares', 'Label_MACD']) # define our daily macd(12,26) with a 9 day signal 12 is fast peried 26 is slow and 9 is signal self.__macd = self.MACD("SPY", 12, 26, 9, MovingAverageType.Exponential, Resolution.Daily) # self.__previous = datetime.min self.PlotIndicator("MACD", True, self.__macd, self.__macd.Signal) # self.PlotIndicator("SPY", self.__macd.Fast, self.__macd.Slow) # define our historical volatility with a moving window of 30 days. # self.__logr = self.LOGR('SPY', 1, Resolution.Daily) # self.logrWin = RollingWindow[IndicatorDataPoint](30) # self.__logr.Updated += self.LogrUpdated # self.PlotIndicator('LogR', self.__logr) # Initialize the stock data for stock in self.underlyings: self.equity = self.AddEquity(stock, Resolution.Minute) self.equity.SetDataNormalizationMode(DataNormalizationMode.Raw) # initialize the option contract container self.contract = str() self.contractsAdded = set() # schedule the time to trade self.target = "09:46:00" self.trade_time = None # adding position size self.frac = 0.9/self.len_underlyings # The frac of the portfolio for exposure; here we use equal weight. # def LogrUpdated(self, sender, updated): # '''Add updated value to rolling window''' # self.logrWin.Add(updated) #self.Log(updated) def OnData(self, data): # wait for our macd to fully initialize if not self.__macd.IsReady: return # # wait for logr to fully initialize # if not self.LogrWin.IsReady: return # logr_values = [] # for i in self.LogrWin: # logr_values.append(i.Value) # hv = np.std(logr_values)*16 # only once per day #if self.__previous.date() == self.Time.date(): return # define a small tolerance on our checks to avoid bouncing # tolerance = 0.0025 Account_Value = self.Portfolio.TotalPortfolioValue for underlying in self.underlyings: ''' Compute the shares for each underlying. ''' if not (self.Securities[underlying].Price == 0.0): # If the option is exerised, the next day's # stock price data may be zero on the next data slice. number_shares = int( round(Account_Value * self.frac /self.Securities[underlying].Price/100) ) * 100 self.df.at[underlying, 'Number of Shares'] = number_shares self.df.at[underlying, 'Label_MACD'] = macd(self.__macd.Current.Value , self.__macd.Signal.Current.Value) # calculate the label # self.df.at[underlying, 'HV'] = np.std(self.__logr)*np.sqrt(252) #self.Debug('The number of shares of {} may be put to us is {}.'.format(underlying, number_shares)) # sell the stocks when been put to if self.Portfolio[underlying].Invested: self.MarketOrder(underlying, -self.Portfolio[underlying].Quantity) for underlying in self.underlyings: '''Update option contracts and trade them''' self.contract = self.df.loc[underlying, 'Contract'] if not (self.Securities.ContainsKey(self.contract) and self.Portfolio[self.contract].Invested): self.contract = self.OptionsFilter(data) self.df.at[underlying, 'Contract'] = str(self.contract) # self.df.at[underlying, 'ImpVol/HisVol'] = self.contract.ImpliedVolatility # *** #self.Debug(str(self.contract)) if ( self.df.loc[underlying, 'Number of Shares']!= 0 \ # To avoid trading zero contract # and self.contract!={} \ # To avoid trading an empty contract and self.Securities.ContainsKey(str(self.contract)) and not self.Portfolio[str(self.contract)].Invested \ # make sure the time is after the contract is added and self.df.at[underlying, 'Label_MACD'] in [1,2,3,4,5,6] #,3,4,6] # and self.df.at[underlying, 'ImpVol/HisVol'] > 2.0 ): # self.Debug('The contract traded is {}.'.format(contract)) self.trade_time = self.Time if self.trade_time.strftime("%H:%M:%S") == self.target: # Trade not when market just opens since at that moment the price could be zero in the data # https://www.quantconnect.com/forum/discussion/3942/option-examples-from-tutorials-fail/p1/comment-11895 self.OpenPositions(self.contract.Symbol, underlying) def OpenPositions(self, contract, underlying): number_contracts = self.df.loc[underlying, 'Number of Shares']/100 self.MarketOrder(contract, -number_contracts) #self.Debug(contract) ## Example of a filtering function to be called ## The result is a selected option contract def OptionsFilter(self, data): # sort contracts to find at the money (ATM) contract with the farthest expiration if data.OptionChains.Count == 0: return for i in data.OptionChains: if i.Key != self.symbol: continue chain = i.Value call = [x for x in chain if x.Right == 1] # filter the put options contracts # sorted the contracts according to their expiration dates and choose the ATM options contracts = sorted(sorted(call, key = lambda x: abs(chain.Underlying.Price - x.Strike)), key = lambda x: x.Expiry, reverse=False) if len(contracts) == 0: return contract = contracts[0] return contract def OnOrderEvent(self, orderEvent): self.Log(str(orderEvent))