Overall Statistics |
Total Orders 4 Average Win 0% Average Loss 0% Compounding Annual Return 0% Drawdown 0% Expectancy 0 Start Equity 100000 End Equity 121745 Net Profit 0% Sharpe Ratio 0 Sortino Ratio 0 Probabilistic Sharpe Ratio 0% Loss Rate 0% Win Rate 0% Profit-Loss Ratio 0 Alpha 0 Beta 0 Annual Standard Deviation 0 Annual Variance 0 Information Ratio 0 Tracking Error 0 Treynor Ratio 0 Total Fees $0.00 Estimated Strategy Capacity $1700000.00 Lowest Capacity Asset SPXW 3281PG4IW5MR2|SPX 31 Portfolio Turnover 14.88% |
# region imports from AlgorithmImports import * # endregion class SPXExample(QCAlgorithm): def Initialize(self): self.SetStartDate(2023, 6, 1) self.SetEndDate(2023, 6, 1) self.SetCash(100000) self.AmountToSpendOnOptions = 5000 self.EnableZeroDayOptions = True self.LogOptions = True # SPX weekly options, including 0dte self.spx = self.add_index("SPX").Symbol self.SPXoption = self.AddIndexOption(self.spx, "SPXW") self.SPXoption.SetFilter(self.OptionFilter) self.NeedToBuy = True self.CallOptionType = 1 self.PutOptionType = 2 def OnData(self, data: Slice): # Buy once. Buy a call and a put. if self.NeedToBuy: self.NeedToBuy = False # Buy call by Delta, closest to 0.70 self.BuyOptionByDelta(self.CallOptionType, self.SPXoption, 0.70) # Buy put by Delta, closest to 0.50 self.BuyOptionByDelta(self.PutOptionType, self.SPXoption, 0.50) # Buy call by Price, closest to 2.00 self.BuyOptionByPrice(self.CallOptionType, self.SPXoption, 2) # Buy put by Price, closest to 2.00 self.BuyOptionByPrice(self.PutOptionType, self.SPXoption, 2) def OptionFilter(self, universe): # Note that we're limiting expirations to within 0-7 days here. If you need later this should be adjusted. return universe.IncludeWeeklys().Strikes(-10, 10).Expiration(0, 7) def BuyOptionByPrice(self, OptionType, underlyingOptions, price): self.BuyOption(OptionType, underlyingOptions, price, False) def BuyOptionByDelta(self, OptionType, underlyingOptions, delta): self.BuyOption(OptionType, underlyingOptions, delta, True) def BuyOption(self, OptionType, underlyingOptions, deltaprice, BuyOptionsByDelta = True): chain = self.CurrentSlice.OptionChains.get(underlyingOptions.Symbol) if chain is None: self.Log("Chain error!") return # First grab expiry dates expiry_dates = [contract.Expiry.date() for contract in chain] expiry_dates = sorted(set(expiry_dates)) # If you explicitly don't want an option that expires today you can pick another date. # Here we just grab the next expiry if 0dte is allowed, otherwise we filter for dates after today. next_expiry = None if self.EnableZeroDayOptions: next_expiry = expiry_dates[0] else: expiries = [date for date in expiry_dates if date > self.Time.date()] if not expiries: return next_expiry = expiries[0] # Filter contracts for the next expiry date and are call options all_options = None desired_delta = deltaprice if OptionType == self.CallOptionType: # Call all_options = [contract for contract in chain if contract.Expiry.date() == next_expiry and contract.Right == OptionRight.Call] else: # Put all_options = [contract for contract in chain if contract.Expiry.date() == next_expiry and contract.Right == OptionRight.Put] # reverse delta for puts if desired_delta > 0: desired_delta = -desired_delta if not all_options: return # Select the option to BUY with delta closest to the desired delta # Result is put into self.selected_option if BuyOptionsByDelta: self.SelectOptionByDelta(all_options, desired_delta) else: self.SelectOptionByPrice(all_options, deltaprice) # Find the last prices for the options. option_price = None if self.selected_option.Symbol in self.CurrentSlice.Bars: option_contract_data = self.CurrentSlice.Bars[self.selected_option.Symbol] option_price = option_contract_data.Close # Now select a number of contracts based on how much we want to invest numcontracts = math.floor(self.AmountToSpendOnOptions/(option_price*100)) numcontracts = max(1,numcontracts) numcontracts = min(300,numcontracts) self.MarketOrder(self.selected_option.Symbol, numcontracts) # Log option details. if self.LogOptions: self.LogOption(self.CallOptionType, option_price) def SelectOptionByDelta(self, all_options, desired_delta): closest_option = None closest_delta_diff = float('inf') for option in all_options: delta_diff = abs(option.Greeks.Delta - desired_delta) #self.Log(f"Delta: {option.Greeks.Delta}, diff: {delta_diff}") if delta_diff < closest_delta_diff: closest_delta_diff = delta_diff closest_option = option #self.Log("selected") self.selected_option = closest_option def SelectOptionByPrice(self, all_options, desired_price): closest_option = None closest_price_diff = float('inf') for option in all_options: mid_price = (option.BidPrice + option.AskPrice) / 2 price_diff = abs(mid_price - desired_price) if price_diff < closest_price_diff: closest_price_diff = price_diff closest_option = option elif price_diff == closest_price_diff: # If price difference is the same, select the lowest strike call or highest strike put if (closest_option.Right == OptionRight.Call and option.Strike < closest_option.Strike): closest_option = option elif (closest_option.Right == OptionRight.Put and option.Strike > closest_option.Strike): closest_option = option self.selected_option = closest_option def LogOption(self, optiontype, lastoptionprice): # The last option selected should be in self.selected_option so we'll log details of that. # If we wanted to find the actual price we bought at, we probably would need to put that in an OnOrderEvent function. opt = "CALL" if optiontype == self.PutOptionType: opt = "PUT" strike_price = self.selected_option.Strike delta = self.selected_option.Greeks.Delta expiry_date = self.selected_option.Expiry self.Log(f"{opt} Strike: {strike_price}, Delta: {delta}, Expiry: {expiry_date}, Last Price: {lastoptionprice}")