Overall Statistics
Total Trades
0
Average Win
0%
Average Loss
0%
Compounding Annual Return
0%
Drawdown
0%
Expectancy
0
Net Profit
0%
Sharpe 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
$0
Lowest Capacity Asset
# QUANTCONNECT.COM - Democratizing Finance, Empowering Individuals.
# Lean Algorithmic Trading Engine v2.0. Copyright 2014 QuantConnect Corporation.
#
# Licensed under the Apache License, Version 2.0 (the "License");
# you may not use this file except in compliance with the License.
# You may obtain a copy of the License at http://www.apache.org/licenses/LICENSE-2.0
#
# Unless required by applicable law or agreed to in writing, software
# distributed under the License is distributed on an "AS IS" BASIS,
# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
# See the License for the specific language governing permissions and
# limitations under the License.

from AlgorithmImports import *

### <summary>
### This example demonstrates how to add and trade SPX index weekly options
### </summary>
### <meta name="tag" content="using data" />
### <meta name="tag" content="options" />
### <meta name="tag" content="indexes" />
class BasicTemplateSPXWeeklyIndexOptionsAlgorithm(QCAlgorithm):
   def Initialize(self):
      # self.SetStartDate(2020, 1, 17)  #Set Start Date
      # self.SetEndDate(2020, 1, 18)  #Set End Date
      # self.SetCash(50000)  #Set Strategy Cash
      # equity = self.AddEquity("GOOG", Resolution.Minute) # Add the underlying stock: Google
      # option = self.AddOption("GOOG", Resolution.Minute) # Add the option corresponding to underlying stock
      # self.symbol = option.Symbol
      # option.SetFilter(-10, 10, timedelta(0), timedelta(days = 1))
        
      self.SetStartDate(2022, 9, 1)
      self.SetEndDate(2022, 9, 30)
      self.SetCash(1000000)
      
      self.underlying = self.AddIndex("SPX", Resolution.Minute)
      self.underlying.SetDataNormalizationMode(DataNormalizationMode.Raw)
      self.SetBrokerageModel(BrokerageName.InteractiveBrokersBrokerage, AccountType.Margin)

      self.spx = self.underlying.Symbol

      # regular option SPX contracts
      self.spxOptions = self.AddIndexOption(self.spx, Resolution.Minute)
      self.spxOptions.SetFilter(lambda u: (u.Strikes(-1, -1).Expiration(0, 0)))

      # weekly option SPX contracts
      spxw = self.AddIndexOption(self.spx, "SPXW", Resolution.Minute)
      # set our strike/expiry filter for this option chain
      spxw.SetFilter(lambda u: (u.Strikes(-1, 1)
                                    # single week ahead since there are many SPXW contracts and we want to preserve performance
                                    .Expiration(0, 1)
                                    .IncludeWeeklys()))

      self.spxw_option = spxw.Symbol

      self.SetSecurityInitializer(self.securityInitializer)
      self.currentDate = None

    # Called every time a security (Option or Equity/Index) is initialized
   def securityInitializer(self, security):
      security.SetDataNormalizationMode(DataNormalizationMode.Raw)
      security.SetMarketPrice(self.GetLastKnownPrice(security))
      if security.Type in [SecurityType.Option, SecurityType.IndexOption]:
         security.SetFillModel(BetaFillModel(self))
         #security.SetFillModel(MidPriceFillModel(self))
         security.SetFeeModel(TastyWorksFeeModel())

   def OnData(self,slice):
      # if it's the same day then stop.
      if self.Time.date().strftime("%Y-%m-%d") == self.currentDate:
         return

      for i in slice.OptionChains:
         if i.Key != self.spxw_option: 
            self.Log(f"\n({self.Time.strftime('%A')}) The `{i.Key}` does not match SPXW so we skip.")
            continue
      
         optionchain = i.Value
         df = pd.DataFrame([[x.Expiry] for x in optionchain],
                     index=[x.Symbol.Value for x in optionchain],
                     columns=['expiry'])
                     
         if df.shape[0] > 0:
            self.Log(f"\n{df.to_string()}")
            # we found data so we consider this day complete
            self.currentDate = self.Time.date().strftime("%Y-%m-%d")
         else:
            self.Log(f"\n({self.Time.strftime('%A')}) No options on this date")
            # self.Quit()


      # if self.Portfolio.Invested: return

      # chain = slice.OptionChains.GetValue(self.spxw_option)
      # if chain is None:
      #    return

      # # we sort the contracts to find at the money (ATM) contract with closest expiration
      # contracts = sorted(sorted(sorted(chain, \
      #    key = lambda x: x.Expiry), \
      #    key = lambda x: abs((chain.Underlying.Price + 20) - x.Strike)), \
      #    key = lambda x: x.Right, reverse=True)
      
      # # select just calls
      # contracts = [x for x in contracts if x.Right == OptionRight.Call]

      # # if found, buy until it expires
      # if len(contracts) == 0: return
      # symbol = contracts[0].Symbol
      # self.MarketOrder(symbol, -1)

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

class TastyWorksFeeModel:
   def GetOrderFee(self, parameters):
      optionFee = min(10, parameters.Order.AbsoluteQuantity * 0.5)
      transactionFee = parameters.Order.AbsoluteQuantity * 0.14
      return OrderFee(CashAmount(optionFee + transactionFee, 'USD'))

# Custom Fill model based on Beta distribution:
#  - Orders are filled based on a Beta distribution  skewed towards the mid-price with Sigma = bidAskSpread/6 (-> 99% fills within the bid-ask spread)
class BetaFillModel(ImmediateFillModel):

   # Initialize Random Number generator with a fixed seed (for replicability)
   random = np.random.RandomState(1234)
   
   def __init__(self, context):
      self.context = context
      
   def MarketFill(self, asset, order):
      # Start the timer
      # self.context.executionTimer.start()
   
      # Get the random number generator
      random = BetaFillModel.random
      # Compute the Bid-Ask spread
      bidAskSpread = abs(asset.AskPrice - asset.BidPrice)
      # Compute the Mid-Price
      midPrice = 0.5*(asset.AskPrice + asset.BidPrice)
      # Call the parent method
      fill = super().MarketFill(asset, order)
      # Setting the parameters of the Beta distribution:
      # - The shape parameters (alpha and beta) are chosen such that the fill is "reasonably close" to the mid-price about 96% of the times
      # - How close -> The fill price is within 15% of half the bid-Ask spread
      if order.Direction == OrderDirection.Sell:
         # Beta distribution in the range [Bid-Price, Mid-Price], skewed towards the Mid-Price
         # - Fill price is within the range [Mid-Price - 0.15*bidAskSpread/2, Mid-Price] with about 96% probability
         offset = asset.BidPrice
         alpha = 20
         beta = 1
      else:
         # Beta distribution in the range [Mid-Price, Ask-Price], skewed towards the Mid-Price
         # - Fill price is within the range [Mid-Price, Mid-Price + 0.15*bidAskSpread/2] with about 96% probability
         offset = midPrice
         alpha = 1
         beta = 20
      # Range (width) of the Beta distribution
      range = bidAskSpread/2.0
      # Compute the new fillPrice (centered around the midPrice)
      fillPrice = round(offset + range * random.beta(alpha, beta), 2)
      # Update the FillPrice attribute
      fill.FillPrice = fillPrice
      # Stop the timer
      # self.context.executionTimer.stop()
      # Return the fill
      return fill