Skip to content
All library documents

SPX Weekly Call Butterfly with Near-Expiry Strike Selection

Article Strategy library · Author: QuantConnect

Summary

The algorithm constructs a call butterfly from SPX weekly index options. It queries contracts within a selected expiration range and near the underlying index level, chooses the nearest expiry, and looks for three call strikes arranged symmetrically around an at-the-money strike. If matching strikes are available, it submits a butterfly order sized according to portfolio value and the estimated cost of its legs.

The example also holds shares of a volatility-linked ETF as a separate exposure, with a comment describing its order of magnitude relative to an option order. The code specifies a historical simulation period and starting capital, but the document provides no results, rationale for the ETF holding, or risk analysis. It does not explain how the butterfly’s payoff profile, transaction costs, liquidity, or assignment and settlement considerations affect outcomes. The example therefore demonstrates contract selection and order construction, not evidence of a validated trading edge.

Key ideas

  • The algorithm selects the nearest available expiry from a filtered set of weekly SPX options.
  • It searches for three call strikes positioned symmetrically around an at-the-money strike.
  • The butterfly order quantity is based on portfolio value divided by an estimated leg cost.
  • The example includes a separate volatility-linked ETF holding but gives no performance or risk results.

Tags

Full text
# IndexOptionCallButterflyAlgorithm


# IndexOptionCallButterflyAlgorithm









## Source (Apache-2.0)

```python
# 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.

#region imports
from AlgorithmImports import *
#endregion

class IndexOptionCallButterflyAlgorithm(QCAlgorithm):

    def initialize(self):
        self.set_start_date(2020, 1, 1)
        self.set_end_date(2021, 1, 1)
        self.set_cash(1000000)

        self.vxz = self.add_equity("VXZ", Resolution.MINUTE).symbol

        index = self.add_index("SPX", Resolution.MINUTE).symbol
        option = self.add_index_option(index, "SPXW", Resolution.MINUTE)
        option.set_filter(lambda x: x.include_weeklys().strikes(-3, 3).expiration(15, 45))

        self.spxw = option.symbol
        self.multiplier = option.symbol_properties.contract_multiplier
        self.tickets = []

    def on_data(self, slice: Slice) -> None:
        # The order of magnitude per SPXW order's value is 10000 times of VXZ
        if not self.portfolio[self.vxz].invested:
            self.market_order(self.vxz, 10000)
        
        # Return if any opening index option position
        if any([self.portfolio[x.symbol].invested for x in self.tickets]): return

        # Get the OptionChain
        chain = slice.option_chains.get(self.spxw)
        if not chain: return

        # Get nearest expiry date
        expiry = min([x.expiry for x in chain])
        
        # Select the call Option contracts with nearest expiry and sort by strike price
        calls = [x for x in chain if x.expiry == expiry and x.right == OptionRight.CALL]
        if len(calls) < 3: return
        sorted_call_strikes = sorted([x.strike for x in calls])

        # Select ATM call
        atm_strike = min([abs(x - chain.underlying.value) for x in sorted_call_strikes])

        # Get the strike prices for the ITM & OTM contracts, make sure they're in equidistance
        spread = min(atm_strike - sorted_call_strikes[0], sorted_call_strikes[-1] - atm_strike)
        itm_strike = atm_strike - spread
        otm_strike = atm_strike + spread
        if otm_strike not in sorted_call_strikes or itm_strike not in sorted_call_strikes: return
        
        # Buy the call butterfly
        call_butterfly = OptionStrategies.call_butterfly(self.spxw, otm_strike, atm_strike, itm_strike, expiry)
        price = sum([abs(self.securities[x.symbol].price * x.quantity) * self.multiplier for x in call_butterfly.underlying_legs])
        if price > 0:
            quantity = int(self.portfolio.total_portfolio_value // price)
            self.tickets = self.buy(call_butterfly, quantity, asynchronous=True)

```

Shown in full with attribution under the source's licence. Licence: Apache-2.0

This summary was written by Stratmill's research agent from the original; it is not a copy of the source.