Hello Everyone,

 

I am absolutely struggling to get the options to work on this platform, I seem to find absolutely no tutorials on options and its really annoying to work through.

 

I have my code below:
 

  1. from AlgorithmImports import *
  2. from collections import deque
  3. import numpy as np
  4. from datetime import timedelta
  5. class SPYOptionsBot(QCAlgorithm):
  6. def Initialize(self):
  7. # Algorithm Settings
  8. self.SetStartDate(2023, 1, 1)
  9. self.SetEndDate(2023, 12, 31)
  10. self.SetCash(100000)
  11. # Add SPY Equity
  12. self.spy = self.AddEquity("SPY", Resolution.Minute).Symbol
  13. self.SetBenchmark(self.spy)
  14. # Add SPY Options
  15. self.option = self.AddOption("SPY", Resolution.Minute)
  16. # ---- Expiration changed to 1 to 2 weeks ----
  17. self.min_days_to_expiry = 7 # 1 week out
  18. self.max_days_to_expiry = 14 # 2 weeks out
  19. self.option.SetFilter(self.FilterOptions)
  20. # Parameters
  21. self.lookback_period = 252 # daily bars for volatility calc
  22. self.required_bullish_days = 3 # consecutive bullish candles
  23. self.std_dev_multiplier = 1.0
  24. self.max_positions = 3 # max concurrent positions
  25. self.position_size = 0.1 # 10% of portfolio value per trade
  26. # Track closing prices for bullish pattern detection
  27. self.previous_closes = deque(maxlen=self.required_bullish_days)
  28. # Keep track of open positions
  29. self.open_positions = {}
  30. # Schedule position management daily at 15:30
  31. self.Schedule.On(self.DateRules.EveryDay(),
  32. self.TimeRules.At(15, 30),
  33. self.ManagePositions)
  34. def FilterOptions(self, universe):
  35. """
  36. Filter for strikes near the current price, with expiration
  37. between 1-2 weeks (7 to 14 days).
  38. """
  39. return (universe
  40. .Strikes(-3, 3)
  41. .Expiration(self.min_days_to_expiry, self.max_days_to_expiry))
  42. def OnData(self, data):
  43. # We need SPY bar data to detect bullish pattern
  44. if self.spy not in data.Bars:
  45. return
  46. # Track consecutive bullish closes
  47. bar = data.Bars[self.spy]
  48. self.previous_closes.append(bar.Close)
  49. # Check for bullish pattern and capacity for new trades
  50. if (len(self.previous_closes) == self.required_bullish_days
  51. and all(self.previous_closes[i] > self.previous_closes[i - 1]
  52. for i in range(1, self.required_bullish_days))
  53. and len(self.open_positions) < self.max_positions):
  54. self.Log(f"Detected {self.required_bullish_days} bullish candles. Looking for put options to sell.")
  55. # Check if we have any option chain data in this slice
  56. if self.spy in data.OptionChains and len(data.OptionChains[self.spy]) > 0:
  57. option_chain = data.OptionChains[self.spy]
  58. self.SellPutOption(option_chain)
  59. else:
  60. self.Log("No option chain data is available for SPY at this moment.")
  61. def SellPutOption(self, option_chain):
  62. # Current price of the underlying equity
  63. underlying_price = self.Securities[self.spy].Price
  64. # Calculate historical volatility (std dev) over specified lookback
  65. history = self.History(self.spy, self.lookback_period, Resolution.Daily)
  66. if history.empty:
  67. self.Log("No historical data available to compute volatility.")
  68. return
  69. close_col = 'close' if 'close' in history.columns else 'Close'
  70. stddev = np.std(history[close_col])
  71. strike_price = underlying_price - (stddev * self.std_dev_multiplier)
  72. # Filter for put options that are out-of-the-money (<= strike_price)
  73. puts = [
  74. contract for contract in option_chain
  75. if contract.Right == OptionRight.Put
  76. and contract.Strike <= strike_price
  77. and (self.Time + timedelta(days=self.min_days_to_expiry)
  78. <= contract.Expiry
  79. <= self.Time + timedelta(days=self.max_days_to_expiry))
  80. ]
  81. if not puts:
  82. self.Log(f"No suitable put options found. Target strike: {strike_price}")
  83. return
  84. # Sort by "closeness" to strike, then volume (descending), then bid price
  85. puts.sort(key=lambda x: (
  86. abs(x.Strike - strike_price),
  87. -(x.Volume or 0),
  88. x.BidPrice
  89. ))
  90. selected_put = puts[0]
  91. max_position_value = self.Portfolio.TotalPortfolioValue * self.position_size
  92. # Each contract represents 100 shares
  93. if selected_put.BidPrice > 0:
  94. quantity = int(max_position_value // (selected_put.BidPrice * 100))
  95. else:
  96. quantity = 0
  97. if quantity > 0:
  98. self.Sell(selected_put.Symbol, quantity)
  99. self.open_positions[selected_put.Symbol] = {
  100. 'entry_price': selected_put.BidPrice,
  101. 'quantity': quantity,
  102. 'entry_time': self.Time,
  103. 'strike': selected_put.Strike
  104. }
  105. self.Log(f"Sold {quantity} contracts of {selected_put.Symbol} at {selected_put.BidPrice}")
  106. else:
  107. self.Log("Calculated position size is zero; not placing any order.")
  108. def ManagePositions(self):
  109. """
  110. Manage open positions daily at 15:30. Closes positions if:
  111. 1) 50% profit is reached (option price <= 50% of entry)
  112. 2) 100% loss is reached (option price >= 2x entry)
  113. 3) Position is held longer than 15 days
  114. """
  115. positions_to_close = []
  116. for symbol, position in list(self.open_positions.items()):
  117. if symbol not in self.Securities:
  118. self.Log(f"Symbol {symbol} not found in Securities.")
  119. continue
  120. current_price = self.Securities[symbol].Price
  121. entry_price = position['entry_price']
  122. days_held = (self.Time - position['entry_time']).days
  123. # Closing conditions
  124. if (current_price <= entry_price * 0.5 # 50% profit
  125. or current_price >= entry_price * 2 # 100% loss
  126. or days_held >= 15): # Time-based exit
  127. self.Buy(symbol, position['quantity'])
  128. positions_to_close.append(symbol)
  129. self.Log(f"Closing position {symbol} after {days_held} days. "
  130. f"Entry: {entry_price}, Exit: {current_price}")
  131. # Remove closed positions from tracking
  132. for symbol in positions_to_close:
  133. del self.open_positions[symbol]
  134. def OnOrderEvent(self, orderEvent):
  135. if orderEvent.Status == OrderStatus.Filled:
  136. self.Log(f"Order {orderEvent.OrderId} filled: "
  137. f"{orderEvent.Symbol} at {orderEvent.FillPrice}")
  138. def OnEndOfAlgorithm(self):
  139. self.Log(f"Algorithm ended. Final portfolio value: "
  140. f"${self.Portfolio.TotalPortfolioValue:,.2f}")
+ Expand


Any help would be so appreciated!

 

Author

Domenico Bezuidenhout

December 2024