Overall Statistics |
Total Trades 21 Average Win 0.23% Average Loss 0% Compounding Annual Return 10.235% Drawdown 1.400% Expectancy 0 Net Profit 0.662% Sharpe Ratio 1.696 Probabilistic Sharpe Ratio 57.408% Loss Rate 0% Win Rate 100% Profit-Loss Ratio 0 Alpha 0 Beta 0 Annual Standard Deviation 0.036 Annual Variance 0.001 Information Ratio 1.696 Tracking Error 0.036 Treynor Ratio 0 Total Fees $7.04 Estimated Strategy Capacity $0 Lowest Capacity Asset SPXW XV2HMT4IXPU6|SPX 31 |
# 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(2022, 1, 4) self.SetEndDate(2022, 1, 30) self.SetCash(1000000) self.underlying = self.AddIndex("SPX", Resolution.Hour) 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.Hour) self.spxOptions.SetFilter(lambda u: (u.Strikes(0, 20).Expiration(0, 14))) # weekly option SPX contracts spxw = self.AddIndexOption(self.spx, "SPXW", Resolution.Hour) # set our strike/expiry filter for this option chain spxw.SetFilter(lambda u: (u.Strikes(0, 20) # 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) # 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 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