Indian Options Archive

Guides /

Building a continuous NIFTY futures series from monthly contracts

How to stitch near, next and far month NIFTY futures into one continuous 1-minute series: choosing the roll day, back-adjusting the gaps, and checking the result.

Futures data arrives one contract at a time. On 14 October 2025, our free sample day, three NIFTY futures traded: the October contract expiring on 28 October, November and December. At the close they were at 25,185.50, 25,314.20 and 25,485.00, while the index closed at 25,123.35. Charting "NIFTY futures" means choosing which of those to follow on each day, and what to do at the joins.

Step 1: pick a contract for each day

The near month usually has almost all the volume. On the sample day it traded 6.6 million units against 0.53 million in November and 0.16 million in December. Two common roll rules:

import re
from pathlib import Path
import pandas as pd

FUT = re.compile(r"NIFTY_FUT_1min_(\d{4}-\d{2}-\d{2})_exp(\d{4}-\d{2}-\d{2})")
rows = []
for f in Path("data").rglob("NIFTY_FUT_1min_*.parquet"):
    m = FUT.search(f.name)
    v = pd.read_parquet(f, columns=["volume"])["volume"].sum()
    rows.append({"day": m[1], "expiry": m[2], "file": f, "volume": v})
fut = pd.DataFrame(rows).sort_values(["day", "expiry"])

# volume rule: each day, the contract with the most volume
pick = fut.loc[fut.groupby("day")["volume"].idxmax()].set_index("day")

Make the choice one-directional in practice: once you have rolled to a later expiry, do not roll back if the old contract has one busier day.

Step 2: join and back-adjust

On a roll day the new contract trades at a different price from the old one, mostly because of carry. Joining them raw leaves a jump that never happened in the market. Back-adjustment shifts all earlier history by the gap at each roll, so returns stay correct and the latest prices are real.

def load(f):
    d = pd.read_parquet(f)
    ts = "datetime" if "datetime" in d else "timestamp"
    return d.set_index(ts)[["open", "high", "low", "close", "volume"]]

parts = []
for day, r in pick.iterrows():
    d = load(r.file)
    d["expiry"] = r.expiry
    parts.append(d)
cont = pd.concat(parts).sort_index()

# gap at each roll = first close of new contract minus same-minute close of old one
rolls = cont["expiry"].ne(cont["expiry"].shift())
adj = pd.Series(0.0, index=cont.index)
for t in cont.index[rolls][1:]:
    day = t.strftime("%Y-%m-%d")
    old_exp = cont.loc[:t, "expiry"].iloc[-2]
    old = fut[(fut.day == day) & (fut.expiry == old_exp)]
    if old.empty:
        continue
    old_close = load(old.file.iloc[0])["close"].asof(t)
    gap = cont.at[t, "close"] - old_close
    adj[adj.index < t] += gap
for col in ["open", "high", "low", "close"]:
    cont[col + "_adj"] = cont[col] + adj

Step 3: check it

Data for this guide

Or start with the free samples.

More guides