DEV Community

Dmitry Romanoff
Dmitry Romanoff

Posted on

Unlocking Market Sentiment with Python: Analyzing Options Data Using `yfinance`

Options trading offers a unique window into market psychology. By analyzing where investors are placing their bets β€” and how much they're wagering β€” we can extract powerful insights into the collective mood of the market.

In this article, I’ll walk you through a Python script that scrapes options chain data from Yahoo Finance using the yfinance library, saves the data into structured CSVs, and performs market sentiment analysis and Max Pain price estimation β€” all with a single function call.


πŸ› οΈ Tools We’ll Use

We’ll rely on a few core Python libraries:

  • yfinance: To fetch real-time financial data from Yahoo Finance
  • csv: To export options data for further analysis
  • math, datetime, collections: For calculations and data structuring

πŸ“¦ The Core Function: fetch_and_save_options()

At the heart of this project is the fetch_and_save_options(symbol, expiration_date) function. Here’s what it does:

  1. Fetches Options Data for a given stock symbol and expiration date
  2. Writes Calls and Puts to separate CSV files
  3. Analyzes Market Sentiment using Put/Call Ratios
  4. Estimates Max Pain Price, a concept used to gauge where the underlying asset might gravitate towards by expiration

Let’s break down the key components.


1️⃣ Fetching and Validating Options Data

stock = yf.Ticker(symbol)
if expiration_date not in stock.options:
    print(f"Invalid expiration date.")
    return
options = stock.option_chain(expiration_date)
Enter fullscreen mode Exit fullscreen mode

We begin by pulling the full option chain for a symbol and validating the expiration date to avoid API errors.


2️⃣ Structuring the Data

Both calls and puts are parsed into structured dictionaries. This allows us to write them easily to CSVs:

calls_data = []
puts_data = []
Enter fullscreen mode Exit fullscreen mode

We also handle missing or NaN values gracefully to avoid data corruption.


3️⃣ Writing to CSV

Data is written to two CSV files: one for calls and another for puts.

with open(f"{symbol}_calls.csv", "w") as file:
    writer = csv.DictWriter(file, fieldnames=calls_data[0].keys())
    writer.writeheader()
    writer.writerows(calls_data)
Enter fullscreen mode Exit fullscreen mode

This makes the dataset portable and easy to load into Excel, pandas, or visualization tools.


4️⃣ Market Sentiment Analysis via Put/Call Ratios

The script calculates:

  • Total Call & Put Open Interest
  • Total Call & Put Volume
  • Put/Call Ratio (by OI and Volume)

These metrics help infer sentiment:

if pcr_oi < 0.7 and pcr_volume < 0.7:
    sentiment = "πŸ“ˆ Bullish sentiment expected."
elif pcr_oi > 1.3 and pcr_volume > 1.3:
    sentiment = "πŸ“‰ Bearish sentiment expected."
else:
    sentiment = "🀝 Neutral or uncertain."
Enter fullscreen mode Exit fullscreen mode

This gives you an immediate snapshot of whether investors are leaning bullish, bearish, or undecided.


5️⃣ Estimating the Max Pain Price

Max Pain Theory suggests that the stock price tends to move toward the strike price that causes the maximum financial loss for options holders β€” and hence, the least payout by options writers.

We calculate the loss at each strike:

for strike in sorted(strike_prices):
    total_loss = 0
    for s in strike_prices:
        ...
    if total_loss < min_loss:
        min_loss = total_loss
        max_pain_strike = strike
Enter fullscreen mode Exit fullscreen mode

And print the strike with the minimum combined payout:

print(f"πŸ’° Estimated 'Max Pain' price: ${max_pain_strike:.2f}")
Enter fullscreen mode Exit fullscreen mode

πŸ§ͺ Example Usage

fetch_and_save_options("LLY", "2025-08-15")
Enter fullscreen mode Exit fullscreen mode

Running this line will:

  • Save two CSV files: LLY_calls.csv and LLY_puts.csv
  • Print total open interest and volume
  • Show Put/Call Ratios and sentiment
  • Estimate the Max Pain price

πŸ’‘ Why This Matters

For traders and investors, understanding where money is flowing in the options market can offer an edge β€” whether it's to:

  • Confirm your investment thesis
  • Avoid getting caught on the wrong side of sentiment
  • Gauge where market participants expect the stock to go

And with Python doing the heavy lifting, you can focus on strategy, not spreadsheets.


Code


import yfinance as yf
import csv
import math
from datetime import datetime
from collections import defaultdict

def fetch_and_save_options(symbol, expiration_date):
    stock = yf.Ticker(symbol)

    if expiration_date not in stock.options:
        print(f"Invalid expiration date. Available options dates: {stock.options}")
        return

    options = stock.option_chain(expiration_date)

    calls_data = []
    puts_data = []

    total_call_oi = 0
    total_put_oi = 0
    total_call_volume = 0
    total_put_volume = 0

    # For Max Pain calculation
    strike_prices = set()
    call_oi_by_strike = defaultdict(int)
    put_oi_by_strike = defaultdict(int)

    # --- Process Calls ---
    for call in options.calls.itertuples():
        volume = 0 if call.volume is None or (isinstance(call.volume, float) and math.isnan(call.volume)) else call.volume
        open_interest = 0 if call.openInterest is None or (isinstance(call.openInterest, float) and math.isnan(call.openInterest)) else call.openInterest
        iv = 0 if call.impliedVolatility is None or (isinstance(call.impliedVolatility, float) and math.isnan(call.impliedVolatility)) else call.impliedVolatility

        calls_data.append({
            "Contract Name": call.contractSymbol,
            "Last Trade Date (EDT)": call.lastTradeDate.strftime("%m/%d/%Y %I:%M %p") if call.lastTradeDate else "",
            "Strike": call.strike,
            "Last Price": call.lastPrice,
            "Bid": call.bid,
            "Ask": call.ask,
            "Change": call.change,
            "% Change": call.percentChange,
            "Volume": volume,
            "Open Interest": open_interest,
            "Implied Volatility": f"{iv * 100:.2f}%"
        })

        total_call_volume += volume
        total_call_oi += open_interest
        call_oi_by_strike[call.strike] += open_interest
        strike_prices.add(call.strike)

    # --- Process Puts ---
    for put in options.puts.itertuples():
        volume = 0 if put.volume is None or (isinstance(put.volume, float) and math.isnan(put.volume)) else put.volume
        open_interest = 0 if put.openInterest is None or (isinstance(put.openInterest, float) and math.isnan(put.openInterest)) else put.openInterest
        iv = 0 if put.impliedVolatility is None or (isinstance(put.impliedVolatility, float) and math.isnan(put.impliedVolatility)) else put.impliedVolatility

        puts_data.append({
            "Contract Name": put.contractSymbol,
            "Last Trade Date (EDT)": put.lastTradeDate.strftime("%m/%d/%Y %I:%M %p") if put.lastTradeDate else "",
            "Strike": put.strike,
            "Last Price": put.lastPrice,
            "Bid": put.bid,
            "Ask": put.ask,
            "Change": put.change,
            "% Change": put.percentChange,
            "Volume": volume,
            "Open Interest": open_interest,
            "Implied Volatility": f"{iv * 100:.2f}%"
        })

        total_put_volume += volume
        total_put_oi += open_interest
        put_oi_by_strike[put.strike] += open_interest
        strike_prices.add(put.strike)

    # --- Write CSVs ---
    with open(f"{symbol}_calls.csv", "w", newline="") as file:
        writer = csv.DictWriter(file, fieldnames=calls_data[0].keys())
        writer.writeheader()
        writer.writerows(calls_data)

    with open(f"{symbol}_puts.csv", "w", newline="") as file:
        writer = csv.DictWriter(file, fieldnames=puts_data[0].keys())
        writer.writeheader()
        writer.writerows(puts_data)

    print(f"βœ… Options data for {symbol} on {expiration_date} saved to CSV files.")

    # --- Market Sentiment Analysis ---
    print("\n--- πŸ“Š Market Sentiment Analysis ---")
    print(f"Total Call Open Interest: {total_call_oi}")
    print(f"Total Put Open Interest: {total_put_oi}")
    print(f"Total Call Volume: {total_call_volume}")
    print(f"Total Put Volume: {total_put_volume}")

    pcr_oi = total_put_oi / total_call_oi if total_call_oi != 0 else float('inf')
    pcr_volume = total_put_volume / total_call_volume if total_call_volume != 0 else float('inf')
    print(f"Put/Call Ratio (OI): {pcr_oi:.2f}")
    print(f"Put/Call Ratio (Volume): {pcr_volume:.2f}")

    if pcr_oi < 0.7 and pcr_volume < 0.7:
        sentiment = "πŸ“ˆ Bullish sentiment expected."
    elif pcr_oi > 1.3 and pcr_volume > 1.3:
        sentiment = "πŸ“‰ Bearish sentiment expected."
    else:
        sentiment = "🀝 Market sentiment appears neutral or uncertain."
    print(f"Conclusion: {sentiment}")

    # --- Max Pain Price Calculation ---
    print("\n--- πŸ’‘ Expected Price Estimate (Max Pain Theory) ---")
    min_loss = float("inf")
    max_pain_strike = None

    for strike in sorted(strike_prices):
        total_loss = 0
        for s in strike_prices:
            call_oi = call_oi_by_strike[s]
            put_oi = put_oi_by_strike[s]
            if s > strike:  # ITM calls
                total_loss += (s - strike) * call_oi
            elif s < strike:  # ITM puts
                total_loss += (strike - s) * put_oi
        if total_loss < min_loss:
            min_loss = total_loss
            max_pain_strike = strike

    if max_pain_strike is not None:
        print(f"πŸ’° Estimated 'Max Pain' price: ${max_pain_strike:.2f}")
        print("This is the strike price where the fewest options would be in-the-money.")
    else:
        print("⚠️ Could not calculate expected price due to insufficient data.")

# πŸ§ͺ Example usage
fetch_and_save_options("LLY", "2025-08-15")

Enter fullscreen mode Exit fullscreen mode

πŸ“ˆ Next Steps

This script is just a foundation. Here are a few ideas to take it further:

  • Visualize open interest and volume per strike using matplotlib or plotly
  • Automate daily downloads for multiple tickers
  • Integrate technical indicators for a broader analysis

πŸ”š Final Thoughts

Options data hides valuable clues about market psychology. With just a few lines of Python, we can tap into that information and make smarter trading decisions. If you’re serious about finance and data, it’s time to let your code do some of the thinking.

Unlocking Market Sentiment with Python: Analyzing Options Data Using  raw `yfinance` endraw

Top comments (0)