Downloading 15 Years of Nifty Index Options Data using Python‘s nsepy Package

If you‘re an options trader or interested in options trading strategies, having access to historical options price data is incredibly valuable. With a dataset of past daily option prices at various strikes and expirations, you can backtest virtually any options trading strategy to see how it would have theoretically performed.

In this post, we‘ll walk through the process of downloading the last 15 years of Nifty index options data from the National Stock Exchange of India (NSE) using Python. By the end, you‘ll be able to access a rich dataset that can become the foundation for your options strategy research and backtesting.

But first, let‘s cover some options basics for those who may be new to the world of calls, puts, and the infamous Greeks.

Options Trading 101

An equity option is a derivative contract that gives the buyer the right, but not the obligation, to buy (call option) or sell (put option) shares of an underlying stock at a preset price (the strike price) on or before a certain date (the expiration date).

The buyer of an option pays a premium to the seller for this right. If the underlying stock‘s price moves favorably, the option will be profitable or "in-the-money" at expiration. If not, the option expires worthless and the buyer loses the premium paid.

Several factors influence an option‘s price:

  • Underlying stock price
  • Strike price
  • Time until expiration
  • Implied volatility
  • Risk-free interest rates

The Black-Scholes options pricing model uses these inputs to calculate the theoretical price of an option. The Greeks (delta, gamma, theta, vega, rho) then measure the sensitivity of the option‘s price to changes in each factor.

  • Delta: change in option price per $1 change in underlying stock
  • Gamma: change in delta per $1 change in underlying stock
  • Theta: change in option price per 1 day decrease in time to expiration
  • Vega: change in option price per 1% change in implied volatility
  • Rho: change in option price per 1% change in the risk-free interest rate

Options can be used for speculation, hedging, or income generation. But to properly evaluate any options strategy, it helps immensely to test it on historical data first. So let‘s look at how to get that data using Python.

Introducing nsepy

nsepy is an open-source Python library that allows you to download historical stock and index data from the National Stock Exchange of India. With a few lines of code, you can access daily price data going back to 1994 for any NSE-listed security.

To install nsepy, simply run:

pip install nsepy

Then you can import the library in your Python scripts and notebooks:

from nsepy import get_history
from datetime import date

The get_history function is the workhorse of nsepy. You pass it a symbol name and date range, and it returns a DataFrame with columns for open, high, low, close prices and volume for each trading day.

nifty_fut = get_history(symbol="NIFTY", 
                        start=date(2022,1,1), 
                        end=date(2022,12,31),
                        index=True,
                        futures=True,
                        expiry_date=date(2022,12,29))

This code would download Nifty futures data for all of 2022, using the December 2022 expiry contract.

We can use the same get_history function to download options data, with a few additional parameters:

  • option_type: "CE" for call option, "PE" for put option
  • strike_price: the preset price at which the option can be exercised
nifty_call = get_history(symbol="NIFTY",
                         start=date(2022,1,1),
                         end=date(2022,12,31),
                         index=True,
                         option_type="CE",
                         strike_price=18000,
                         expiry_date=date(2022,12,29))

This downloads Nifty 18000 strike call option prices for the December 2022 expiry.

With this functionality in mind, let‘s put it to use to download 15 years of Nifty options data.

Downloading 15 Years of Nifty Options Data

Our goal is to download daily closing prices for a full range of Nifty call and put options, at all available strikes, for each monthly expiration going back 15 years from today.

Here‘s the basic process:

  1. Loop through each month and year in our desired 15 year lookback range
  2. For each month, determine the expiry date for that month‘s options
  3. Get the Nifty‘s futures price range for that month to determine which strike prices to download
  4. For both call and put options at each strike, download daily closing prices starting 2-3 months before expiry
  5. Append each contract‘s data to a master DataFrame

Breaking this down step-by-step:

Step 1: Create a loop for each month and year

First we‘ll define a start and end date for our lookback range. 15 years before May 2023 is May 2008. We‘ll create a list of years from 2008 to 2023 and a list of months 1 to 12.

import numpy as np

# List of months
month_list = np.arange(1, 13)

# List of years
year_list = np.arange(2008, 2024)

Then we set up a nested for loop to iterate through each year and month:

# Create empty DataFrames to store data
nifty_fut_data = pd.DataFrame()
option_data = pd.DataFrame()

# Loop through each year and month
for year in year_list:
    for month in month_list:

        # Code for steps 2-5 here

Step 2: Get monthly expiry date

nsepy provides a get_expiry_date function that returns a list of option expiry dates for a given year and month. Since monthly options expire on the last Thursday of each month, we‘ll grab that date.

from datetime import date

current_date = date(year, month, 1) 
expiry_date = max(get_expiry_date(year=year, month=month))

Step 3: Determine range of strike prices to download

To get a full range of option data, we‘ll download strikes from 1000 points below the low to 1000 points above the high for Nifty futures that month.

start_date = current_date - relativedelta(months=2)

nifty_fut = get_history(symbol="NIFTY",
                        start=start_date,
                        end=expiry_date, 
                        index=True,
                        futures=True,
                        expiry_date=expiry_date)

nifty_fut_data = nifty_fut_data.append(nifty_fut)

low_strike = round(nifty_fut["Low"].min() / 100) * 100 - 1000
high_strike = round(nifty_fut["High"].max() / 100) * 100 + 1000
strike_range = np.arange(low_strike, high_strike+100, 100)

This code snippet downloads Nifty futures prices from 2 months before expiry up through the expiry date to determine the high and low. It calculates a range of strikes in increments of 100 around those values.

Step 4: Download data for each strike and option type

Now we loop through the strike_range and download call and put option prices for each one:

for strike in strike_range:

    call_opt = get_history(symbol="NIFTY",
                           start=start_date,
                           end=expiry_date,
                           index=True,
                           option_type="CE",
                           strike_price=strike,
                           expiry_date=expiry_date)

    option_data = option_data.append(call_opt)

    put_opt = get_history(symbol="NIFTY",
                          start=start_date,
                          end=expiry_date,
                          index=True,
                          option_type="PE",
                          strike_price=strike,
                          expiry_date=expiry_date)

    option_data = option_data.append(put_opt)

The daily data for each contract is appended to the option_data DataFrame.

Step 5: Repeat for all months

The loop repeats this process for each month and year until we have data spanning our full 15 year lookback window. The complete code is available on my GitHub repo here.

The final option_data DataFrame contains columns for the date, expiry, option type, strike price, and closing price. We can save this to a CSV file for easy access later.

option_data.to_csv("nifty_options_data_15_years.csv", index=False)

Analyzing the Data

With 15 years of options data at your fingertips, the analysis possibilities are endless. You could test virtually any options strategy – covered calls, cash-secured puts, strangles, straddles, butterflies, condors…the list goes on.

But before diving into complex strategies, let‘s look at a simple analysis we can do with this dataset – visualizing the volatility skew.

The volatility skew refers to the phenomenon where options with lower strike prices tend to have higher implied volatilities than options with higher strikes. This is because there is usually more demand for downside protection, which gets priced into put options.

We can select a single expiry date and plot the implied volatility of each strike price to see this skew in action.

First, let‘s calculate the implied volatility. nsepy doesn‘t provide this data directly, but we can approximate it using the price, strike, risk-free rate, and days until expiry.

from scipy.stats import norm

def implied_volatility(price, strike, risk_free, days, option_type):
    if days == 0:
        days = 0.001

    P = price
    K = strike
    r = risk_free
    q = 0
    t = days/252

    if option_type == "CE":
        P = P + K*np.exp(-r*t)
        F = K*np.exp(r*t)
    else:
        F = K*np.exp(r*t) 

    def fp(sigma):
        d1 = (np.log(F/K) + (0.5*sigma**2)*t ) / (sigma*np.sqrt(t))
        d2 = d1 - sigma*np.sqrt(t)

        if option_type == "CE":
            return F*norm.cdf(d1) - K*np.exp(-r*t)*norm.cdf(d2) - P
        else:
            return K*np.exp(-r*t)*norm.cdf(-d2) - F*norm.cdf(-d1) - P

    tol = 0.00001 
    max_iter = 1000
    vol = 0.5 # initial guess

    for k in range(max_iter):

        diff = fp(vol)
        vega = K*np.exp(-r*t)*norm.pdf(-d2)*np.sqrt(t)

        if abs(diff) < tol:
            return vol
        else:
            vol = vol - diff/vega

    raise ValueError(f"Failed to converge after {max_iter} iterations")

This code defines a function implied_volatility that uses a numerical solver to find the implied volatility given an option‘s price and market conditions. It‘s based on the Black-Scholes equation.

Now let‘s select an expiry date and plot the volatility skew:

exp_date = pd.Timestamp("2022-12-29")

dec_exp_data = option_data[(option_data["Expiry"] == exp_date) & (option_data["Date"] == option_data["Date"].max())]

dec_exp_data["IV"] = dec_exp_data.apply(lambda x: implied_volatility(x["Close"], x["Strike"], 0.05, (exp_date - x["Date"]).days, x["Option Type"]), axis=1)

import matplotlib.pyplot as plt

plt.figure(figsize=(10,6))
plt.plot(dec_exp_data[dec_exp_data["Option Type"] == "CE"]["Strike"], 
         dec_exp_data[dec_exp_data["Option Type"] == "CE"]["IV"], 
         marker="o", label="Call")

plt.plot(dec_exp_data[dec_exp_data["Option Type"] == "PE"]["Strike"],
         dec_exp_data[dec_exp_data["Option Type"] == "PE"]["IV"],
         marker="o", label="Put")

plt.xlabel("Strike Price")
plt.ylabel("Implied Volatility")  
plt.title(f"Nifty Volatility Skew - {exp_date.date()}")
plt.legend()
plt.show()

This code calculates the implied volatility for each strike on the last trading day before the December 2022 expiry. It then plots the results, with call options in blue and puts in orange.

The resulting chart shows a clear volatility skew, with put options at lower strikes having significantly higher IVs than the at-the-money and call options. This is typical of most markets.

Visualizing the volatility skew is just one basic example of the types of analyses made possible with historical options data. Testing actual trading strategies is where the real fun begins.

Conclusion

Options trading is often seen as risky and speculative. But like any investment, proper research and testing can improve your odds of success. And a comprehensive dataset of historical option prices across a variety of strikes and expirations is invaluable for such testing.

In this post, we covered how to use Python and the nsepy package to download 15 years worth of Nifty index options data. With a few lines of code, you can pull daily pricing data for any NSE option and use it to backtest strategies to your heart‘s content.

We also looked at an example analysis – visualizing the implied volatility skew for a given expiry date. This is just a small taste of what‘s possible. Backtesting actual mechanical options trading strategies is the logical next step, and with this data, the sky‘s the limit.

The complete code for this project is available on GitHub. I encourage you to check it out, experiment with it, and see what new options trading insights you can uncover. And if you have any questions or feedback, don‘t hesitate to reach out. Happy trading!

How useful was this post?

Click on a star to rate it!

Average rating 0 / 5. Vote count: 0

No votes so far! Be the first to rate this post.

Similar Posts