In our previous articles, we built a robust theoretical foundation. We started with the clear, predictable world of option payoff diagrams at expiration. Then, we explored the dynamic forces, the Greeks, that cause an option’s price to fluctuate during its lifetime Now, it’s time to leave the laboratory and step into the real world.
Real-world financial data is rarely as pristine as the inputs to a theoretical model. It comes with quirks, formatting issues, and requires careful handling before any meaningful analysis. This is especially true for options data, which is inherently more complex than simple stock price history. For any given stock, there are dozens, if not hundreds, of individual option contracts, each with a unique strike price, expiration date, and type (call or put).
This collection of contracts is known as an option chain, and learning to import, clean, and interpret it is a fundamental skill for any quantitative analyst or trader.
In this article, we will bridge the gap between theory and practice. Using the pandas library in Python, we will tackle a real-world dataset of Netflix (NFLX) options. We will walk through the essential, step-by-step process of:
- Importing and Inspecting the raw option chain data from CSV files.
- Data Wrangling: Cleaning and transforming messy columns into usable numerical formats.
- Feature Engineering: Calculating critical metrics like time to expiration and the bid-ask spread.
- Extracting Market Insights: Visualizing data to understand market sentiment, liquidity, and areas of high interest.
Let’s get our hands dirty and turn raw data into actionable intelligence.
Importing the Option Chain
An option chain is a table listing all available option contracts for a given security. Our data is provided in two separate files: nflx_call.csv for call options and nflx_puts.csv for put options. Our first task is to load this data into pandas DataFrames, which are the primary data structures for analysis in Python.
import pandas as pd
import numpy as np
import matplotlib.pyplot as plt
from datetime import datetime
# Load the datasets
try:
calls_df = pd.read_csv('mflx_call.csv')
puts_df = pd.read_csv('nflx_puts.csv')
except FileNotFoundError:
print("Make sure 'mflx_call.csv' and 'nflx_puts.csv' are in the correct directory.")
# Create empty dataframes to avoid further errors in the script
calls_df = pd.DataFrame()
puts_df = pd.DataFrame()
# Display the first few rows of the call options data to understand its structure
if not calls_df.empty:
print("Netflix Call Options Chain:")
print(calls_df.head())
When we look at the output, we see a rich dataset with columns like Strike, Last Price, Bid, Ask, Volume, Open Interest, and Implied Volatility. However, we can immediately spot a few issues that need addressing. For instance, Implied Volatility is a string with commas and percentage signs, not a number. This brings us to the most critical phase: data cleaning.
The Data Janitor’s Work: Cleaning and Preprocessing
Before we can perform any calculations or create plots, we must convert our data into a clean, numerical format. This preprocessing stage is often the most time-consuming part of data analysis, but it is absolutely essential for accurate results.
Step 1: Cleaning Implied Volatility
The Implied Volatility column is formatted for human reading, not for machine computation (e.g., “3,228.13%”). We need to remove the non-numeric characters (, and %) and convert the result into a floating-point number representing the decimal value (e.g., 32.2813).
def clean_iv(iv_string):
"""Function to clean the Implied Volatility string."""
if isinstance(iv_string, str):
return float(iv_string.replace(',', '').replace('%', '')) / 100
return iv_string
# Apply the cleaning function to both DataFrames
if not calls_df.empty:
calls_df['Implied Volatility'] = calls_df['Implied Volatility'].apply(clean_iv)
if not puts_df.empty:
puts_df['Implied Volatility'] = puts_df['Implied Volatility'].apply(clean_iv)
# print("\nCleaned Implied Volatility for Calls:")
# if not calls_df.empty:
# print(calls_df[['Strike', 'Implied Volatility']].head())
Step 2: Calculating Time to Expiration
The Black-Scholes model from our previous article requires time to expiration (t) as an input, expressed in years. Our dataset does not have this directly, so we must calculate it. For this example, we’ll assume the data was pulled on June 21, 2022, and the options expire on June 24, 2022.
In a live trading application, the current_date would be today’s date, and the expiration_date would be parsed from the Contract Name or provided by the data source. Here, we use fixed dates to ensure the example is reproducible.
# Define the relevant dates
current_date = datetime(2022, 6, 21)
expiration_date = datetime(2022, 6, 24)
# Calculate the number of days until expiration
days_to_expiration = (expiration_date - current_date).days
# Convert to years for financial modeling
time_to_expiration_years = days_to_expiration / 365.0
print(f"\nDays to Expiration: {days_to_expiration}")
print(f"Time to Expiration (in Years): {time_to_expiration_years:.4f}")
Days to Expiration: 3
Time to Expiration (in Years): 0.0082
Step 3: Calculating Bid-Ask Spread and Midpoint Price
The Last Price of an option can sometimes be stale if the contract hasn’t traded recently. A more reliable indicator of an option’s current market value is the midpoint between the Bid (the highest price a buyer will pay) and the Ask (the lowest price a seller will accept).
# Calculate the spread and midpoint for both calls and puts
if not calls_df.empty:
calls_df['Spread'] = calls_df['Ask'] - calls_df['Bid']
calls_df['Midpoint'] = (calls_df['Ask'] + calls_df['Bid']) / 2
if not puts_df.empty:
puts_df['Spread'] = puts_df['Ask'] - puts_df['Bid']
puts_df['Midpoint'] = (puts_df['Ask'] + puts_df['Bid']) / 2
# print("\nCalls with Spread and Midpoint:")
# if not calls_df.empty:
# print(calls_df[['Strike', 'Bid', 'Ask', 'Spread', 'Midpoint']].head())
With our data now clean and ready, we can move on to the exciting part: analysis and visualization.
Uncovering Market Insights from the Data
Now we can query our cleaned data to answer important questions about market activity and sentiment.
Insight 1: Open Interest – Where is the Action?
Open Interest is the total number of outstanding option contracts that have not been settled or closed. It represents the total level of participation in a specific contract. High open interest at a particular strike price suggests that it is a significant level for many market participants. It could be a target price, a hedging level, or a point of expected support or resistance.
Let’s plot Open Interest against the strike price for both calls and puts.
if not (calls_df.empty or puts_df.empty):
plt.figure(figsize=(12, 7))
plt.bar(calls_df['Strike'], calls_df['Open Interest'], width=1.5, label='Call Open Interest')
plt.bar(puts_df['Strike'], puts_df['Open Interest'], width=1.5, label='Put Open Interest')
plt.xlabel('Strike Price ($)')
plt.ylabel('Number of Open Contracts')
plt.title('Open Interest for Netflix (NFLX) Options')
plt.legend()
plt.grid(axis='y', linestyle='--')
plt.show()

From this chart, we can instantly identify the strike prices with the highest concentration of activity. These peaks are “hotspots” that warrant closer inspection, as they represent points of maximum consensus or speculation among traders.
Insight 2: Put-Call Ratio – Gauging Market Sentiment
The Put-Call Ratio is a widely used sentiment indicator. It is calculated by dividing the total open interest of put options by the total open interest of call options.
- A high ratio (> 1.0) suggests that traders are more interested in puts than calls, which is generally interpreted as a bearish sentiment.
- A low ratio (< 0.7) suggests a more bullish sentiment, with a preference for calls.
- A ratio between 0.7 and 1.0 is often considered neutral.
if not (calls_df.empty or puts_df.empty):
# Calculate total open interest
total_call_oi = calls_df['Open Interest'].sum()
total_put_oi = puts_df['Open Interest'].sum()
# Calculate the ratio
put_call_ratio = total_put_oi / total_call_oi
print(f"\nTotal Call Open Interest: {total_call_oi:,.0f}")
print(f"Total Put Open Interest: {total_put_oi:,.0f}")
print(f"Put-Call Ratio (by Open Interest): {put_call_ratio:.2f}")
Total Call Open Interest: 202,375
Total Put Open Interest: 19,520
Put-Call Ratio (by Open Interest): 0.10
A single day’s ratio provides a snapshot, but its true power comes from tracking it over time to see how sentiment is evolving.
Insight 3: Bid-Ask Spread – A Measure of Liquidity
As we know, the bid-ask spread is a direct measure of an option’s liquidity. Illiquid options (wide spreads) are riskier and more expensive to trade. Let’s visualize how the spread changes as we move away from the current stock price (i.e., for different strike prices). We would expect the most liquid options to be “at-the-money” or near it.
if not (calls_df.empty or puts_df.empty):
plt.figure(figsize=(12, 7))
plt.plot(calls_df['Strike'], calls_df['Spread'], marker='o', linestyle='--', label='Call Bid-Ask Spread')
plt.plot(puts_df['Strike'], puts_df['Spread'], marker='x', linestyle='--', label='Put Bid-Ask Spread')
plt.xlabel('Strike Price ($)')
plt.ylabel('Bid-Ask Spread ($)')
plt.title('Option Liquidity for Netflix (NFLX)')
plt.legend()
plt.grid(linestyle='--')
plt.show()

The plot typically shows that the spreads are tightest for options near the current stock price and widen significantly for deep in-the-money or far out-of-the-money options. This confirms that liquidity is concentrated where the most trading activity occurs.
Conclusion
In this article, we took the essential leap from clean theory to messy reality. We demonstrated that with a structured approach and the right tools, we can systematically understand raw options data. We performed crucial cleaning tasks and most importantly transformed the cleaned data into actionable insights. We identified key strike prices through open interest, measuring market mood with the put-call ratio, and assessing trading costs via the bid-ask spread.
You now have the foundational skills to approach any options dataset. With this practical knowledge in hand, we are fully prepared for our next topic: using this data to compare the P&L and risk profiles of different leveraged trading strategies.

