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:
 

from AlgorithmImports import *
from collections import deque
import numpy as np
from datetime import timedelta

class SPYOptionsBot(QCAlgorithm):
    def Initialize(self):
        # Algorithm Settings
        self.SetStartDate(2023, 1, 1)
        self.SetEndDate(2023, 12, 31)
        self.SetCash(100000)
        
        # Add SPY Equity
        self.spy = self.AddEquity("SPY", Resolution.Minute).Symbol
        self.SetBenchmark(self.spy)

        # Add SPY Options
        self.option = self.AddOption("SPY", Resolution.Minute)
        
        # ---- Expiration changed to 1 to 2 weeks ----
        self.min_days_to_expiry = 7   # 1 week out
        self.max_days_to_expiry = 14  # 2 weeks out
        
        self.option.SetFilter(self.FilterOptions)

        # Parameters
        self.lookback_period       = 252   # daily bars for volatility calc
        self.required_bullish_days = 3     # consecutive bullish candles
        self.std_dev_multiplier    = 1.0
        self.max_positions         = 3     # max concurrent positions
        self.position_size         = 0.1   # 10% of portfolio value per trade

        # Track closing prices for bullish pattern detection
        self.previous_closes = deque(maxlen=self.required_bullish_days)

        # Keep track of open positions
        self.open_positions = {}

        # Schedule position management daily at 15:30
        self.Schedule.On(self.DateRules.EveryDay(), 
                         self.TimeRules.At(15, 30), 
                         self.ManagePositions)

    def FilterOptions(self, universe):
        """
        Filter for strikes near the current price, with expiration
        between 1-2 weeks (7 to 14 days).
        """
        return (universe
            .Strikes(-3, 3)
            .Expiration(self.min_days_to_expiry, self.max_days_to_expiry))

    def OnData(self, data):
        # We need SPY bar data to detect bullish pattern
        if self.spy not in data.Bars:
            return

        # Track consecutive bullish closes
        bar = data.Bars[self.spy]
        self.previous_closes.append(bar.Close)

        # Check for bullish pattern and capacity for new trades
        if (len(self.previous_closes) == self.required_bullish_days 
            and all(self.previous_closes[i] > self.previous_closes[i - 1]
                    for i in range(1, self.required_bullish_days))
            and len(self.open_positions) < self.max_positions):
            
            self.Log(f"Detected {self.required_bullish_days} bullish candles. Looking for put options to sell.")

            # Check if we have any option chain data in this slice
            if self.spy in data.OptionChains and len(data.OptionChains[self.spy]) > 0:
                option_chain = data.OptionChains[self.spy]
                self.SellPutOption(option_chain)
            else:
                self.Log("No option chain data is available for SPY at this moment.")

    def SellPutOption(self, option_chain):
        # Current price of the underlying equity
        underlying_price = self.Securities[self.spy].Price

        # Calculate historical volatility (std dev) over specified lookback
        history = self.History(self.spy, self.lookback_period, Resolution.Daily)
        if history.empty:
            self.Log("No historical data available to compute volatility.")
            return

        close_col = 'close' if 'close' in history.columns else 'Close'
        stddev = np.std(history[close_col])
        strike_price = underlying_price - (stddev * self.std_dev_multiplier)

        # Filter for put options that are out-of-the-money (<= strike_price)
        puts = [
            contract for contract in option_chain
            if contract.Right == OptionRight.Put
            and contract.Strike <= strike_price
            and (self.Time + timedelta(days=self.min_days_to_expiry)
                 <= contract.Expiry
                 <= self.Time + timedelta(days=self.max_days_to_expiry))
        ]

        if not puts:
            self.Log(f"No suitable put options found. Target strike: {strike_price}")
            return

        # Sort by "closeness" to strike, then volume (descending), then bid price
        puts.sort(key=lambda x: (
            abs(x.Strike - strike_price),
            -(x.Volume or 0),
            x.BidPrice
        ))

        selected_put = puts[0]
        max_position_value = self.Portfolio.TotalPortfolioValue * self.position_size
        
        # Each contract represents 100 shares
        if selected_put.BidPrice > 0:
            quantity = int(max_position_value // (selected_put.BidPrice * 100))
        else:
            quantity = 0

        if quantity > 0:
            self.Sell(selected_put.Symbol, quantity)
            self.open_positions[selected_put.Symbol] = {
                'entry_price': selected_put.BidPrice,
                'quantity': quantity,
                'entry_time': self.Time,
                'strike': selected_put.Strike
            }
            self.Log(f"Sold {quantity} contracts of {selected_put.Symbol} at {selected_put.BidPrice}")
        else:
            self.Log("Calculated position size is zero; not placing any order.")

    def ManagePositions(self):
        """
        Manage open positions daily at 15:30. Closes positions if:
        1) 50% profit is reached (option price <= 50% of entry)
        2) 100% loss is reached (option price >= 2x entry)
        3) Position is held longer than 15 days
        """
        positions_to_close = []

        for symbol, position in list(self.open_positions.items()):
            if symbol not in self.Securities:
                self.Log(f"Symbol {symbol} not found in Securities.")
                continue

            current_price = self.Securities[symbol].Price
            entry_price   = position['entry_price']
            days_held     = (self.Time - position['entry_time']).days

            # Closing conditions
            if (current_price <= entry_price * 0.5    # 50% profit
                or current_price >= entry_price * 2   # 100% loss
                or days_held >= 15):                  # Time-based exit
                self.Buy(symbol, position['quantity'])
                positions_to_close.append(symbol)
                self.Log(f"Closing position {symbol} after {days_held} days. "
                         f"Entry: {entry_price}, Exit: {current_price}")

        # Remove closed positions from tracking
        for symbol in positions_to_close:
            del self.open_positions[symbol]

    def OnOrderEvent(self, orderEvent):
        if orderEvent.Status == OrderStatus.Filled:
            self.Log(f"Order {orderEvent.OrderId} filled: "
                     f"{orderEvent.Symbol} at {orderEvent.FillPrice}")

    def OnEndOfAlgorithm(self):
        self.Log(f"Algorithm ended. Final portfolio value: "
                 f"${self.Portfolio.TotalPortfolioValue:,.2f}")


Any help would be so appreciated!