Fixing Unofficed

if ! pgrep -f ‘discordbridge.py’
then
nohup /usr/bin/python3 -u /root/apps/bridge/discordbridge.py > /root/apps/bridge/discordbridge.out &
fi

echo ‘slackbridge’
if ! pgrep -f ‘slackbridge.py’
then
nohup /usr/bin/python3 -u /root/apps/bridge/slackbridge.py > /root/apps/bridge/slackbridge.out &
fi

Lets talk about what these two does.

Discord bridge takes care of the content that is posted in Discord.
Slack bridge does the same.

It checks various intent and throws the content with various platform like FB, Twitter, Telegram Channel and all.
Now my VA has screwed up amazingly by fucking up telebridge which used to moderate the Telegram Integrations.

Mostly also I intended to keep another chat solution apart from slack where the entire message flow will be preserved but that was done with mattermost. But let me try to do that in rocketchat.

After long time, I allocated time in Unofficed. So feel free to tell if you want to see any change. It will act as a log. Will update shortly.

Maintaining a live trading tool means staying on your toes, especially when the upstream data provider changes their backend. Recently, the maxpain.py module on Unofficed broke down. The culprit? The oi_chain_builder function was failing because its core dependency, nse_optionchain_scrapper(symbol), was trying to hit an obsolete NSE API endpoint.

While digging into the backend to fix this, I also discovered (and patched) an unrelated SEO hack stemming from an un-updated plugin backdoor. Here is a technical breakdown of how I resolved the NSE API changes and secured the site.

The Problem: The NSE API Overhaul

Historically, fetching option chain data from the NSE involved checking if a symbol was an index or an equity, and then routing the request accordingly:

The Old Way (Now Broken)

def nse_optionchain_scrapper(symbol):
symbol = nsesymbolpurify(symbol)
if any(x in symbol for x in indices):
payload = nsefetch('https://www.nseindia.com/api/option-chain-indices?symbol='+symbol)
else:
payload = nsefetch('https://www.nseindia.com/api/option-chain-equities?symbol='+symbol)
return payload


NSE has now unified these endpoints under a new NextApi structure. However, the new API endpoint for the option chain (getOptionChainData) requires a specific expiryDate parameter to function. We can’t just pull the whole board at once anymore; we have to know the exact expiry dates first.

Step 1: Fixing expiry_list()

To feed the new option chain scraper, I first had to update the expiry_list() function to hit the new dropdown API endpoint:

nseindia.com/api/NextApi/apiClient/GetQuoteApi?functionName=getOptionChainDropdown&symbol=SYMBOL

This endpoint returns a clean JSON response containing arrays for both expiryDates and strikePrice. Here is the updated function to parse the new structure while maintaining backward compatibility with the old expected output (either a Pandas DataFrame or a Python list):

def expiry_list(symbol, output_format=“list”):
symbol = nsesymbolpurify(symbol)
url = f"https://www.nseindia.com/api/NextApi/apiClient/GetQuoteApi?functionName=getOptionChainDropdown&symbol={symbol}"

# Fetch JSON using the existing nsefetch wrapper
payload = nsefetch(url)
expiries = payload.get("expiryDates", [])

if output_format == "list":
    return expiries
else:
    import pandas as pd
    return pd.DataFrame({"Date": expiries})

Step 2: Refactoring nse_optionchain_scrapper()

With expiry_list() working again, I could refactor the main scraper. The new approach drops the clunky if/else logic for indices vs. equities. Instead, it dynamically fetches the available expiry dates and passes the nearest expiry (or a specified one) directly into the new endpoint.

Python

def nse_optionchain_scrapper(symbol, expiry_index=0):
    symbol = nsesymbolpurify(symbol)
    
    # 1. Fetch available expiries using our updated function
    expiries = expiry_list(symbol, output_format="list")
    
    if not expiries:
        return None # Handle edge case where no data is returned
        
    # 2. Select the target expiry (defaults to the current/nearest expiry)
    target_expiry = expiries[expiry_index]
    
    # 3. Hit the new unified API endpoint
    url = f"https://www.nseindia.com/api/NextApi/apiClient/GetQuoteApi?functionName=getOptionChainData&symbol={symbol}&params=expiryDate={target_expiry}"
    
    payload = nsefetch(url)
    return payload

Side Quest: Plugging a Security Hole

While deploying these fixes, I noticed some anomalies in the site traffic and structure. A deep dive revealed that Unofficed had fallen victim to an SEO hack. An outdated plugin had a backdoor vulnerability, allowing malicious actors to inject hidden spam links into the site to siphon our domain authority.

I’ve since purged the malicious files, patched the backdoor, updated all core plugins, and hardened the site’s security headers to prevent a recurrence.

Rebuilding oi_chain_builder and Remapping NSE API Keys

With the base scraper functioning, the next step was fixing oi_chain_builder(symbol, expiry="latest", oi_mode="full"). This function outputs the final 23-column DataFrame, Last Traded Price (LTP), and timestamp (crontime). Post-API shift, it was completely failing due to unhandled expiry parameters and a major overhaul of the NSE’s JSON keys.

1. Dynamic Routing for “latest” Expiry

The new NSE endpoint mandates an explicit expiry date string for every request; it no longer automatically defaults to the near-month if the parameter is omitted. To restore functionality for calls where expiry="latest", I updated the logic to dynamically pull the nearest date using the newly fixed expiry_list function:

Python

if expiry == "latest":
    latest_expiry = expiry_list_df.iloc[0,0]
    # Pass latest_expiry to the scraper

2. Patching the Zero-Fill Bug in /root/apps/nseworks/rahu.py

Even with a valid expiry date, the script was returning a DataFrame populated entirely with 0.0.

The oi_chain_builder initializes row data with zeros and updates them by extracting data from the CE (Call) and PE (Put) objects in the API response. I identified that the NSE had completely renamed their market depth keys and enforced strict case sensitivity for others (like lastPrice and pchange). The legacy script was looking for keys that no longer existed.

I updated the extraction logic in /root/apps/nseworks/rahu.py to properly map to the new JSON structure:

Fixed Mapping Logic:

  • openInterestopenInterest

  • changeinOpenInterestchangeinOpenInterest

  • totalTradedVolumetotalTradedVolume

  • impliedVolatilityimpliedVolatility

  • lastPricelastPrice (Ensured correct case sensitivity)

  • changechange

  • bidpricebuyPrice1

  • bidQtybuyQuantity1

  • askPricesellPrice1

  • askQtysellQuantity1

Aligning the parser with the new buyPrice1/sellPrice1 nomenclature instantly fixed the zero-drops, restoring the full multi-column OI output.

try:
logging.info(“Fetching F&O stock data from NSE…”)
secload = nsefetch(‘https://www.nseindia.com/api/equity-stockIndices?index=SECURITIES%20IN%20F%26O’)
secdata = pd.DataFrame(secload[‘data’])[
[‘change’, ‘chart30dPath’, ‘chart365dPath’, ‘chartTodayPath’, ‘date30dAgo’, ‘date365dAgo’,
‘dayHigh’, ‘dayLow’, ‘identifier’, ‘lastPrice’, ‘lastUpdateTime’, ‘meta’, ‘nearWKH’, ‘nearWKL’,
‘open’, ‘pChange’, ‘perChange30d’, ‘perChange365d’, ‘previousClose’, ‘series’, ‘symbol’,
‘totalTradedValue’, ‘totalTradedVolume’, ‘yearHigh’, ‘yearLow’]
]
gsync(‘1WJ33VAIVUctsUG9MnVPThOv72IkL_w9Bwn78QiYbapY’, ‘Sheet14’, secdata)
logging.info(“Stock metadata synced to GSheet Sheet14.”)
except Exception as e:
logging.critical(“Failed to fetch or sync F&O metadata.”)
logging.critical(traceback.format_exc())

As I tried to run stockpain.py it uses stockpain() function from rahubolt. it is throwing error. this part i mean i think you need to fix it as i think some structure change has happened.

I’ve identified that the column lastUpdateTime is missing from the API response, which is causing the KeyError in stockpain(). I’ll update the
function in /root/apps/nseworks/rahubolt.py to remove this column from the selection list. I’ll also add a .get() or reindex() step to ensure
the function remains robust against future column changes.

Taking about FNO list. So be caustious.

Summary

I identified and fixed a long-standing limitation in the nsepython derivative fetching logic.

The library has now been migrated from the restrictive getOptionChainData API to the comprehensive **getSymbolDerivativesData API`.

This change resolves issues with multi-expiry calculations (for example pcr(payload, 1) returning zero) and significantly improves both data availability and performance.


The Problem: “The Expiry Wall”

Previously, nse_optionchain_scrapper(symbol) was hardcoded to fetch only the nearest expiry by default.

API Used

getOptionChainData

Limitation

The API returns data for only one expiry date at a time.

Result

Functions such as:

pcr(payload, 1)
nse_optionchain_ltp(payload, strike, type, 1)

would fail because the second expiry data did not exist in the payload.

To analyze multiple expiries (for example weekly vs monthly), developers had to make multiple network calls, which:

  • slowed execution

  • increased the risk of NSE rate limiting


The Solution: “God Mode” API Integration

I rewrote nse_optionchain_scrapper to use the getSymbolDerivativesData endpoint.


1. Unified Fetching

A single network call now retrieves the entire derivative universe for a symbol in one JSON response.

This includes:

  • All Strike Prices

  • All Expiry Dates (Weekly, Monthly, Yearly)

  • All Option Types (CE / PE)

  • All Future Contracts


2. Intelligent Transformation Layer

To ensure all existing strategies continue to work without modification (such as art_main.py, heatmap.py, maxpain.py), I implemented a Normalization Bridge.

This bridge converts the raw flat response from NSE into the legacy option chain structure expected by existing functions.

# New payload structure (compatible with legacy functions)

payload = {
    "records": {
        "data": [ ... combined CE/PE objects ... ],
        "expiryDates": [ ... sorted list of all expiries ... ],
        "timestamp": "...",
        "underlyingValue": 22500.0
    }
}

This ensures no breaking changes for existing strategy scripts.


New Function: nse_quote_derivatives

This function is now the recommended method for developers who want raw access to all derivative contracts.


Function Signature

nse_quote_derivatives(symbol)

Parameters

Parameter Type Description
symbol str FNO symbol such as "NIFTY", "RELIANCE", "INFY"

Returns

A dictionary containing:

Key Description
data List of dictionaries representing derivative contracts
timestamp Last update time from NSE

Example Usage

from rahu import nse_quote_derivatives

# Fetch every derivative contract for NIFTY
raw_payload = nse_quote_derivatives("NIFTY")

# Filter all Call Options for a specific expiry
nifty_calls = [
    d for d in raw_payload["data"]
    if d["expiryDate"] == "10-Mar-2026" and d["optionType"] == "CE"
]

print(f"Total Call Strikes found: {len(nifty_calls)}")

Verification and Benchmarks

Tests were performed inside the pinakin Docker container to ensure environment consistency.


NIFTY Multi-Expiry PCR Test

Expiry Index Expiry Date Previous Result New Result Status
inp=0 (Nearest) 10-Mar-2026 0.6767 0.6767 Stable
inp=1 (Next) 17-Mar-2026 0.0000 0.6099 Fixed

Legacy Function Compatibility

Core legacy functions now correctly retrieve data for non-zero expiry indices.

Test

nse_optionchain_ltp(payload, 24250, "CE", 1)

Result

513.85

This confirms that the function correctly retrieves LTP from the second expiry.

Status: Fully compatible.