blog /
NIFTY and SENSEX expiry days: the calendar changes your backtest has to respect
NIFTY weeklies moved from Thursday to Tuesday, SENSEX went Friday, Tuesday, then Thursday, and holidays pull expiries forward. Read the expiry from the data instead of guessing the weekday.
An expiry-day backtest is only as good as its list of expiry days. Hard-coding "every Thursday" was close enough a few years ago. It is now wrong for most of the history you are likely to test, and the errors are not random: a strategy that thinks it is trading the last session of a series may actually be holding a contract with a week left.
What changed, and when
- NIFTY weekly and monthly options expired on Thursdays for years. From September 2025 they expire on Tuesdays, with the monthly contract on the last Tuesday of the month.
- SENSEX weekly options were relaunched in 2023 with a Friday expiry, moved to Tuesday at the start of 2025, and moved again to Thursday from September 2025.
- One weekly index per exchange. Since late November 2024 each exchange lists weekly options on a single benchmark index.
- Holidays. When the scheduled expiry falls on a market holiday, the contract expires on the previous trading day.
A live example from the free sample
Our free sample day is Tuesday 14 October 2025, a NIFTY weekly expiry. It includes the expiring series and the next four. Their expiry dates are 14 October, 20 October, 28 October, 4 November and 11 November. Four of those are Tuesdays. The 20th is a Monday, because Tuesday 21 October 2025 was the Diwali holiday. A rule-based calendar would have filed that contract under the wrong day.
Let the files tell you
Every option file carries its expiry twice: in the file name and in a column. So you never need a calendar. An expiry session is any file whose trade date equals its expiry date:
import re
from pathlib import Path
pat = re.compile(r"(?P<sym>[A-Z]+)_OPT_1min_(?P<day>\d{4}-\d{2}-\d{2})_exp(?P<exp>\d{4}-\d{2}-\d{2})")
expiry_files = []
for f in Path("NIFTY").rglob("*_OPT_1min_*.parquet"):
m = pat.search(f.name)
if m and m["day"] == m["exp"]:
expiry_files.append(f)
print(len(expiry_files), "expiry sessions")
Group the result by weekday and you will see the Thursday-to-Tuesday switch in your own data, plus the odd Monday or Wednesday where a holiday moved the expiry.
Why it matters for results
- Days-to-expiry features. Many expiry strategies key off "0 DTE" or "1 DTE". Compute DTE from the expiry column, not from the weekday, or holiday weeks become noise.
- Comparing NIFTY and SENSEX. Before September 2025 the two had different expiry weekdays, so a "same day" comparison was really a comparison of two different points in each index's cycle.
- Regime splits. If your edge depends on expiry-day flows, test it separately before and after each change. Liquidity and the mix of participants shifted with every move.
Our packs cover every trading day in their window, so each of these regimes is inside the files. The calendar does not need to be in your code.