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.
On this page:
- Python yfinance module
- Symbol input
- yfinance.Ticker class
- Getting option expiration dates
- All expirations for-loop
- Getting data for single expiration
- yfinance Options class
- Option chain DataFrame columns
- contractSymbol format
- Adding underlying symbol and option type
- The complete process_expiration() function
- Full Python code
- Bulk download options data
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.