Overall Statistics |
Total Trades 76 Average Win 16.04% Average Loss -12.65% Compounding Annual Return 2.974% Drawdown 4.400% Expectancy 0.105 Net Profit 6.042% Sharpe Ratio 0.555 Probabilistic Sharpe Ratio 22.728% Loss Rate 51% Win Rate 49% Profit-Loss Ratio 1.27 Alpha 0.001 Beta 0.073 Annual Standard Deviation 0.038 Annual Variance 0.001 Information Ratio -1.18 Tracking Error 0.221 Treynor Ratio 0.292 Total Fees $475.50 Estimated Strategy Capacity $4000.00 Lowest Capacity Asset QQQ 31TIRIE21U3XI|QQQ RIWIV7K5Z9LX |
#region imports from AlgorithmImports import * #endregion # Evaluate 10Delta Put Spread Selling Strategy # Using the following rules from OptionsAlpha.com # # Becktest Duration: years # Trade Frequency: Weekly # Capital: $100,000, 30% of capital per position # Strategy: # Entry Conditions # Long Delta: 0.05 # Short Delta: 0.10 # Days to Expiration: 30 # Minimum IV Rank: 0 # Maximum IV Rank: 100 # Avoid Earnings: Yes # Exit Conditions # Profit Taking: None # Stop Loss At: None # Days to Expiration: Expire # Reference: # QC BullPutSpread Implimentation # https://github.com/QuantConnect/Lean/blob/a8c81cad2a7807c8c868fcf578a6aa9c45769ec4/Common/Securities/Option/OptionStrategies.cs#L200 # QC Python Algo for Options Strategy # https://github.com/QuantConnect/Lean/blob/master/Algorithm.Python/BasicTemplateOptionStrategyAlgorithm.py # AlphaVantage Earnings Dates # https://www.alphavantage.co/query?function=EARNINGS&symbol=IBM&apikey=demo # Zhen Liu - Evolving decision making with humanity from QuantConnect import * from QuantConnect.Algorithm import * from QuantConnect.Indicators import * from QuantConnect.Securities.Option import OptionPriceModels from datetime import timedelta import math import numpy as np class BullPutSpreadAlgorithm(QCAlgorithm): def Initialize(self): if self.LiveMode: self.Debug("Trading Live!") self.SetStartDate(2020, 1, 1) self.SetEndDate(2022, 1, 1) self.SetWarmUp(30, Resolution.Daily) self.SetCash(100000) UnderlyingSymbol = "QQQ" equity = self.AddEquity(UnderlyingSymbol, dataNormalizationMode=DataNormalizationMode.Raw) self.symbol = equity.Symbol option = self.AddOption(self.symbol, Resolution.Minute) equity.VolatilityModel = StandardDeviationOfReturnsVolatilityModel(30) # set our strike/expiry filter for this option chain option.SetFilter(lambda u: (u.Strikes(-200, +2) # Expiration method accepts TimeSpan objects or integer for days. # The following statements yield the same filtering criteria .Expiration(5, 45))) #.Expiration(TimeSpan.Zero, TimeSpan.FromDays(180)))) #option = self.AddOption(symbol, Resolution.Minute) option.PriceModel = OptionPriceModels.CrankNicolsonFD() self.symbol = option.Symbol self.underlying = UnderlyingSymbol self.Num_Contracts = 0 self.minimum_premium = 0.10 self.position_size = 0.05 # set strike/expiry filter for this option chain, weekly #option.SetFilter(self.UniverseFunc) # use the underlying equity as the benchmark self.SetBenchmark(equity.Symbol) # self.Schedule.On(self.DateRules.EveryDay(symbol), self.TimeRules.BeforeMarketClose(symbol, 40), \ # self.PositionManagement) # time rule here tells it to fire 40 minutes before IWM's market close # Earnings Dates # self.EarningsDates = self.Get_EarningsDates() # self.next_earnings_date = datetime(2017, 1, 1) # def FineSelectionFunction(self, fine): # fine = [x for x in fine if self.Time < x.EarningReports.FileDate + timedelta(days=30) # and x.EarningReports.FileDate != datetime.time()] # return [i.Symbol for i in fine[:self.__numberOfSymbolsFine]] def UniverseFunc(self, universe): # include weekly contracts return universe.IncludeWeeklys().Expiration(TimeSpan.FromDays(5), TimeSpan.FromDays(45)).Strikes(-200,2) def OnData(self,slice): if (self.Time.hour,self.Time.minute) != (10, 30): return # if self.Time < self.next_earnings_date: # self.Debug('ED is ' + str(self.next_earnings_date)) # #return # else: # # self.Debug('ED is ' + str(self.next_earnings_date)) # self.next_earnings_date = self.NextEarningsDate() #self.next_earnings_date) # self.Debug('New ED is ' + str(self.next_earnings_date)) # self.Debug('Today is ' + str(self.Time)) # Set the stock symbol symbol = self.underlying #"SPY" #self.Log(symbol) # self.next_earnings_date = self.NextEarningsDate() #self.EarningsDates) # Num_Contracts=0 # Set the position size if slice.ContainsKey(symbol): # Trade stocks if invested. if self.Portfolio[symbol].Invested: self.Log('Current position has " + str(symbol) + " shares.') #Num_Contracts = math.floor(self.Portfolio.TotalPortfolioValue/(100*slice[symbol].Price)) #Num_Shares = 100 * Num_Contracts self.Liquidate(symbol) #self.MarketOrder(symbol, Num_Shares) # Buy shares of underlying stocks option_invested = [x.Key for x in self.Portfolio if x.Value.Invested and x.Value.Type==SecurityType.Option] #option_holding = [x for x in self.Portfolio if x.Value.Invested and x.Value.Type==SecurityType.Option] if len(option_invested) == 1: self.Liquidate() #self.symbol) if len(option_invested) == 0: Capital_at_risk = self.position_size*self.Portfolio.TotalPortfolioValue #Num_Contracts = self.Portfolio[symbol].Quantity/100 #if Num_Contracts > 0: self.TradeOptions(slice, Capital_at_risk) #, self.next_earnings_date ) def NextEarningsDate(self): # Check earnings dates nearest = datetime.strptime('01/01/3000', "%m/%d/%Y") dates = pd.to_datetime(self.EarningsDates) #[str(self.equity.Symbol)] #self.Debug(dates) #self.EarningsDates.head()) if len(dates) == 0: #self.EarningsDates[self.equity.Symbol]) == 0: self.Debug('The ticker\'s earnings dates data are not available') return nearest else: nearest = dates[dates > self.Time].iloc[-1] #for i in dates: # date = pd.to_datetime(i) #datetime.strptime(str(i), "%m/%d/%Y") # if date > self.Time: # nearest = date return nearest def TradeOptions(self, slice, capital): #, next_earnings_date): # self.Debug(next_earnings_date) contracts_selected = [] for i in slice.OptionChains: if i.Key != self.symbol: continue chain = i.Value # filter the call options contracts put = [x for x in chain if x.Right == OptionRight.Put] # sorted the contracts according to their expiration dates and choose the ATM options contracts = sorted(sorted(put, key = lambda x: abs(chain.Underlying.Price - x.Strike)), key = lambda x: x.Expiry, reverse=False) if len(contracts) == 0: return #continue #self.legs = [contracts[1], contracts[2]] for i in contracts: if (i.Greeks.Delta <= -0.01) and (i.Greeks.Delta >= -0.11): contracts_selected.append(i) if len(contracts_selected) <= 1: self.Debug("No contracts available.") continue contracts = contracts_selected contract1 = contracts[0] contract2 = contracts[1] strike1 = contract1.Strike strike2 = contract2.Strike premium1 = contract1.BidPrice premium2 = contract2.AskPrice delta1 = contract1.Greeks.Delta delta2 = contract2.Greeks.Delta self.Log(delta1) # self.Log("Delta: " + str([i.Greeks.Delta for i in contracts])) if premium1-premium2 <= self.minimum_premium: return # check the minimum premium $0.10 exp = pd.to_datetime(contracts[0].Expiry) self.Debug('Exp is ' + str(exp)) # self.Debug(type(next_earnings_date)) # self.Debug(type(exp)) # if exp >= next_earnings_date: # # self.Debug("Earnings date is before the expriation.") # # self.Debug('Today is ' + str(self.Time)) # continue #return # does a better job in execution speed than continue or return unit_spread_risk = abs(strike1 - strike2)*100 Num_Contracts = math.floor(capital/unit_spread_risk) # short the put spreads self.Buy(OptionStrategies.BullPutSpread(self.symbol, strike1, strike2, exp ), Num_Contracts) def OnOrderEvent(self, orderEvent): self.Log(str(orderEvent)) def Get_EarningsDates(self): # import custom data # Note: dl must be 1, or it will not download automatically. # Tutorial: Ensure you're downloading the direct file link - not the HTML page of # the download. You can specify this by adding &dl=1 to the end of the Dropbox # download URL. url = "https://www.dropbox.com/s/feaj2dcyrv9sozk/AMZN.csv?dl=1" data = self.Download(url).split('\r\n') df = pd.Series(data[1:]) #, header = 0) return df