Overall Statistics
Total Trades
2
Average Win
15.98%
Average Loss
0%
Compounding Annual Return
-22.886%
Drawdown
0.500%
Expectancy
0
Net Profit
-0.333%
Sharpe Ratio
-6.605
Probabilistic Sharpe Ratio
14.218%
Loss Rate
0%
Win Rate
100%
Profit-Loss Ratio
0
Alpha
0
Beta
0
Annual Standard Deviation
0.029
Annual Variance
0.001
Information Ratio
-6.605
Tracking Error
0.029
Treynor Ratio
0
Total Fees
$2.00
Estimated Strategy Capacity
$14000000.00
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 = 5
      # 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 = []
      
      # Add option
      option = self.AddIndexOption(index.Symbol, self.timeResolution)
      self.optionSymbol = option.Symbol

      # Set the option chain filter function
      option.SetFilter(self.optionChainFilter)
      
      # Set option pricing model for retrieving the Greeks. It appears that the Delta computed by the platform is incurrect!
      #option.PriceModel = OptionPriceModels.CrankNicolsonFD()  # Handles both European & American options
      # The pricing model needs some warm-up time to get the Greeks
      #self.SetWarmUp(TimeSpan.FromDays(7))
      
      option.PriceModel = OptionPriceModels.BlackScholes()  # European Options 

      # 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):
        if security.Type == SecurityType.Equity:
            security.SetDataNormalizationMode(DataNormalizationMode.Raw)
        elif security.Type == SecurityType.Option:
            security.SetMarketPrice(self.GetLastKnownPrice(security))
            
   def optionChainFilter(self, universe):
      return universe.IncludeWeeklys()\
                    .Strikes(-self.nStrikes, self.nStrikes)\
                    .Expiration(0, self.dte)

   
   def getOptionChain(self, slice):
      # Loop through all chains
      for chain in slice.OptionChains:
         # Look for the specified optionSymbol      
         if chain.Key != self.optionSymbol:
            continue  
         # Make sure there are any contracts in this chain   
         if chain.Value.Contracts.Count != 0:
            return chain.Value


   def openPosition(self):
   
      self.Debug("Entering method openPosition")
      
      # Get the option chain
      chain = self.getOptionChain(self.CurrentSlice)

      # Exit if we got no chains
      if chain == None:
         self.Debug(" -> No chains!")
         return
   
      # Log the number of contracts that were found
      self.Debug(" -> Found " + str(chain.Contracts.Count) + " 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(chain, key = lambda x: x.Expiry, reverse = True)[0].Expiry
      
      # Sort all Put contracts (with the given expiry date) by the strike price in reverse order
      puts = sorted([contract for contract in chain 
                        if contract.Expiry == expiry 
                           and contract.Right == OptionRight.Put 
                     ]
                    , key = lambda x: x.Strike
                    , reverse = True
                    )
                    
      # Get the ATM put
      contract = puts[0]
                     
      # Subscribe to the option contract data feed
      if not contract.Symbol in self.optionContractsSubscriptions:
         self.AddOptionContract(contract.Symbol, self.timeResolution)
         self.optionContractsSubscriptions.append(contract.Symbol)
       
      # Sell the Put  
      self.MarketOrder(contract.Symbol, -1, True)

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

   
   def OnData(self, slice):
      pass