NIFTY Open Interest

Overview

This python code fetches and visualizes Open Interest (OI) profile for NIFTY options, helping traders identify support and resistance levels based on options market data.

Features

  • Real-time OI data for Call and Put options

  • Automatic ATM strike calculation

  • Batch processing with rate limiting

  • Interactive Plotly visualization

  • Error handling with retry logic

Full Python Code

"""
NIFTY 31 JUL 2025 – OI profile (10-req/s batches)
Author  : OpenAlgo GPT
Updated : 2025-06-28
"""

print("🔁 OpenAlgo Python Bot is running.")                     # rule 13

import os, sys, re, time, asyncio, pandas as pd, plotly.graph_objects as go
from datetime import datetime
from openalgo import api

# ───────────── CONFIG (edit to suit) ─────────────────────────────────────
API_KEY  = os.getenv("OPENALGO_API_KEY",  "openalgo-api-key")
API_HOST = os.getenv("OPENALGO_API_HOST", "http://127.0.0.1:5000")

EXPIRY        = "31JUL25"      # ✅ option expiry
RADIUS        = 20             # ± strikes
STEP          = 100            # ✅ 100-pt strikes (was 50)
BATCH_SIZE    = 10             # ✅ broker cap per second
BATCH_PAUSE   = 2           # seconds to wait between batches
MAX_RETRIES   = 1              # one retry on 429 / timeout
BACKOFF_SEC   = 1.2

client = api(api_key=API_KEY, host=API_HOST)
if sys.platform.startswith("win"):
    asyncio.set_event_loop_policy(asyncio.WindowsSelectorEventLoopPolicy())

# ───────────── Helpers ───────────────────────────────────────────────────
def get_atm_strike(step: int = STEP) -> int:
    q = client.quotes(symbol="NIFTY", exchange="NSE_INDEX")
    print("Underlying Quote :", q)                               # rule 14
    return int(round(q["data"]["ltp"] / step) * step)

_sym_rx = re.compile(r"^[A-Z]+(\d{2}[A-Z]{3}\d{2})(\d+)(CE|PE)$")
def parse_symbol(sym: str):
    m = _sym_rx.match(sym)
    return (int(m.group(2)), m.group(3)) if m else None

def fetch_sync(sym: str) -> dict | None:
    """Blocking quote call with a retry for 429 / timeout."""
    for attempt in range(MAX_RETRIES + 1):
        q = client.quotes(symbol=sym, exchange="NFO")
        print(sym, "→", q)                                       # rule 14
        if q.get("status") == "success":
            strike, opt = parse_symbol(sym)
            if strike is None:
                print("⚠️  Bad symbol", sym)
                return None
            return dict(strike=strike, type=opt,
                        oi=q["data"]["oi"], ltp=q["data"]["ltp"])
        if (q.get("code") == 429 or q.get("error_type") == "timeout_error") and attempt < MAX_RETRIES:
            time.sleep(BACKOFF_SEC)
    return None

# ───────────── Batch-paced gather (≈5 req/s) ─────────────────────────────
async def gather_df() -> pd.DataFrame:
    atm = get_atm_strike()
    strikes = [atm + i*STEP for i in range(-RADIUS, RADIUS + 1)]
    symbols = [f"NIFTY{EXPIRY}{k}{s}" for k in strikes for s in ("CE", "PE")]

    rows: list[dict] = []
    for i in range(0, len(symbols), BATCH_SIZE):
        batch = symbols[i:i+BATCH_SIZE]
        res   = await asyncio.gather(*[asyncio.to_thread(fetch_sync, s) for s in batch])
        rows.extend(r for r in res if r)
        if i + BATCH_SIZE < len(symbols):
            await asyncio.sleep(BATCH_PAUSE)          # pace → 5 req/s

    if not rows:
        raise RuntimeError("All quotes failed – check API / symbols.")
    return pd.DataFrame(rows)

# ───────────── Plot (Call = green, Put = red, both positive) ─────────────
def plot_oi(df: pd.DataFrame):
    piv = (df.pivot(index="strike", columns="type", values=["oi", "ltp"])
             .sort_index())
    piv.columns = ["CE_OI", "PE_OI", "CE_LTP", "PE_LTP"]
    piv = piv.reset_index()

    fig = go.Figure()
    fig.add_bar(
        x=piv["strike"], y=piv["CE_OI"],          # no minus sign
        name="Call OI", marker_color="seagreen",  # ✅ green
        customdata=piv[["CE_OI", "CE_LTP"]],
        hovertemplate="<b>%{x} CE</b><br>OI %{customdata[0]:,}"
                      "<br>LTP ₹%{customdata[1]:.2f}<extra></extra>"
    )
    fig.add_bar(
        x=piv["strike"], y=piv["PE_OI"],
        name="Put OI", marker_color="crimson",    # ✅ red
        customdata=piv[["PE_OI", "PE_LTP"]],
        hovertemplate="<b>%{x} PE</b><br>OI %{customdata[0]:,}"
                      "<br>LTP ₹%{customdata[1]:.2f}<extra></extra>"
    )
    atm = get_atm_strike()
    fig.add_vline(
        x=piv.index[piv["strike"] == atm][0],
        line_dash="dash", line_color="gray",
        annotation_text=f"ATM {atm}", annotation_position="top"
    )
    fig.update_layout(
        title=f"NIFTY {datetime.strptime(EXPIRY,'%d%b%y').strftime('%d %b %Y')} – OI Profile",
        xaxis=dict(title="Strike", type="category"),
        yaxis_title="Open Interest",
        bargap=0.05, template="plotly_dark",
        height=500, width=1250,
        legend=dict(orientation="h", yanchor="bottom", y=1.02,
                    xanchor="right", x=1)
    )
    fig.show()

# ───────────── Runner (script / notebook) ────────────────────────────────
async def _main():
    df = await gather_df()
    print(df.head())
    plot_oi(df)

def _in_nb() -> bool:
    try:
        import IPython
        return IPython.get_ipython() is not None
    except ImportError:
        return False

if _in_nb():
    await _main()                   # Jupyter
else:
    asyncio.run(_main())            # script

Full Code Explaination

Configuration

Environment Variables

OPENALGO_API_KEY  = "your_api_key_here"
OPENALGO_API_HOST = "http://127.0.0.1:5000"

Parameters

EXPIRY        = "31JUL25"      # Option expiry date (DDMMMYY format)
RADIUS        = 20             # Number of strikes above/below ATM
STEP          = 100            # Strike price interval
BATCH_SIZE    = 5              # API requests per batch
BATCH_PAUSE   = 1.05           # Seconds between batches
MAX_RETRIES   = 1              # Retry attempts for failed requests
BACKOFF_SEC   = 1.2           # Backoff time for retries

Code Structure

1. ATM Strike Calculation

def get_atm_strike(step: int = STEP) -> int:
    q = client.quotes(symbol="NIFTY", exchange="NSE_INDEX")
    return int(round(q["data"]["ltp"] / step) * step)

Fetches current NIFTY spot price and rounds to nearest strike.

2. Symbol Parsing

_sym_rx = re.compile(r"^[A-Z]+(\d{2}[A-Z]{3}\d{2})(\d+)(CE|PE)$")
def parse_symbol(sym: str):
    m = _sym_rx.match(sym)
    return (int(m.group(2)), m.group(3)) if m else None

Extracts strike price and option type from symbol string.

3. Quote Fetching

def fetch_sync(sym: str) -> dict | None:
    for attempt in range(MAX_RETRIES + 1):
        q = client.quotes(symbol=sym, exchange="NFO")
        if q.get("status") == "success":
            strike, opt = parse_symbol(sym)
            return dict(strike=strike, type=opt,
                        oi=q["data"]["oi"], ltp=q["data"]["ltp"])
        if (q.get("code") == 429 or q.get("error_type") == "timeout_error") and attempt < MAX_RETRIES:
            time.sleep(BACKOFF_SEC)
    return None

Fetches quote data with retry logic for rate limits and timeouts.

4. Batch Data Collection

async def gather_df() -> pd.DataFrame:
    atm = get_atm_strike()
    strikes = [atm + i*STEP for i in range(-RADIUS, RADIUS + 1)]
    symbols = [f"NIFTY{EXPIRY}{k}{s}" for k in strikes for s in ("CE", "PE")]

    rows: list[dict] = []
    for i in range(0, len(symbols), BATCH_SIZE):
        batch = symbols[i:i+BATCH_SIZE]
        res = await asyncio.gather(*[asyncio.to_thread(fetch_sync, s) for s in batch])
        rows.extend(r for r in res if r)
        if i + BATCH_SIZE < len(symbols):
            await asyncio.sleep(BATCH_PAUSE)
    
    return pd.DataFrame(rows)

Processes symbols in batches to respect API rate limits.

5. Visualization

def plot_oi(df: pd.DataFrame):
    piv = df.pivot(index="strike", columns="type", values=["oi", "ltp"]).sort_index()
    piv.columns = ["CE_OI", "PE_OI", "CE_LTP", "PE_LTP"]
    
    fig = go.Figure()
    
    # Call OI - Green bars
    fig.add_bar(
        x=piv["strike"], y=piv["CE_OI"],
        name="Call OI", marker_color="seagreen",
        customdata=piv[["CE_OI", "CE_LTP"]],
        hovertemplate="<b>%{x} CE</b><br>OI %{customdata[0]:,}<br>LTP ₹%{customdata[1]:.2f}"
    )
    
    # Put OI - Red bars
    fig.add_bar(
        x=piv["strike"], y=piv["PE_OI"],
        name="Put OI", marker_color="crimson",
        customdata=piv[["PE_OI", "PE_LTP"]],
        hovertemplate="<b>%{x} PE</b><br>OI %{customdata[0]:,}<br>LTP ₹%{customdata[1]:.2f}"
    )
    
    # ATM line
    atm = get_atm_strike()
    fig.add_vline(
        x=piv.index[piv["strike"] == atm][0],
        line_dash="dash", line_color="gray",
        annotation_text=f"ATM {atm}"
    )
    
    fig.update_layout(
        title=f"NIFTY {datetime.strptime(EXPIRY,'%d%b%y').strftime('%d %b %Y')} – OI Profile",
        xaxis=dict(title="Strike", type="category"),
        yaxis_title="Open Interest",
        bargap=0.05, template="plotly_dark",
        height=500, width=1250
    )
    
    fig.show()

Creates interactive bar chart with hover details.

Usage

In Jupyter Notebook

# Run all cells to generate OI profile
await _main()  # Automatically detected and executed

As Python Script

# Run directly
python NiftyOI.py

Output

The script generates:

  • Interactive Plotly chart

  • Green bars for Call OI

  • Red bars for Put OI

  • Gray dashed line for ATM strike

  • Hover tooltips showing OI and LTP values

Error Handling

  • 429 errors: Automatic retry with backoff

  • Timeout errors: Retry with MAX_RETRIES limit

  • Invalid symbols: Skipped with warning

  • API failures: Graceful degradation

Data Structure

Output DataFrame contains:

  • strike: Strike price

  • type: Option type (CE/PE)

  • oi: Open Interest

  • ltp: Last Traded Price

Last updated