Overall Statistics
Total Trades
2
Average Win
16.00%
Average Loss
0%
Compounding Annual Return
-22.097%
Drawdown
0.300%
Expectancy
0
Net Profit
-0.320%
Sharpe Ratio
-7.192
Probabilistic Sharpe Ratio
1.216%
Loss Rate
0%
Win Rate
100%
Profit-Loss Ratio
0
Alpha
0
Beta
0
Annual Standard Deviation
0.025
Annual Variance
0.001
Information Ratio
-7.192
Tracking Error
0.025
Treynor Ratio
0
Total Fees
$2.00
Estimated Strategy Capacity
$0
Lowest Capacity Asset
SPX 31KC0UJJBN6EM|SPX 31
class TestIndexOptionAlgorithm(QCAlgorithm):

   def Initialize(self):
      # Backtesting parameters
      self.SetStartDate(2021, 1, 11)
      self.SetEndDate(2021, 1, 15)
      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 = 100
            
      # 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)

      # Setting the benchmark below causes the following error:
      # Runtime Error: Sorry Hour is not a supported resolution for TradeBar and SecurityType.Index. Please change your AddData to use one of the supported resolutions (Minute). in DataManager.cs:line 455     (Open Stacktrace)
      
      #self.SetBenchmark(index.Symbol)

      # -----------------------------------------------------------------------------
      # 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.date() - self.Time.date()).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)

   def OnOrderEvent(self, orderEvent):
   
      self.Debug(orderEvent)

   
   def OnData(self, slice):
      pass