Ask me anything
Ithy Logo
Sign in to save

Comprehensive Troubleshooting for Your Python Options Script

Ensure your script accurately filters and saves options data

python code debugging

Key Takeaways

  • Expiration Date Filtering: The script correctly filters options expiring between 25 and 120 days from the current date.
  • Price Filtering Mechanism: Options are filtered to include only those with strike prices within 10% of the current stock price.
  • Potential Issues and Solutions: Various factors, including API response structure and data availability, may prevent options from being saved. Implementing thorough debugging and adjusting filter parameters can resolve these issues.

1. Confirmation of Expiration Date Filtering

Ensuring Options Expire Within the Specified Range

Your script sets EXPIRY_MIN_DAYS to 25 and EXPIRY_MAX_DAYS to 120, which is intended to filter options that expire between 25 and 120 days from the current date. The relevant portion of your code is as follows:


days_to_expiry = (expiry_date - today).days
if not (EXPIRY_MIN_DAYS <= days_to_expiry <= EXPIRY_MAX_DAYS):
    continue
  

This logic correctly calculates the number of days until an option's expiration and appropriately filters out options that do not fall within the specified range. Therefore, you can be confident that the script is functioning as intended in this regard.

Additional Considerations

Ensure that the parse_expiry_date function accurately parses the expiration dates from the API response. Any discrepancies in date formats can lead to incorrect calculations of days_to_expiry.


2. Verification of Price Filtering

Filtering Options Within 10% of the Current Stock Price

Your script includes a price filter designed to retain only those options whose strike prices are within 10% of the current stock price. The relevant code segment is:


if abs(stock_price - strike_price) / stock_price > PRICE_WITHIN_PERCENT:
    continue
  

This calculation determines the percentage difference between the stock price and the option's strike price. If the difference exceeds 10%, the option is excluded. This ensures that only options closely related to the current stock price are considered, aligning with your intended filtering criteria.

Ensuring Accurate Stock Price Retrieval

It's crucial that the get_stock_price function retrieves accurate and up-to-date stock prices. Any inaccuracies in this function can lead to incorrect filtering of options. Implementing logging to verify the retrieved stock prices can help ensure this component functions correctly.


3. Troubleshooting: Why Are No Options Being Saved?

Identifying and Resolving Common Issues

Despite the script's logic being sound in theory, several factors could prevent options from being saved. Below are potential causes and corresponding solutions:

a. API Response Structure Mismatch

The structure of the API response is pivotal for correctly processing options data. Your print statements indicate that the response includes keys such as expirationDate, impliedVolatility, and options, but not calls or puts. This discrepancy can lead to the script failing to access and process the necessary data.

Solution: Modify the script to align with the actual structure of the API response. Instead of accessing calls and puts directly, iterate through the options list and determine the option type within each option object.

b. Missing or Incorrect Data Fields

If the API response lacks essential fields such as strike, openInterest, bid, or ask, the filtering logic will fail to process these options. This can result in no options meeting all the criteria, hence nothing being saved.

Solution: Implement validation checks to ensure all required fields are present before applying filters. Additionally, handle cases where certain fields might be missing to prevent the script from skipping valid options.

c. Open Interest Threshold Too High

Your script sets an OPEN_INTEREST_THRESHOLD of 100. If most options for your tickers have open interest below this threshold, they will be excluded from the results.

Solution: Temporarily lower the OPEN_INTEREST_THRESHOLD to determine if this is the limiting factor. For example, reduce it to 50 or 10 and observe if options start appearing in the CSV.

d. API Rate Limits or Key Issues

Exceeding API rate limits or using an invalid API key can result in failed data retrieval. Your code includes a RATE_LIMIT_DELAY of 0, which might lead to rapid successive API calls that exceed allowable limits.

Solution: Introduce a reasonable delay between API calls by setting RATE_LIMIT_DELAY to a value like 1 or 2 seconds. Additionally, ensure that your API key is valid and has the necessary permissions to access the options data.

e. CSV Export Logic Errors

Even if options are correctly filtered, errors in the CSV export function can prevent data from being saved. This includes issues like incorrect file paths, write permissions, or bugs in the export_to_csv function.

Solution: Verify that the CSV export section of your code is functioning correctly. Test by manually appending a sample option to the filtered_options list and ensuring it gets written to the CSV.

f. Stock Price Retrieval Failures

If the get_stock_price function fails to retrieve the current stock price for a ticker, the script will skip processing options for that ticker altogether.

Solution: Add logging within the get_stock_price function to confirm that stock prices are being retrieved successfully. Address any issues related to API response handling or network connectivity that may impede this process.


4. Analyzing Sample Output and Data Structures

Interpreting API Response Structures

Your sample data structure indicates the presence of keys such as expirationDate, impliedVolatility, and options. However, the absence of calls and puts directly suggests that options are nested differently within the response.

Adjusting Code to Match Data Structure

Modify your options processing loop to iterate through the options list and identify the option type within each option. Here's an example of how you can adjust your code:


for chain in options_data.get('data', []):
    expiry_date = parse_expiry_date(chain.get('expirationDate'))
    if not expiry_date:
        continue

    days_to_expiry = (expiry_date - today).days
    if not (EXPIRY_MIN_DAYS <= days_to_expiry <= EXPIRY_MAX_DAYS):
        continue

    # Process each option within the options list
    for option in chain.get('options', []):
        try:
            strike_price = float(option.get('strike', 0))
            option_type = option.get('type', '').lower()  # Assuming 'call' or 'put'

            if abs(stock_price - strike_price) / stock_price > PRICE_WITHIN_PERCENT:
                continue

            if option.get('openInterest', 0) < OPEN_INTEREST_THRESHOLD:
                continue

            filtered_options.append({
                "ticker": ticker,
                "expiration_date": expiry_date.strftime("%Y-%m-%d"),
                "strike_price": strike_price,
                "option_type": option_type,
                "bid": option.get('bid', 0),
                "ask": option.get('ask', 0),
                "last_price": option.get('lastPrice', 0),
                "open_interest": option.get('openInterest', 0),
                "implied_volatility": option.get('impliedVolatility', 0),
                "delta": option.get('delta', 0),
                "underlying_price": stock_price
            })

        except Exception as e:
            print(f"Error processing option: {e}")
            continue
  

By iterating through the options list and dynamically identifying the option type, your script will better align with the API's data structure, increasing the likelihood of successfully filtering and saving relevant options.


5. Enhancing Debugging and Logging

Implementing Comprehensive Debug Statements

To effectively identify where the script is failing, implement detailed logging throughout your code. This will help you trace the script's execution flow and pinpoint problematic areas.

a. Logging Stock Prices

Ensure that stock prices are being retrieved correctly:


def get_stock_price(ticker):
    # Existing code...
    try:
        response = requests.get(url, params=params)
        response.raise_for_status()
        data = response.json()
        stock_price = data.get('c')
        print(f"Stock price for {ticker}: {stock_price}")
        return stock_price
    except Exception as e:
        print(f"Error fetching price for {ticker}: {e}")
        return None
  

b. Logging Raw API Responses

Print the entire API response to verify its structure:


def fetch_options_chain(ticker):
    # Existing code...
    try:
        response = requests.get(url, params=params)
        response.raise_for_status()
        data = response.json()
        print(f"Raw options data for {ticker}: {data}")
        return data
    except Exception as e:
        print(f"Error fetching options chain for {ticker}: {e}")
        return None
  

c. Logging Each Filtering Stage

Track how many options pass each filter criterion:


for chain in options_data.get('data', []):
    expiry_date = parse_expiry_date(chain.get('expirationDate'))
    if not expiry_date:
        print(f"Skipping due to unrecognized expiry for {ticker}")
        continue

    days_to_expiry = (expiry_date - today).days
    if not (EXPIRY_MIN_DAYS <= days_to_expiry <= EXPIRY_MAX_DAYS):
        print(f"Option expired in {days_to_expiry} days, outside range for {ticker}")
        continue

    for option in chain.get('options', []):
        try:
            strike_price = float(option.get('strike', 0))
            option_type = option.get('type', '').lower()

            price_diff = abs(stock_price - strike_price) / stock_price
            if price_diff > PRICE_WITHIN_PERCENT:
                print(f"Strike price {strike_price} differs by {price_diff*100}% for {ticker}")
                continue

            open_interest = option.get('openInterest', 0)
            if open_interest < OPEN_INTEREST_THRESHOLD:
                print(f"Open interest {open_interest} below threshold for {ticker}")
                continue

            # Option passes all filters
            print(f"Adding option: {option}")
            # Append to filtered_options

        except Exception as e:
            print(f"Error processing option: {e}")
            continue
  

d. Confirming CSV Export

Verify that options are being written to the CSV:


def export_to_csv(filtered_options):
    if not filtered_options:
        print("No options data to export.")
        return

    try:
        with open("options_research.csv", mode="w", newline="") as file:
            writer = csv.DictWriter(file, fieldnames=[
                "ticker", "expiration_date", "strike_price", "option_type",
                "bid", "ask", "last_price", "open_interest", "implied_volatility",
                "delta", "underlying_price"
            ])
            writer.writeheader()
            # Sort by ask price for easier analysis
            for option in sorted(filtered_options, key=lambda x: x['ask']):
                writer.writerow(option)
                print(f"Written option to CSV: {option}")
        print("Export complete: options_research.csv")
    except Exception as e:
        print(f"Error exporting to CSV: {e}")
  

6. Recommendations for Script Improvement

Optimizing Your Python Script for Better Performance and Reliability

a. Aligning with API Response Structure

Since the API response does not contain calls and puts directly, restructure your option processing logic to handle the actual data structure. This will ensure that all available options are correctly evaluated.

b. Enhancing Error Handling

Implement robust error handling to catch and log unexpected issues. This includes handling missing fields, unexpected data types, and network-related errors.

c. Implementing Configurable Parameters

Allow parameters such as OPEN_INTEREST_THRESHOLD, EXPIRY_MIN_DAYS, EXPIRY_MAX_DAYS, and PRICE_WITHIN_PERCENT to be configurable. This flexibility enables you to adjust filters without modifying the code.

d. Utilizing Logging Libraries

Instead of using simple print() statements, consider using Python's built-in logging library. This provides more control over log levels and outputs, facilitating easier debugging and monitoring.


import logging

# Configure logging
logging.basicConfig(level=logging.INFO, format='%(asctime)s - %(levelname)s - %(message)s')

# Replace print statements with logging
logging.info(f"Fetching options for {ticker}...")
logging.debug(f"Raw options data for {ticker}: {data}")
logging.warning(f"No options data available for {ticker}")
logging.error(f"Error fetching options chain for {ticker}: {e}")
  

e. Validating API Key and Permissions

Ensure that the API key used has the necessary permissions to access the required endpoints. Verify the key's validity and check for any access restrictions or rate limits imposed by the API provider.

f. Reducing API Call Rate

Set a reasonable RATE_LIMIT_DELAY to prevent exceeding API rate limits. A delay of 1 to 2 seconds between API calls is generally advisable.


RATE_LIMIT_DELAY = 1  # Delay in seconds between API calls
time.sleep(RATE_LIMIT_DELAY)
  

g. Testing with a Smaller Ticker Set

Before scaling up to 1,000 tickers, test your script with a smaller subset to ensure it functions correctly. This approach allows you to identify and resolve issues without overwhelming the system.

h. Implementing Unit Tests

Create unit tests for individual functions to verify their correctness. Testing functions like get_stock_price, fetch_options_chain, and parse_expiry_date separately can help ensure each component works as expected.


import unittest

class TestOptionsScript(unittest.TestCase):
    def test_parse_expiry_date(self):
        date_str = "2025-05-15"
        expected_date = datetime(2025, 5, 15)
        self.assertEqual(parse_expiry_date(date_str), expected_date)

    def test_get_stock_price(self):
        # Mock API response
        pass  # Implement mocking logic

    # Additional tests...

if __name__ == '__main__':
    unittest.main()
  

7. Example of an Improved Processing Loop

Aligning with API Data Structure and Enhancing Robustness

Below is an example of how you can restructure your processing loop to better handle the API's data structure and incorporate enhanced error handling and logging:


def fetch_and_filter_options(tickers):
    filtered_options = []
    today = datetime.now()

    for ticker in tickers:
        logging.info(f"Fetching options for {ticker}...")

        # Get current stock price
        stock_price = get_stock_price(ticker)
        if not stock_price:
            logging.warning(f"Could not get price for {ticker}, skipping...")
            continue

        # Get options chain
        options_data = fetch_options_chain(ticker)
        if not options_data or 'data' not in options_data:
            logging.warning(f"No options data available for {ticker}")
            continue

        # Process each option chain
        for chain in options_data.get('data', []):
            expiry_date = parse_expiry_date(chain.get('expirationDate'))
            if not expiry_date:
                logging.warning(f"Unrecognized expiry date format for {ticker}: {chain.get('expirationDate')}")
                continue

            days_to_expiry = (expiry_date - today).days
            if not (EXPIRY_MIN_DAYS <= days_to_expiry <= EXPIRY_MAX_DAYS):
                logging.info(f"Option expired in {days_to_expiry} days, outside range for {ticker}")
                continue

            # Iterate through each option in the 'options' list
            for option in chain.get('options', []):
                try:
                    strike_price = float(option.get('strike', 0))
                    option_type = option.get('type', '').lower()

                    price_diff = abs(stock_price - strike_price) / stock_price
                    if price_diff > PRICE_WITHIN_PERCENT:
                        logging.debug(f"Strike price {strike_price} differs by {price_diff*100}% for {ticker}")
                        continue

                    open_interest = option.get('openInterest', 0)
                    if open_interest < OPEN_INTEREST_THRESHOLD:
                        logging.debug(f"Open interest {open_interest} below threshold for {ticker}")
                        continue

                    # Option passes all filters
                    filtered_option = {
                        "ticker": ticker,
                        "expiration_date": expiry_date.strftime("%Y-%m-%d"),
                        "strike_price": strike_price,
                        "option_type": option_type,
                        "bid": option.get('bid', 0),
                        "ask": option.get('ask', 0),
                        "last_price": option.get('lastPrice', 0),
                        "open_interest": open_interest,
                        "implied_volatility": option.get('impliedVolatility', 0),
                        "delta": option.get('delta', 0),
                        "underlying_price": stock_price
                    }
                    filtered_options.append(filtered_option)
                    logging.info(f"Added option: {filtered_option}")

                except Exception as e:
                    logging.error(f"Error processing option for {ticker}: {e}")
                    continue

        # Respect API rate limits
        time.sleep(RATE_LIMIT_DELAY)

    return filtered_options
  

This revised loop aligns with the actual data structure returned by the API, adds comprehensive logging for better traceability, and maintains robust error handling to ensure smooth execution.


8. Recommendations for API Usage and Data Handling

Best Practices for Working with API Data in Python

a. Understanding API Documentation

Thoroughly review the API's documentation to understand the exact structure of responses, available endpoints, and any changes that might have occurred. This knowledge is essential for accurately parsing and utilizing the data.

b. Handling Pagination

If the API paginates results, ensure your script correctly handles multiple pages of data. This involves iterating through pages until all relevant data is retrieved.

c. Managing Rate Limits

Implement strategies to respect the API's rate limits, such as exponential backoff, retries with delays, and batching requests where possible. This prevents your script from being throttled or blocked.

d. Securing API Keys

Protect your API keys by avoiding hardcoding them into scripts. Use environment variables or configuration files with appropriate access controls to keep your keys secure.

e. Validating Data Integrity

After fetching data, perform validation checks to ensure the integrity and completeness of the data. This includes verifying data types, checking for null values, and confirming that all necessary fields are present.

f. Optimizing Data Storage

Consider using more efficient data storage solutions for large datasets, such as databases or dataframes, especially when dealing with thousands of tickers. This can improve performance and make data manipulation easier.


Conclusion

Your Python script is logically structured to filter options based on expiration dates and strike prices relative to the current stock price. However, discrepancies between the expected and actual API response structures, combined with potential high thresholds for open interest and possible API rate limiting, are likely preventing options from being saved. By aligning your code with the actual data structure, implementing comprehensive logging, adjusting filter parameters, and enhancing error handling, you can effectively troubleshoot and resolve these issues. Additionally, adhering to best practices for API usage and data handling will ensure your script operates reliably and efficiently, even when scaling up to process a large number of tickers.


References


Last updated January 23, 2025

AI-assisted research. Sources, methodology and corrections · Source-checked guides and tools · Explore published answers

Ask Ithy AI