In my previous article on fat tails in the NSE Nifty, we saw that fat-tailed events are more frequent than expected under a standard normal distribution. We also saw the impact on our returns if we are able to protect ourselves against the downside of negative black swans or capture the upside from positive black swans — reproduced below for reference (link here).
While there are many ways to achieve these objectives, the ones that interest me the most are equity options. Backtesting your options strategy is always a good idea before you start executing it. So I downloaded 15 years of index options data of the NSE Nifty. This is based on the very helpful nsepy package.
During the course of this article, I will also touch upon how to work with data objects and go back or move forward in time as well as optionally structure nested for loops
Here’s a longish video on the basics of equity options that you should review before reading further if you have no idea about what options are. Below is a 2 minute Maggi noodle version
Options are like term insurance —
However, unlike term insurance —
Let’s look at examples of both
Note: This is an extremely simplistic view. Please read and watch some videos to understand this further. And don’t trade in options till you don’t understand the risks. It is better for you to donate to a charity.
# lets try to download for a single option first; manually specify dates sample_opt1 = get_history(symbol = 'NIFTY', start = date(2004,11,1), end = date(2005,1,27), index = True, option_type = 'PE', strike_price = 2000, expiry_date = date(2005,1,27))
print(sample_opt1.shape) sample_opt1.head(n=3)
For each options contract, we need to enter 5 variables- the start and end dates, option type, strike (contract) price, and its expiry date. The NSE site allows us to only download options data for 1 contract at a time. See the screenshot below.
Since our objective is to look at the impact of black swan events we need to get deep out of the money options for strategy. This means that for each month we need over 20 option prices for 90 months (15 years). We need to loop else we get loopy 🙂
For each strike price, we have two types of options. Recall that an option is a contract that allows us to buy the underlying item (NSE Nifty for us) at a pre-agreed price on a future date irrespective of the market price on the expiry date. Call options are a right to buy the underlying and used when the market price increases while Put options are the right to sell and used to protect oneself when the market falls.
The NSE has weekly, monthly and yearly options listed on its website for strike prices that are sufficiently above and below the current market levels (aka at-the-money option price). See screenshot from the NSE site below
Of all these options, monthly options are the most liquid for the current month and next month. Liquidity shifts to the next month as the current month nears expiry. Weekly options were started a few years ago but they lack sufficient liquidity beyond the current week and next week.In summary, we need to download data for monthly options with a start date 3 months prior to expiry for strike prices ~1000 points above and below the highest and lowest prices for the month for 90 months.
Monthly options are settled on the last Thursday of each month. The current date is defined as a “ object. We use the relativedelta from dateutils library to get the download start date by going back 2 months (see months = -2)
# current date - 3 months prior to the 1st option contract expiry current_date = date(2005, 1,1); print(current_date); print(type(current_date)) type(current_date)
# price download start date start_date = current_date + relativedelta(months = -2); print(start_date); print(type(start_date)) start_month = current_date.month; print('Start Month:', start_month)
start_yr = start_date.year; print('Start Year: ', start_yr)
NSE options expire on the last Thursday of the month for daily options and every Thursday for weekly options. For the last week of the month the monthly option doubles as the weekly option. We use the `get_expiry` function from the NSEPy library to get the list of data for all Thursdays for the month and put it inside a max function to get the date of the last Thursday or the monthly expiry.
# get expiry date end_month = current_date.month; print('End Month:', end_month) end_yr = current_date.year; print('End Year: ', end_yr) # Use the get expiry function to get a list of expiry dates - sample below # get_expiry_date returns a list of weekly expiries; use max to get the month end expiry date expiry_date = max(get_expiry_date(year = end_yr, month = end_month)) print('Expiry_date:', expiry_date, 'Type: ', type(expiry_date)) type(expiry_date)
With a clear handle on start and end dates, we proceed to embed them into a loop to allow us to call the `get_expiry` function for each month over 15 years. We’ll use nested for-loops for this.
In order to identify the range of option strike prices we get the Nifty closing value for each month; define a range of strike prices that are 1000 points above the highest price for the month and 1000 points below the lowest closing for that month. For each option, we get daily prices that are 3 months prior to the expiry date.
Before we code, let’s do a recap and understand what exactly it is that we want to loop over. We want monthly option data for 15 years so 180 months. For each month assume that the average range between high and low values is 300 points. With over 1000 points above the high point and 1000 points below the option point, we have 23 option strike prices at each month and 2 types of options — Puts and Calls; which takes us to 46 discrete options per month. 46 options times 180 months gives us 8280 strike prices.
The nested loops are run as follows:
For each year in the range → For each month in the year → For each strike
# define and month year range to loop over month_list = np.arange(1, 13, step = 1); print(month_list)
# break the year list into 2 parts - 2005 to 2012 and 2013 to 2020 yr_list = np.arange(2005, 2012, step = 1 ); print(yr_list)
# create empty dataframe to store results nifty_data = pd.DataFrame() # to use in the loop option_data = pd.DataFrame() # to store output counter = 0
# break the loop into 2 parts to avoid querying errors for yr in yr_list: # loop through all the months and years print('Year: ', yr) for mnth in month_list: current_dt = date(yr, mnth, 1) start_dt = current_dt + relativedelta(months = -2) end_dt = max(get_expiry_date(year = yr, month = mnth)) # print('current: ', current_dt) # print('start: ', start_dt) # print('end: ', end_dt) # get nifty futures data nifty_fut = get_history(symbol = 'NIFTY', start = start_dt, end = end_dt, index = True, expiry_date = end_dt) nifty_data = nifty_data.append(nifty_fut) # calculate high and low values for each month; round off to get strike prices high = nifty_fut['Close'].max() high = int(round(high/100)*100) + 1000# ; print('High:', high) low = nifty_fut['Close'].min() low = int(round(low/100)*100) + 1000# ; print('Low :', low) for strike in range(low, high, 100): # start, stop, step """ get daily closing nifty index option prices for 3 months over the entire range """ #time.sleep(random.randint(10,25)) # pause for random interval so as to not overwhelm the site nifty_opt = get_history(symbol = 'NIFTY', start = start_dt, end = end_dt, index = True, option_type = 'PE', strike_price = strike, expiry_date = end_dt) option_data = option_data.append(nifty_opt) #time.sleep(random.randint(20,50)) # pause for random interval so as to not overwhelm the site nifty_opt = get_history(symbol = 'NIFTY', start = start_dt, end = end_dt, index = True, option_type = 'CE', strike_price = strike, expiry_date = end_dt) option_data = option_data.append(nifty_opt) counter+=1 print('Months: ', counter)
And voila! We have 15 years of option data for a range of strike prices that can be stored to csv; let’s verify before we store
# visually verify print(option_data.shape) option_data.tail()
All the above code is on this GitHub page.
Connect with me on LinkedIn, Twitter, or Medium to stay updated. That’s all folks!
Note: All content is for research purposes and not investment recommendations. I recommend that you do not try the same without consulting a registered financial advisor and only then make investment decisions.
Hi, Thanks for sharing this. start_date = current_date + relativedelta(months = -2); print(start_date); print(type(start_date)) this line throws the following error NameError: name 'relativedelta' is not defined
Hi, I very much appreciate your efforts to write this article. Last week i was actually speaking to my friend about downloading historical options data (15mins data infact, not the daily OHLC). It is interesting that I came across this article today. I have not searched for this online, may be the Google/ bixby has overheared my discussion and its AI ML has suggested me this article that too in the home page of browser :). I used to crib about AI ML listening our conversations and pushing marketing info to us. This time it has helped me .. thanks Sir, I will be downloading options data now . Incase if there is a method to download 15 mins OHLC of all 15 years data, please let me know
Any source for minute level data pre 2021 ?