Skip to content

Commit 76eb0f7

Browse files
authored
Merge pull request #116 from jugaad-py/fix/issue-105-stock-quote-fno-nextapi
[Fix] Issue #105 - Fix stock_quote_fno() using NSE NextApi endpoint
2 parents 96a603e + e23a70d commit 76eb0f7

8 files changed

Lines changed: 217 additions & 47 deletions

File tree

.github/copilot-instructions.md

Lines changed: 15 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -23,6 +23,20 @@ $ source env/bin/activate
2323
# Running tests
2424
Tests are important while development. Whenever you add/update a functionality, ensure you add tests for the same. We are using `pytest` for testing.
2525

26+
# GitHub CLI Usage
27+
Use GitHub CLI (`gh`) to interact with GitHub for fetching issues, PRs, and project information:
28+
29+
- **Fetch an issue by number**: `gh issue view <issue-number>`
30+
- **List open issues**: `gh issue list --state open`
31+
- **List closed issues**: `gh issue list --state closed`
32+
- **Search issues by label**: `gh issue list --label "<label>"`
33+
- **Fetch a specific PR**: `gh pr view <pr-number>`
34+
- **List open PRs**: `gh pr list --state open`
35+
- **Create a PR**: `gh pr create --title "..." --body "..."`
36+
- **View PR details with comments**: `gh pr view <pr-number> --comments`
37+
38+
Always use `gh` instead of manually browsing GitHub when you need to reference issues or PRs.
39+
2640
# Project structure
2741

2842
- `blog_run_test.sh`: Shell script for running tests in a blog context.
@@ -71,6 +85,7 @@ Tests are important while development. Whenever you add/update a functionality,
7185
1. **Take the requirement from the user**
7286
- Listen carefully and document the requirement clearly
7387
- Ask clarifying questions if ambiguous
88+
- If referring to a GitHub issue/PR, use `gh issue view <number>` or `gh pr view <number>` to fetch full details
7489

7590
2. **Discuss and refine the requirement WITH the user**
7691
- Clarify scope, expected behavior, edge cases

CHANGELOG.md

Lines changed: 21 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -5,6 +5,27 @@ All notable changes to this project will be documented in this file.
55
The format is based on [Keep a Changelog](https://keepachangelog.com/en/1.0.0/),
66
and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0.html).
77

8+
## [0.33.0] - 2026-03-16
9+
10+
### Changed
11+
- **BREAKING CHANGE**: `stock_quote_fno()` now returns NSE NextApi response format (see API_REFERENCE.md)
12+
- **Old format**: Nested structure with `stocks[*].metadata` and `stocks[*].tradeInfo`
13+
- **New format**: Flat array structure with `data[*]` containing all contract info
14+
- Returns all available contracts (futures + all expiries of calls and puts) in single response
15+
- Each contract now includes comprehensive fields: `identifier`, `instrumentType`, `expiryDate`, `optionType`, `strikePrice`, `lastPrice`, `openInterest`, etc.
16+
- Response also includes `timestamp` field
17+
18+
### Fixed
19+
- Issue #105: `stock_quote_fno()` returning empty data - NSE deprecated old `/api/quote-derivative` endpoint
20+
- Now uses NSE NextApi endpoint (`getSymbolDerivativesData`) which provides current derivatives data
21+
- Resolves `IndexError: list index out of range` when accessing `stocks[0]`
22+
23+
### Updated
24+
- Enhanced test coverage: `test_stock_quote_fno()` now validates actual data presence, not just structure
25+
- Updated API documentation with new response fields and format
26+
- Updated LIVE_DATA_GUIDE with examples for processing new response format
27+
- Updated QUICKSTART guide with new usage examples
28+
829
## [0.32.0] - 2026-03-16
930

1031
### Added

docs/API_REFERENCE.md

Lines changed: 34 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -134,20 +134,49 @@ Get detailed trade information for a stock.
134134
}
135135
```
136136

137-
#### `stock_quote_fno(symbol)`
138-
Get futures and options quotes for a stock.
137+
#### `stock_quote_fno(symbol)` - [BREAKING CHANGE v0.33]
138+
Get futures and options quotes for a symbol.
139139

140140
**Parameters:**
141-
- `symbol` (str): Stock symbol
141+
- `symbol` (str): Stock or index symbol (e.g., 'RELIANCE', 'NIFTY')
142142

143143
**Returns:**
144144
```python
145145
{
146-
'stocks': list, # List of all F&O contracts
147-
# Each contract contains metadata and market depth
146+
'data': [
147+
{
148+
'identifier': 'FUTSTKRELIANCE30-Mar-2026XX0.00',
149+
'instrumentType': 'FUTSTK', # FUTSTK (Futures) or OPTSTK (Options)
150+
'underlying': 'RELIANCE',
151+
'expiryDate': '30-Mar-2026',
152+
'optionType': 'XX', # XX (Futures), CE (Call), PE (Put)
153+
'strikePrice': '0.00',
154+
'lastPrice': 2850.5,
155+
'change': 15.2,
156+
'pchange': 0.54,
157+
'openPrice': 2835.0,
158+
'highPrice': 2860.0,
159+
'lowPrice': 2820.0,
160+
'closePrice': 2835.3,
161+
'prevClose': 2835.3,
162+
'totalTradedVolume': 45678,
163+
'totalTurnover': 1296789420,
164+
'openInterest': 123456,
165+
'changeinOpenInterest': 1234,
166+
'pchangeinOpenInterest': 1.01,
167+
'underlyingValue': 2850.5,
168+
'volumeFreezeQuantity': 240001,
169+
'ticksize': 0.05,
170+
...other fields
171+
},
172+
...more contracts
173+
],
174+
'timestamp': '16-Mar-2026 15:30:00'
148175
}
149176
```
150177

178+
**Note:** Response includes all available contracts (futures + all expiries of calls and puts) for the given symbol.
179+
151180
### Option Chains
152181

153182
#### `index_option_chain(symbol, expiry_date=None)`

docs/LIVE_DATA_GUIDE.md

Lines changed: 65 additions & 31 deletions
Original file line numberDiff line numberDiff line change
@@ -387,45 +387,79 @@ contract = turnover['value'][0]
387387
### Futures and Options Quote for Stock
388388

389389
```python
390-
# Get all F&O contracts for a stock
391-
quotes = n.stock_quote_fno("HDFC")
390+
# Get all F&O contracts for a symbol (includes all expiries and strikes)
391+
fno_data = n.stock_quote_fno("RELIANCE")
392392

393-
# List all contracts with prices
394-
for quote in quotes['stocks']:
395-
identifier = quote['metadata']['identifier']
396-
last_price = quote['metadata']['lastPrice']
397-
print(f"{identifier}: {last_price}")
393+
# Response structure
394+
{
395+
'data': [
396+
{
397+
'identifier': 'FUTSTKRELIANCE30-Mar-2026XX0.00',
398+
'instrumentType': 'FUTSTK', # or 'OPTSTK'
399+
'underlying': 'RELIANCE',
400+
'expiryDate': '30-Mar-2026',
401+
'optionType': 'XX', # 'XX' for Futures, 'CE' or 'PE' for Options
402+
'strikePrice': '0.00',
403+
'lastPrice': 2850.5,
404+
'openPrice': 2835.0,
405+
'highPrice': 2860.0,
406+
'lowPrice': 2820.0,
407+
'closePrice': 2835.3,
408+
'prevClose': 2835.3,
409+
'change': 15.2,
410+
'pchange': 0.54,
411+
'openInterest': 123456,
412+
'changeinOpenInterest': 1234,
413+
'totalTradedVolume': 45678,
414+
'totalTurnover': 1296789420,
415+
'underlyingValue': 2850.5,
416+
'volumeFreezeQuantity': 240001,
417+
'ticksize': 0.05,
418+
},
419+
...more contracts
420+
],
421+
'timestamp': '16-Mar-2026 15:30:00'
422+
}
398423
```
399424

400-
### Contract Details
425+
### Processing Contract Data
401426

402427
```python
403-
contract = quotes['stocks'][0]
428+
# Iterate through all contracts
429+
fno_data = n.stock_quote_fno("RELIANCE")
404430

405-
# Metadata
406-
metadata = contract['metadata']
407-
{
408-
'instrumentType': 'Stock Futures',
409-
'expiryDate': '28-Jan-2021',
410-
'optionType': '-',
411-
'strikePrice': 0,
412-
'identifier': 'FUTSTKHDFC28-01-2021XX0.00',
413-
'lastPrice': 2565.85,
414-
'change': -29.35,
415-
'pChange': -1.13,
416-
'numberOfContractsTraded': 26898,
417-
'totalTurnover': 211855.64
418-
}
431+
# Filter futures only
432+
futures = [c for c in fno_data['data'] if c['instrumentType'] == 'FUTSTK']
419433

420-
# Order book (market depth)
421-
order_book = contract['marketDeptOrderBook']
434+
# Filter call options with 30-Mar expiry
435+
calls_mar = [c for c in fno_data['data']
436+
if c['instrumentType'] == 'OPTSTK'
437+
and c['optionType'] == 'CE'
438+
and c['expiryDate'] == '30-Mar-2026']
422439

423-
# Trade info
424-
trade_info = contract['tradeInfo']
425-
print(f"Open Interest: {trade_info['openInterest']}")
426-
print(f"Change in OI: {trade_info['changeinOpenInterest']}")
427-
print(f"Volatility (Daily): {contract['otherInfo']['dailyvolatility']}")
428-
print(f"Volatility (Annualized): {contract['otherInfo']['annualisedVolatility']}")
440+
# Get highest IV call option
441+
calls_mar_sorted = sorted(calls_mar, key=lambda x: x['lastPrice'], reverse=True)
442+
for call in calls_mar_sorted[:5]:
443+
print(f"Strike: {call['strikePrice']:8} | Price: {call['lastPrice']:8.2f} | OI: {call['openInterest']}")
444+
```
445+
446+
### Working with Specific Contracts
447+
448+
```python
449+
# Get a specific contract
450+
contract = fno_data['data'][0]
451+
452+
# Extract key information
453+
identifier = contract['identifier']
454+
price = contract['lastPrice']
455+
oi = contract['openInterest']
456+
volume = contract['totalTradedVolume']
457+
iv = contract.get('impliedVolatility', 'N/A') # If available
458+
459+
print(f"Contract: {identifier}")
460+
print(f"Current Price: {price}")
461+
print(f"Open Interest: {oi}")
462+
print(f"Volume: {volume}")
429463
```
430464

431465
---

docs/QUICKSTART.md

Lines changed: 14 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -114,10 +114,20 @@ from jugaad_data.nse import NSELive
114114

115115
n = NSELive()
116116

117-
# Stock futures and options
118-
quotes = n.stock_quote_fno("HDFC")
119-
for quote in quotes['stocks']:
120-
print(f"{quote['metadata']['identifier']}\t{quote['metadata']['lastPrice']}")
117+
# Stock/Index futures and options data
118+
fno_data = n.stock_quote_fno("RELIANCE")
119+
120+
# Process all contracts
121+
for contract in fno_data['data']:
122+
identifier = contract['identifier']
123+
price = contract['lastPrice']
124+
oi = contract['openInterest']
125+
print(f"{identifier:40} | Price: {price:8.2f} | OI: {oi}")
126+
127+
# Filter only call options
128+
calls = [c for c in fno_data['data']
129+
if c['instrumentType'] == 'OPTSTK' and c['optionType'] == 'CE']
130+
print(f"Total Call Contracts: {len(calls)}")
121131

122132
# Equity derivative turnover
123133
turnover = n.eq_derivative_turnover()

jugaad_data/nse/live.py

Lines changed: 51 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -7,11 +7,13 @@
77
class NSELive:
88
time_out = 5
99
base_url = "https://www.nseindia.com/api"
10+
nextapi_url = "https://www.nseindia.com/api/NextApi/apiClient/GetQuoteApi"
1011
page_url = "https://www.nseindia.com/get-quotes/equity?symbol=LT"
1112
_routes = {
1213
"stock_meta": "/equity-meta-info",
1314
"stock_quote": "/quote-equity",
1415
"stock_derivative_quote": "/quote-derivative",
16+
"stock_derivatives_data": "/NextApi/apiClient/GetQuoteApi",
1517
"market_status": "/marketStatus",
1618
"chart_data": "/chart-databyindex",
1719
"market_turnover": "/market-turnover",
@@ -56,15 +58,62 @@ def get(self, route, payload={}):
5658
r = self.s.get(url, params=payload)
5759
return r.json()
5860

61+
def _get_nextapi(self, function_name, **params):
62+
"""Call NextApi endpoint with functionName and additional parameters.
63+
64+
Args:
65+
function_name: The functionName parameter for NextApi
66+
**params: Additional query parameters
67+
68+
Returns:
69+
Parsed JSON response from NextApi
70+
"""
71+
query_params = {"functionName": function_name}
72+
query_params.update(params)
73+
r = self.s.get(self.nextapi_url, params=query_params)
74+
return r.json()
75+
5976
@live_cache
6077
def stock_quote(self, symbol):
6178
data = {"symbol": symbol}
6279
return self.get("stock_quote", data)
6380

6481
@live_cache
6582
def stock_quote_fno(self, symbol):
66-
data = {"symbol": symbol}
67-
return self.get("stock_derivative_quote", data)
83+
"""Fetch live derivatives (futures & options) data for a symbol.
84+
85+
Args:
86+
symbol: Stock/Index symbol (e.g., 'HDFC', 'NIFTY')
87+
88+
Returns:
89+
Dictionary with derivatives data:
90+
{
91+
"data": [
92+
{
93+
"identifier": "OPTSTKHDFC30-Mar-2026CE2500.00",
94+
"instrumentType": "OPTSTK", # OPTSTK or FUTSTK
95+
"underlying": "HDFC",
96+
"expiryDate": "30-Mar-2026",
97+
"optionType": "CE", # CE, PE, or XX for futures
98+
"strikePrice": "2500.00",
99+
"lastPrice": 125.5,
100+
"openInterest": 1234,
101+
"totalTradedVolume": 5000,
102+
"openPrice": 120.0,
103+
"highPrice": 130.0,
104+
"lowPrice": 119.5,
105+
"changeInOpenInterest": 100,
106+
...more fields
107+
},
108+
...more contracts
109+
],
110+
"timestamp": "..."
111+
}
112+
113+
Note: This uses the NSE NextApi endpoint (getSymbolDerivativesData).
114+
Returns all available contracts for the given symbol.
115+
"""
116+
return self._get_nextapi("getSymbolDerivativesData", symbol=symbol)
68117

69118
@live_cache
70119
def trade_info(self, symbol):

pyproject.toml

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -4,7 +4,7 @@ build-backend = "setuptools.build_meta"
44

55
[project]
66
name = "jugaad-data"
7-
version = "0.32.0"
7+
version = "0.33.0"
88
requires-python = ">= 3.9"
99
authors = [{name = "jugaad-coder", email = "abc@xyz.com"}]
1010
description = "Free Zerodha API python library"

tests/test_nse_live.py

Lines changed: 16 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -6,10 +6,22 @@ def test_stock_quote():
66
assert r['info']['symbol'] == 'HDFC'
77

88
def test_stock_quote_fno():
9-
r = n.stock_quote_fno("HDFC")
10-
assert 'strikePrices' in r
11-
assert 'info' in r
12-
assert 'stocks' in r
9+
# Use a symbol with active derivatives (NIFTY or RELIANCE)
10+
r = n.stock_quote_fno("RELIANCE")
11+
# Validate response structure
12+
assert 'data' in r, "Response should have 'data' key"
13+
assert 'timestamp' in r, "Response should have 'timestamp' key"
14+
# Validate data is not empty
15+
assert len(r['data']) > 0, "Derivatives data should not be empty"
16+
17+
# Validate first contract structure
18+
contract = r['data'][0]
19+
assert 'identifier' in contract
20+
assert 'instrumentType' in contract
21+
assert 'underlying' in contract
22+
assert contract['underlying'] == 'RELIANCE', "Underlying symbol should match requested symbol"
23+
assert 'expiryDate' in contract
24+
assert 'lastPrice' in contract or contract.get('lastPrice') is not None
1325

1426
def test_trade_info():
1527
r = n.trade_info("HDFC")

0 commit comments

Comments
 (0)