Yahoo Finance Options Data Download with Python yfinance

Published: 7 Feb 2023

This tutorial creates a short script to download option price quotes from Yahoo Finance, using the yfinance Python module. It explains each step in detail and provides full Python code at the end.

Python yfinance module

The script uses the yfinance Python module, which scrapes price quotes and other information from the Yahoo Finance website.

You need to install it, e.g. with pip:

pip install yfinance

Then you need to import it:

import yfinance as yf

Symbol input

The input is the underlying symbol, which is a string such as "AAPL" (for the Apple stock).

symbol = "AAPL"

yfinance.Ticker class

The symbol is used to create a yfinance.Ticker object:

tk = yf.Ticker(symbol)

Getting option expiration dates

Out of the numerous Ticker attributes, we will use Ticker.options, which is a tuple of expiration dates, such as this:

expirations = tk.options
('2023-02-03',
 '2023-02-10',
 '2023-02-17',
 '2023-02-24',
 '2023-03-03',
 '2023-03-10',
 '2023-03-17',
 '2023-04-21',
 '2023-05-19',
 '2023-06-16',
 '2023-07-21',
 '2023-08-18',
 '2023-09-15',
 '2023-10-20',
 '2023-12-15',
 '2024-01-19',
 '2024-03-15',
 '2024-06-21',
 '2024-09-20',
 '2025-01-17',
 '2025-06-20',
 '2025-12-19')

Note that the expiration dates are strings ("%Y-%m-%d"), not datetime objects. They are ordered from nearest to furthest and include all expiration cycles (monthly, weekly) for the given underlying.

All expirations for-loop

We will iterate over the expirations tuple to download data for all individual expirations. On every iteration we will call a function, which we can name process_expiration():

def process_expiration(exp_td_str):
    """
    NOT IMPL
    Just a placeholder for now
    """
    return NotImplemented

for exp_td_str in expirations:
    process_expiration(exp_td_str)

Getting data for single expiration

Now we need to implement the process_expiration() function.

It takes the expiration date string (one item from expirations) as input.

For the expiration date string exp_td_str, we will use the Ticker.option_chain() method to download option quotes:

options = tk.option_chain(exp_td_str)

yfinance Options class

The output of options may seem like a pandas DataFrame at a first glance:

Options(calls=         contractSymbol             lastTradeDate  strike  lastPrice    bid  \
0   AAPL230616C00060000 2023-02-01 17:15:32+00:00    60.0      82.50  91.30   
1   AAPL230616C00065000 2023-01-19 16:53:26+00:00    65.0      70.55  86.40   
2   AAPL230616C00070000 2023-02-01 15:31:28+00:00    70.0      74.01  81.50   
3   AAPL230616C00075000 2023-02-02 16:58:58+00:00    75.0      75.90  76.55   
4   AAPL230616C00080000 2023-02-02 18:18:25+00:00    80.0      71.10  71.90   
...

But it is not.

It is an object of the yfinance.Options class.

It has two attributes, calls and puts, which are indeed pandas DataFrames:

calls = options.calls
puts = options.puts

Their rows are individual option contracts. Columns are the following.

Option chain DataFrame columns

contractSymbol
lastTradeDate
strike
lastPrice
bid
ask
change
percentChange
volume
openInterest
impliedVolatility
inTheMoney
contractSize
currency

Most of the columns are obvious. Only contractSymbol deserves some explanation.

contractSymbol format

It is a string such as "AAPL230616C00195000". It is a concatenation of:

  • Underlying symbol ("AAPL")
  • Expiration date ("230616" – only two digits for year)
  • Option type ("C" / "P")
  • Strike price (the rest, here "00195000" means $195 strike)

Expiration date and strike already have their own columns, but there is no column for underlying symbol and option type. If you want to merge all the data into a single DataFrame or insert it into a database, you may want to add these columns.

Adding underlying symbol and option type

We could possibly extract them from contractSymbol, but it is easiest to just add the columns.

Underlying symbol is the same for all rows.

Option type is same for all rows in the calls DataFrame and same for all puts in the puts DataFrame. So we can do:

calls['optionType'] = 'C'
puts['optionType'] = 'P'

The complete process_expiration() function

def process_expiration(exp_td_str):
    """
    Download Yahoo Finance call and put option quotes 
    for a single expiration
    Input:
    exp_td_str = expiration date string "%Y-$m-$d" 
        (a single item from yfinance.Ticker.options tuple)
    Return pandas.DataFrame with merged calls and puts data
    """
    
    options = tk.option_chain(exp_td_str)
    
    calls = options.calls
    puts = options.puts
    
    # Add optionType column
    calls['optionType'] = 'C'
    puts['optionType'] = 'P'
    
    # Merge calls and puts into a single dataframe
    exp_data = pd.concat(objs=[calls, puts], ignore_index=True)
    
    return exp_data

We have merged calls and puts into a single DataFrame. Alternatively, you may want to return them separately as a tuple or dict.

This way, we can iterate over all expirations and concatenate all the resulting DataFrames.

Full Python code

import datetime as dt
import pandas as pd
import yfinance as yf


symbol = "AAPL"


def process_expiration(exp_td_str):
    """
    Download Yahoo Finance call and put option quotes 
    for a single expiration
    Input:
    exp_td_str = expiration date string "%Y-$m-$d" 
        (a single item from yfinance.Ticker.options tuple)
    Return pandas.DataFrame with merged calls and puts data
    """
    
    options = tk.option_chain(exp_td_str)
    
    calls = options.calls
    puts = options.puts
    
    # Add optionType column
    calls['optionType'] = 'C'
    puts['optionType'] = 'P'
    
    # Merge calls and puts into a single dataframe
    exp_data = pd.concat(objs=[calls, puts], ignore_index=True)
    
    return exp_data


tk = yf.Ticker(symbol)
expirations = tk.options

# Create empty DataFrame, then add individual expiration data to it
data = pd.DataFrame()

for exp_td_str in expirations[:1]:
    exp_data = process_expiration(exp_td_str)
    data = pd.concat(objs=[data, exp_data], ignore_index=True)
    
# Add underlyingSymbol column
data['underlyingSymbol'] = symbol

Bulk download options data

We can wrap the entire code in a new process_symbol() function and then iterate over a list of symbols to download options data for all.

SYMBOLS = ['AAPL', 'MSFT', 'XOM', 'BAC']

def process_symbol(symbol):
    tk = yf.Ticker(symbol)
    ...
    return data

for symbol in SYMBOLS:
    process_symbol(symbol)
    ...

When downloading data in bulk, be considerate and do not overload Yahoo servers. Ideally put a delay in between individual symbol downloads (using time.sleep) and do not download more data than you really need. You will reduce the risk of getting your IP address blocked by Yahoo.

By remaining on this website or using its content, you confirm that you have read and agree with the Terms of Use Agreement.

We are not liable for any damages resulting from using this website. Any information may be inaccurate or incomplete. See full Limitation of Liability.

Content may include affiliate links, which means we may earn commission if you buy on the linked website. See full Affiliate and Referral Disclosure.

We use cookies and similar technology to improve user experience and analyze traffic. See full Cookie Policy.

See also Privacy Policy on how we collect and handle user data.

© 2024 FinTut