Buying a Rounded-Strike SPY Call and Holding It to Expiration
Summary
This example strategy buys a call option on SPY during its first trading iteration and then makes no further purchases. It reads the latest daily close of the underlying, rounds that price to the nearest whole number to set the strike, and submits an order to open ten contracts with a configured expiration date. The example is designed to hold the option through expiration and includes setup paths for live trading and historical simulation.
The code demonstrates basic option-asset construction and order submission, along with a check for missing or invalid price data. It does not describe an exit rule, position sizing rationale, or risk controls, and it reports no backtest results. The sample expiration and simulation dates are fixed, so they are illustrative rather than a general method for selecting maturities. Option pricing, contract multiplier effects, and the payoff at expiration are not analyzed.
Key ideas
- The strategy opens a SPY call once, using the latest daily close to choose a rounded strike.
- It submits an order for ten contracts and does not define an early exit.
- The example includes both live broker setup and a historical simulation path.
- It gives no performance evidence or risk-management framework.
Tags
Full text
# options_hold_to_expiry.py
```py
from datetime import datetime
from lumibot.entities import Asset
from lumibot.strategies.strategy import Strategy
"""
Strategy Description
An example strategy for buying an option and holding it to expiry.
"""
class OptionsHoldToExpiry(Strategy):
parameters = {
"buy_symbol": "SPY",
"expiry": datetime(2023, 10, 20),
}
# =====Overloading lifecycle methods=============
def initialize(self):
# Set the initial variables or constants
# Built in Variables
self.sleeptime = "1D"
def on_trading_iteration(self):
"""Buys the self.buy_symbol once, then never again"""
buy_symbol = self.parameters["buy_symbol"]
expiry = self.parameters["expiry"]
# What to do each iteration
underlying_asset = Asset(buy_symbol)
bars = self.get_historical_prices(underlying_asset, 1, "day")
underlying_price = None
if bars is not None and getattr(bars, "df", None) is not None and not bars.df.empty:
close_value = bars.df["close"].iloc[-1]
if close_value is not None:
try:
underlying_price = float(close_value)
if underlying_price != underlying_price: # NaN guard
underlying_price = None
except Exception:
underlying_price = None
self.log_message(f"The value of {buy_symbol} is {underlying_price}")
if underlying_price is None:
return
if self.first_iteration:
# Calculate the strike price (round to nearest 1)
strike = round(underlying_price)
# Create options asset
asset = Asset(
symbol=buy_symbol,
asset_type="option",
expiration=expiry,
strike=strike,
right="call",
)
# Create order
order = self.create_order(
asset,
10,
"buy_to_open",
)
# Submit order
self.submit_order(order)
# Log a message
self.log_message(f"Bought {order.quantity} of {asset}")
if __name__ == "__main__":
is_live = False
if is_live:
from credentials import INTERACTIVE_BROKERS_CONFIG
from lumibot.brokers import InteractiveBrokers
broker = InteractiveBrokers(INTERACTIVE_BROKERS_CONFIG)
strategy = OptionsHoldToExpiry(broker=broker)
strategy.run_live()
else:
from lumibot.backtesting import PolygonDataBacktesting
# Backtest this strategy
backtesting_start = datetime(2023, 10, 19)
backtesting_end = datetime(2023, 10, 24)
results = OptionsHoldToExpiry.backtest(
PolygonDataBacktesting,
backtesting_start,
backtesting_end,
benchmark_asset="SPY",
polygon_api_key="YOUR_POLYGON_API_KEY_HERE", # Add your polygon API key here
)
```Shown in full with attribution under the source's licence. Licence: GPL-3.0
This summary was written by Stratmill's research agent from the original; it is not a copy of the source.