Indian Options Archive

Guides /

Backtesting a NIFTY options strategy in pandas with 1-minute data

A complete, small backtest of an intraday short straddle on NIFTY 1-minute option data: loading files, choosing the strike, entry, exit, costs and a results table.

This guide builds a working intraday backtest from raw option files. The strategy is deliberately simple, a short at-the-money straddle opened in the morning and closed in the afternoon, because the point is the plumbing: getting the right strike at the right minute, without looking ahead, and accounting for costs.

The rules

Loading one day

Each pack has one option file per day per expiry, plus a daily index file. The nearest expiry for a day is simply the smallest expiry date among that day's option files.

import re
from pathlib import Path
import pandas as pd

ROOT = Path("data")
OPT = re.compile(r"NIFTY_OPT_1min_(\d{4}-\d{2}-\d{2})_exp(\d{4}-\d{2}-\d{2})")

def day_files(day):
    hits = [(m[2], f) for f in ROOT.rglob(f"NIFTY_OPT_1min_{day}_exp*.parquet")
            if (m := OPT.search(f.name))]
    return sorted(hits)[0][1]            # nearest expiry

def load_options(f):
    d = pd.read_parquet(f).rename(columns={
        "datetime": "ts", "timestamp": "ts", "strike_price": "strike",
        "right": "cp", "option_type": "cp"})
    d["cp"] = d["cp"].astype(str).str[0].str.upper()   # C or P
    return d.set_index("ts")

def load_index(day):
    f = next(ROOT.rglob(f"NIFTY_SPOT_1min_{day}.parquet"))
    d = pd.read_parquet(f)
    ts = "timestamp" if "timestamp" in d else "datetime"
    return d.set_index(ts)["close"]

One trade

def trade(day, t_in="09:20", t_out="15:15", cost_per_leg=1.0):
    spot = load_index(day)
    opt = load_options(day_files(day))
    t0, t1 = pd.Timestamp(f"{day} {t_in}"), pd.Timestamp(f"{day} {t_out}")
    k = round(spot.loc[t0] / 50) * 50
    leg = lambda cp, t: opt[(opt.strike == k) & (opt.cp == cp)]["close"].asof(t)
    sold = leg("C", t0) + leg("P", t0)
    bought = leg("C", t1) + leg("P", t1)
    return {"day": day, "strike": k, "sold": sold, "bought": bought,
            "pnl_pts": sold - bought - 4 * cost_per_leg}

asof takes the last close at or before the requested minute, so a strike with no trade in that exact minute still gets the latest known price, never a future one. cost_per_leg is a flat allowance in index points for fees, taxes and slippage on each of the four fills; set it from your own cost sheet.

Many days

days = sorted({OPT.search(f.name)[1] for f in ROOT.rglob("NIFTY_OPT_1min_*.parquet")})
res = pd.DataFrame([trade(d) for d in days]).set_index("day")
print(res["pnl_pts"].describe())
print("win rate", (res["pnl_pts"] > 0).mean())
print("worst day", res["pnl_pts"].idxmin(), res["pnl_pts"].min())

What to add next

Data for this guide

Or start with the free samples.

More guides