In reference to my earlier post: “Challenges Accessing SPX Options,” after much tinkering, I've gotten options information. 

Apologies about starting a new post, but doing a “reply” on that discussion would have made both the text and code attached very messy and nearly impossible to read. 

There were real hurdles to printing a complete list. So you may want to copy this code straight away! 

For the arbitrary date of June 9, 2023, with a strike range of 100 from ATM, and at 10 am US Eastern time (minute basis used): This code will print a separate table for each expiration date. Each table will include relevant option information (i.e., Contract, Strike, OptionRight, Expiration, Last Price, Volume, Open Interest, Greeks). 

There are 3 new challenges involving the data: 

(1) The first challenge is that nearly all “Volume” information is “NaN” and all “Open Interest” information is “None.”  In other words, it's missing. Is it the case perhaps that only daily volume information is available?

(2) Many expiration dates are are simply missing. For instance, the first expiry is June 16, 2023. The expiration dates of June 12, June 13, June 14 and June 15 are missing, as are many many others. 

(3) Except for the implied volatility, most of the data does not match what I'm viewing from other options sources generally considered reliable, such as OptionsNet Explorer, Options Omega and others. Quantconnect's data is from Algoseek, which generally has a solid reputation, so I'm not sure what is wrong. 

I'd love to hear from Quantconnect personnel and others. Also, if you're interested in advanced options and related trading, please feel free to comment and reach out to me. 

 

Code:
from datetime import datetime, timedelta
import pandas as pd
import numpy as np
from scipy.stats import norm
from IPython.display import display

# Initialize QuantBook
qb = QuantBook()

# Define the analysis datetime (10 AM ET on June 9, 2023)
analysis_datetime = datetime(2023, 6, 9, 14, 0, 0)  # 10 AM ET is 14:00 UTC

# Add the SPX index symbol
spx = qb.AddIndex("SPX", Resolution.Minute).Symbol

# Use the OptionChainProvider to get the list of option contracts
contracts = qb.OptionChainProvider.GetOptionContractList(spx, analysis_datetime)

if not contracts:
    print(f"No option contracts found for SPX on {analysis_datetime.date()}")
else:
    # Retrieve SPX price at the analysis datetime
    spx_price_data = qb.History(spx, analysis_datetime - timedelta(minutes=1), analysis_datetime, Resolution.Minute)
    if not spx_price_data.empty:
        spx_price = spx_price_data['close'].iloc[-1]
        print(f"SPX price at {analysis_datetime.strftime('%Y-%m-%d %H:%M:%S')} UTC: {spx_price}")
    else:
        raise Exception("SPX price data not available.")
    
    # Set strike price ranges around the SPX price
    strike_range = 100  # Adjust as needed
    lower_strike = int(spx_price - strike_range)
    upper_strike = int(spx_price + strike_range)
    
    # Filter contracts based on strike price and expiration date
    selected_contracts = [contract for contract in contracts
                          if (lower_strike <= contract.ID.StrikePrice <= upper_strike)
                          and (contract.ID.Date.date() >= analysis_datetime.date())
                          ]
    print(f"Total selected contracts: {len(selected_contracts)}")
    
    # Initialize a list to collect the data
    data_list = []
    risk_free_rate = 0.05  # Adjust this rate as needed
    
    # Define the function to compute Greeks using the Black-Scholes model
    def calculate_greeks(option_type, S, K, T, r, sigma):
        d1 = (np.log(S / K) + (r + 0.5 * sigma**2) * T) / (sigma * np.sqrt(T))
        d2 = d1 - sigma * np.sqrt(T)

        if option_type == 'Call':
            delta = norm.cdf(d1)
            theta = (-S * sigma * norm.pdf(d1) / (2 * np.sqrt(T))
                     - r * K * np.exp(-r * T) * norm.cdf(d2)) / 365
        elif option_type == 'Put':
            delta = norm.cdf(d1) - 1
            theta = (-S * sigma * norm.pdf(d1) / (2 * np.sqrt(T))
                     + r * K * np.exp(-r * T) * norm.cdf(-d2)) / 365
        else:
            delta = gamma = vega = theta = None
            return delta, gamma, vega, theta

        gamma = norm.pdf(d1) / (S * sigma * np.sqrt(T))
        vega = (S * norm.pdf(d1) * np.sqrt(T)) / 100  # Vega per 1% change in volatility

        return delta, gamma, vega, theta

    # Loop through the selected contracts
    for contract in selected_contracts:
        symbol = contract

        # Retrieve historical data for the contract up to 10 AM
        history = qb.History(symbol, analysis_datetime - timedelta(minutes=1), analysis_datetime, Resolution.Minute)
        if history.empty:
            print(f"No data available for {symbol} at {analysis_datetime}")
            continue

        # Time to expiration in years
        T = (symbol.ID.Date - analysis_datetime).total_seconds() / (365 * 24 * 60 * 60)
        if T <= 0:
            continue  # Skip expired options

        # Option price
        option_price = history['close'].iloc[-1]

        # Implied volatility (Assuming a value, as it's not readily available)
        implied_volatility = 0.2  # Adjust as needed based on market conditions
        sigma = implied_volatility

        # Volume
        volume = history['volume'].iloc[-1] if 'volume' in history.columns else None

        # Open Interest
        open_interest = history['openinterest'].iloc[-1] if 'openinterest' in history.columns else None

        # Compute Greeks
        option_type = 'Call' if symbol.ID.OptionRight == OptionRight.Call else 'Put'

        # Calculate Greeks using the Black-Scholes formula
        delta, gamma, vega, theta = calculate_greeks(
            option_type, spx_price, symbol.ID.StrikePrice, T, risk_free_rate, sigma)

        data_list.append({
            'Contract': symbol.Value,
            'Strike': symbol.ID.StrikePrice,
            'OptionRight': option_type,
            'Expiration': symbol.ID.Date.date(),
            'Last Price': option_price,
            'Implied Volatility': sigma,
            'Volume': volume,
            'Open Interest': open_interest,
            'Delta': delta,
            'Gamma': gamma,
            'Vega': vega,
            'Theta': theta,
        })

    # Convert to DataFrame
    df_options = pd.DataFrame(data_list)

    # Set Pandas display options to show all rows and columns
    pd.set_option('display.max_rows', None)
    pd.set_option('display.max_columns', None)

    # Check if DataFrame is empty before sorting
    if not df_options.empty:
        df_options = df_options.sort_values(by=['OptionRight', 'Strike'])
        
        # Group by Expiration date and create separate tables
        grouped = df_options.groupby('Expiration')
        
        for expiration_date, group_df in grouped:
            print(f"\nOptions expiring on {expiration_date}:")
            display(group_df)
    else:
        print("No data available to display.")