Overall Statistics |
Total Trades 1 Average Win 0% Average Loss 0% Compounding Annual Return 73.073% Drawdown 0.000% Expectancy 0 Net Profit 0.706% Sharpe Ratio 9.985 Probabilistic Sharpe Ratio 95.977% Loss Rate 0% Win Rate 0% Profit-Loss Ratio 0 Alpha 0 Beta 0 Annual Standard Deviation 0.056 Annual Variance 0.003 Information Ratio 9.985 Tracking Error 0.056 Treynor Ratio 0 Total Fees $1.00 Estimated Strategy Capacity $85000.00 Lowest Capacity Asset SPX 31RSTWMM8TW8E|SPX 31 |
#region imports from AlgorithmImports import * #endregion class TestIndexOptionAlgorithm(QCAlgorithm): def Initialize(self): # Backtesting parameters self.SetStartDate(2021, 10, 11) self.SetEndDate(2021, 10, 16) self.SetCash(1000000) # Index Ticker Symbol self.ticker = "SPX" # Time Resolution self.timeResolution = Resolution.Minute # Resolution.Minute .Hour .Daily # Set brokerage model and margin account self.SetBrokerageModel(BrokerageName.InteractiveBrokersBrokerage, AccountType.Margin) # Days to Expiration self.dte = 0 # Number of strikes to retrieve from the option chain universe (nStrikes on each side of ATM) self.nStrikes = 10 # Add the underlying Index index = self.AddIndex(self.ticker, self.timeResolution) index.SetDataNormalizationMode(DataNormalizationMode.Raw) self.underlyingSymbol = index.Symbol # Keep track of the option contract subscriptions self.optionContractsSubscriptions = [] # Set Security Initializer (This does not seem to solve the issue with the benchmark below) self.SetSecurityInitializer(self.security_initializer) # ----------------------------------------------------------------------------- # Scheduled function: every day, 25 minutes after the market open # ----------------------------------------------------------------------------- self.Schedule.On(self.DateRules.EveryDay(self.underlyingSymbol) , self.TimeRules.AfterMarketOpen(self.underlyingSymbol, 25) , Action(self.openPosition) ) def security_initializer(self, security): # * SecurityType.Index & SecurityType.IndexOption security.SetDataNormalizationMode(DataNormalizationMode.Raw) security.SetMarketPrice(self.GetLastKnownPrice(security)) def InitialFilter(self, underlyingsymbol, symbol_list, min_strike_rank, max_strike_rank, min_expiry, max_expiry): ''' This method is an initial filter of option contracts based on the range of strike price and the expiration date https://www.quantconnect.com/tutorials/applied-options/iron-condor''' if len(symbol_list) == 0 : return None # fitler the contracts based on the expiry range contract_list = [i for i in symbol_list if min_expiry <= (i.ID.Date - self.Time).days <= max_expiry] if not contract_list: return None # find the strike price of ATM option atm_strike = sorted(contract_list, key = lambda x: abs(x.ID.StrikePrice - self.Securities[underlyingsymbol].Price))[0].ID.StrikePrice strike_list = sorted(set([i.ID.StrikePrice for i in contract_list])) # find the index of ATM strike in the sorted strike list atm_strike_rank = strike_list.index(atm_strike) try: min_strike = strike_list[atm_strike_rank + min_strike_rank + 1] max_strike = strike_list[atm_strike_rank + max_strike_rank - 1] except: min_strike = strike_list[0] max_strike = strike_list[-1] # skip weekly options filtered_contracts = [i for i in contract_list if i.ID.StrikePrice >= min_strike \ and i.ID.StrikePrice <= max_strike] return filtered_contracts def openPosition(self): self.Debug("Entering method openPosition") # Get the option contracts contracts = self.OptionChainProvider.GetOptionContractList(self.underlyingSymbol, self.Time) contracts = self.InitialFilter(self.underlyingSymbol, contracts, -self.nStrikes, self.nStrikes, 0, self.dte) # Exit if we got no chains if not contracts: self.Debug(" -> No chains!") return # Log the number of contracts that were found self.Debug(" -> Found " + str(len(contracts)) + " contracts in the option chain!") # Do not open any new positions if we have already one open if self.Portfolio.Invested: return # Get the furthest expiry date expiry = sorted(contracts, key = lambda x: x.ID.Date, reverse = True)[0].ID.Date # Sort all Put contracts (with the given expiry date) by the strike price in reverse order puts = sorted([contract for contract in contracts if contract.ID.Date == expiry and contract.ID.OptionRight == OptionRight.Put ] , key = lambda x: x.ID.StrikePrice , reverse = True ) # Get the ATM put contract = puts[0] # Subscribe to the option contract data feed if not contract in self.optionContractsSubscriptions: self.AddOptionContract(contract, self.timeResolution) self.optionContractsSubscriptions.append(contract) # Sell the Put self.MarketOrder(contract, -1, True) # Keep track of the sold contract self.put = contract def OnOrderEvent(self, orderEvent): self.Debug(orderEvent) def OnData(self, slice): if self.Portfolio.Invested: # Get the security security = self.Securities[self.put] # Get the price (Looks like self.Securities[x].Price is calculated as the mid-price from self.GetLastKnownPrice(x)) price = security.Price lastKnown = self.GetLastKnownPrice(security) # Find out at which time the price was last retreived lastPriceDttm = lastKnown.Time lastKnownPrice = 0.5*(lastKnown.Bid.Close + lastKnown.Ask.Close) # Print the Price every 15 minutes (avoid clogging the log) if self.Time.minute % 15 == 0: self.Log(f"Contract: {self.put} Price: {price} ({lastKnownPrice}) @ {lastPriceDttm}")