nsepython 2.95__py3-none-any.whl → 2.98__py3-none-any.whl

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
nsepython/rahu.py CHANGED
@@ -1,966 +1,1758 @@
1
- import os,sys
2
- # os.chdir(os.path.dirname(os.path.abspath(__file__)))
3
- # sys.path.insert(1, os.path.join(sys.path[0], '..'))
4
-
5
- import requests
6
- import pandas as pd
7
- import json
8
- import random
9
- import datetime,time
10
- import logging
11
- import re
12
- import urllib.parse
13
-
14
- mode ='local'
15
-
16
- if mode == "vpn":
17
- def nsefetch(payload: str):
18
- def encode(url: str) -> str:
19
- if "%26" in url or "%20" in url:
20
- return url
21
- return urllib.parse.quote(url, safe=":/?&=")
22
-
23
- def refresh_cookies():
24
- os.popen(f'curl -c cookies.txt "https://www.nseindia.com" {curl_headers}').read()
25
- os.popen(f'curl -b cookies.txt -c cookies.txt "https://www.nseindia.com/option-chain" {curl_headers}').read()
26
-
27
- if not os.path.exists("cookies.txt"):
28
- refresh_cookies()
29
-
30
- encoded_url = encode(payload)
31
- cmd = f'curl -b cookies.txt "{encoded_url}" {curl_headers}'
32
- raw = os.popen(cmd).read()
33
-
34
- try:
35
- return json.loads(raw)
36
- except ValueError:
37
- refresh_cookies()
38
- raw = os.popen(cmd).read()
39
- try:
40
- return json.loads(raw)
41
- except ValueError:
42
- return {}
43
-
44
- if(mode=='local'):
45
- def nsefetch(payload):
46
-
47
- try:
48
- s = requests.Session()
49
- s.get("https://www.nseindia.com", headers=headers, timeout=10)
50
- s.get("https://www.nseindia.com/option-chain", headers=headers, timeout=10)
51
- output = s.get(payload, headers=headers, timeout=10).json()
52
- except ValueError:
53
- output = {}
54
- return output
55
-
56
-
57
- # headers = {
58
- # 'Connection': 'keep-alive',
59
- # 'Cache-Control': 'max-age=0',
60
- # 'DNT': '1',
61
- # 'Upgrade-Insecure-Requests': '1',
62
- # 'User-Agent': 'Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/79.0.3945.79 Safari/537.36',
63
- # 'Sec-Fetch-User': '?1',
64
- # 'Accept': 'text/html,application/xhtml+xml,application/xml;q=0.9,image/webp,image/apng,*/*;q=0.8,application/signed-exchange;v=b3;q=0.9',
65
- # 'Sec-Fetch-Site': 'none',
66
- # 'Sec-Fetch-Mode': 'navigate',
67
- # 'Accept-Encoding': 'gzip, deflate, br',
68
- # 'Accept-Language': 'en-US,en;q=0.9,hi;q=0.8',
69
- # }
70
-
71
- #Rahul_Mittal's entry
72
- headers = {
73
- "accept": "text/html,application/xhtml+xml,application/xml;q=0.9,image/avif,image/webp,image/apng,*/*;q=0.8,application/signed-exchange;v=b3;q=0.7",
74
- "accept-language": "en-US,en;q=0.9,en-IN;q=0.8,en-GB;q=0.7",
75
- "cache-control": "max-age=0",
76
- "priority": "u=0, i",
77
- "sec-ch-ua": '"Microsoft Edge";v="129", "Not=A?Brand";v="8", "Chromium";v="129"',
78
- "sec-ch-ua-mobile": "?0",
79
- "sec-ch-ua-platform": '"Windows"',
80
- "sec-fetch-dest": "document",
81
- "sec-fetch-mode": "navigate",
82
- "sec-fetch-site": "none",
83
- "sec-fetch-user": "?1",
84
- "upgrade-insecure-requests": "1",
85
- "user-agent": "Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/129.0.0.0 Safari/537.36 Edg/129.0.0.0"
86
- }
87
-
88
-
89
-
90
- #Curl headers
91
- curl_headers = ''' -H "authority: beta.nseindia.com" -H "cache-control: max-age=0" -H "dnt: 1" -H "upgrade-insecure-requests: 1" -H "user-agent: Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/79.0.3945.117 Safari/537.36" -H "sec-fetch-user: ?1" -H "accept: text/html,application/xhtml+xml,application/xml;q=0.9,image/webp,image/apng,*/*;q=0.8,application/signed-exchange;v=b3;q=0.9" -H "sec-fetch-site: none" -H "sec-fetch-mode: navigate" -H "accept-encoding: gzip, deflate, br" -H "accept-language: en-US,en;q=0.9,hi;q=0.8" --compressed'''
92
-
93
- run_time=datetime.datetime.now()
94
-
95
- #Constants
96
- indices = ['NIFTY','FINNIFTY','BANKNIFTY']
97
-
98
- def running_status():
99
- start_now=datetime.datetime.now().replace(hour=9, minute=15, second=0, microsecond=0)
100
- end_now=datetime.datetime.now().replace(hour=15, minute=30, second=0, microsecond=0)
101
- return start_now<datetime.datetime.now()<end_now
102
-
103
- #Getting FNO Symboles
104
- def fnolist():
105
- # df = pd.read_csv("https://www1.nseindia.com/content/fo/fo_mktlots.csv")
106
- # return [x.strip(' ') for x in df.drop(df.index[3]).iloc[:,1].to_list()]
107
-
108
- positions = nsefetch('https://www.nseindia.com/api/equity-stockIndices?index=SECURITIES%20IN%20F%26O')
109
-
110
- nselist=['NIFTY','NIFTYIT','BANKNIFTY']
111
-
112
- i=0
113
- for x in range(i, len(positions['data'])):
114
- nselist=nselist+[positions['data'][x]['symbol']]
115
-
116
- return nselist
117
-
118
- def nsesymbolpurify(symbol):
119
- symbol = symbol.replace('&','%26') #URL Parse for Stocks Like M&M Finance
120
- return symbol
121
-
122
- def nse_optionchain_scrapper(symbol):
123
- symbol = nsesymbolpurify(symbol)
124
- if any(x in symbol for x in indices):
125
- payload = nsefetch('https://www.nseindia.com/api/option-chain-indices?symbol='+symbol)
126
- else:
127
- payload = nsefetch('https://www.nseindia.com/api/option-chain-equities?symbol='+symbol)
128
- return payload
129
-
130
-
131
- def oi_chain_builder(symbol,expiry="latest",oi_mode="full"):
132
-
133
- payload = nse_optionchain_scrapper(symbol)
134
-
135
- if(oi_mode=='compact'):
136
- col_names = ['CALLS_OI','CALLS_Chng in OI','CALLS_Volume','CALLS_IV','CALLS_LTP','CALLS_Net Chng','Strike Price','PUTS_OI','PUTS_Chng in OI','PUTS_Volume','PUTS_IV','PUTS_LTP','PUTS_Net Chng']
137
- if(oi_mode=='full'):
138
- col_names = ['CALLS_Chart','CALLS_OI','CALLS_Chng in OI','CALLS_Volume','CALLS_IV','CALLS_LTP','CALLS_Net Chng','CALLS_Bid Qty','CALLS_Bid Price','CALLS_Ask Price','CALLS_Ask Qty','Strike Price','PUTS_Bid Qty','PUTS_Bid Price','PUTS_Ask Price','PUTS_Ask Qty','PUTS_Net Chng','PUTS_LTP','PUTS_IV','PUTS_Volume','PUTS_Chng in OI','PUTS_OI','PUTS_Chart']
139
- oi_data = pd.DataFrame(columns = col_names)
140
-
141
- #oi_row = {'CALLS_OI':0, 'CALLS_Chng in OI':0, 'CALLS_Volume':0, 'CALLS_IV':0, 'CALLS_LTP':0, 'CALLS_Net Chng':0, 'Strike Price':0, 'PUTS_OI':0, 'PUTS_Chng in OI':0, 'PUTS_Volume':0, 'PUTS_IV':0, 'PUTS_LTP':0, 'PUTS_Net Chng':0}
142
- oi_row = {'CALLS_OI':0, 'CALLS_Chng in OI':0, 'CALLS_Volume':0, 'CALLS_IV':0, 'CALLS_LTP':0, 'CALLS_Net Chng':0, 'CALLS_Bid Qty':0,'CALLS_Bid Price':0,'CALLS_Ask Price':0,'CALLS_Ask Qty':0,'Strike Price':0, 'PUTS_OI':0, 'PUTS_Chng in OI':0, 'PUTS_Volume':0, 'PUTS_IV':0, 'PUTS_LTP':0, 'PUTS_Net Chng':0,'PUTS_Bid Qty':0,'PUTS_Bid Price':0,'PUTS_Ask Price':0,'PUTS_Ask Qty':0}
143
- if(expiry=="latest"):
144
- expiry = payload['records']['expiryDates'][0]
145
- m=0
146
- for m in range(len(payload['records']['data'])):
147
- if(payload['records']['data'][m]['expiryDate']==expiry):
148
- if(1>0):
149
- try:
150
- oi_row['CALLS_OI']=payload['records']['data'][m]['CE']['openInterest']
151
- oi_row['CALLS_Chng in OI']=payload['records']['data'][m]['CE']['changeinOpenInterest']
152
- oi_row['CALLS_Volume']=payload['records']['data'][m]['CE']['totalTradedVolume']
153
- oi_row['CALLS_IV']=payload['records']['data'][m]['CE']['impliedVolatility']
154
- oi_row['CALLS_LTP']=payload['records']['data'][m]['CE']['lastPrice']
155
- oi_row['CALLS_Net Chng']=payload['records']['data'][m]['CE']['change']
156
- if(oi_mode=='full'):
157
- oi_row['CALLS_Bid Qty']=payload['records']['data'][m]['CE']['bidQty']
158
- oi_row['CALLS_Bid Price']=payload['records']['data'][m]['CE']['bidprice']
159
- oi_row['CALLS_Ask Price']=payload['records']['data'][m]['CE']['askPrice']
160
- oi_row['CALLS_Ask Qty']=payload['records']['data'][m]['CE']['askQty']
161
- except KeyError:
162
- oi_row['CALLS_OI'], oi_row['CALLS_Chng in OI'], oi_row['CALLS_Volume'], oi_row['CALLS_IV'], oi_row['CALLS_LTP'],oi_row['CALLS_Net Chng']=0,0,0,0,0,0
163
- if(oi_mode=='full'):
164
- oi_row['CALLS_Bid Qty'],oi_row['CALLS_Bid Price'],oi_row['CALLS_Ask Price'],oi_row['CALLS_Ask Qty']=0,0,0,0
165
- pass
166
-
167
- oi_row['Strike Price']=payload['records']['data'][m]['strikePrice']
168
-
169
- try:
170
- oi_row['PUTS_OI']=payload['records']['data'][m]['PE']['openInterest']
171
- oi_row['PUTS_Chng in OI']=payload['records']['data'][m]['PE']['changeinOpenInterest']
172
- oi_row['PUTS_Volume']=payload['records']['data'][m]['PE']['totalTradedVolume']
173
- oi_row['PUTS_IV']=payload['records']['data'][m]['PE']['impliedVolatility']
174
- oi_row['PUTS_LTP']=payload['records']['data'][m]['PE']['lastPrice']
175
- oi_row['PUTS_Net Chng']=payload['records']['data'][m]['PE']['change']
176
- if(oi_mode=='full'):
177
- oi_row['PUTS_Bid Qty']=payload['records']['data'][m]['PE']['bidQty']
178
- oi_row['PUTS_Bid Price']=payload['records']['data'][m]['PE']['bidprice']
179
- oi_row['PUTS_Ask Price']=payload['records']['data'][m]['PE']['askPrice']
180
- oi_row['PUTS_Ask Qty']=payload['records']['data'][m]['PE']['askQty']
181
- except KeyError:
182
- oi_row['PUTS_OI'], oi_row['PUTS_Chng in OI'], oi_row['PUTS_Volume'], oi_row['PUTS_IV'], oi_row['PUTS_LTP'],oi_row['PUTS_Net Chng']=0,0,0,0,0,0
183
- if(oi_mode=='full'):
184
- oi_row['PUTS_Bid Qty'],oi_row['PUTS_Bid Price'],oi_row['PUTS_Ask Price'],oi_row['PUTS_Ask Qty']=0,0,0,0
185
- else:
186
- logging.info(m)
187
-
188
- if(oi_mode=='full'):
189
- oi_row['CALLS_Chart'],oi_row['PUTS_Chart']=0,0
190
- #oi_data = oi_data.append(oi_row, ignore_index=True)
191
- #oi_data = pd.concat([oi_data, oi_row], ignore_index=True)
192
- oi_data = pd.concat([oi_data, pd.DataFrame([oi_row])], ignore_index=True)
193
-
194
-
195
-
196
- oi_data['time_stamp']=payload['records']['timestamp']
197
- return oi_data,float(payload['records']['underlyingValue']),payload['records']['timestamp']
198
-
199
-
200
- def nse_quote(symbol,section=""):
201
- #https://forum.unofficed.com/t/nsetools-get-quote-is-not-fetching-delivery-data-and-delivery-can-you-include-this-as-part-of-feature-request/1115/4
202
- symbol = nsesymbolpurify(symbol)
203
-
204
- if(section==""):
205
- if any(x in symbol for x in fnolist()):
206
- payload = nsefetch('https://www.nseindia.com/api/quote-derivative?symbol='+symbol)
207
- else:
208
- payload = nsefetch('https://www.nseindia.com/api/quote-equity?symbol='+symbol)
209
- return payload
210
-
211
- if(section!=""):
212
- payload = nsefetch('https://www.nseindia.com/api/quote-equity?symbol='+symbol+'&section='+section)
213
- return payload
214
-
215
-
216
- def nse_expirydetails(payload,i=0): #Can make problem. Use nse_expirydetails_by_symbol()
217
-
218
- expiry_dates = payload['records']['expiryDates']
219
- expiry_dates = [datetime.datetime.strptime(date, "%d-%b-%Y").date() for date in expiry_dates]
220
- expiry_dates = [date.strftime("%d-%b-%Y") for date in expiry_dates if date >= datetime.datetime.now().date()]
221
- currentExpiry=expiry_dates[i]
222
- currentExpiry = datetime.datetime.strptime(currentExpiry,'%d-%b-%Y').date() # converting json datetime to alice datetime
223
- date_today = run_time.strftime('%Y-%m-%d') # required to remove hh:mm:ss
224
- date_today = datetime.datetime.strptime(date_today,'%Y-%m-%d').date()
225
- dte = (currentExpiry - date_today).days
226
- return currentExpiry,dte
227
-
228
- def pcr(payload,inp='0'):
229
- ce_oi = 0
230
- pe_oi = 0
231
- for i in payload['records']['data']:
232
- if i['expiryDate'] == payload['records']['expiryDates'][inp]:
233
- try:
234
- ce_oi += i['CE']['openInterest']
235
- pe_oi += i['PE']['openInterest']
236
- except KeyError:
237
- pass
238
- return pe_oi / ce_oi
239
-
240
- #forum.unofficed.com/t/unable-to-find-nse-quote-meta-api/702/4
241
- #Refer https://forum.unofficed.com/t/changed-the-nse-quote-ltp-function/1276
242
- def nse_quote_ltp(symbol,expiryDate="latest",optionType="-",strikePrice=0):
243
- payload = nse_quote(symbol)
244
-
245
- meta = "Options"
246
- if(optionType=="Fut"): meta = "Futures"
247
- if(optionType=="PE"):optionType="Put"
248
- if(optionType=="CE"):optionType="Call"
249
-
250
- if(expiryDate=="latest") or (expiryDate=="next"):
251
-
252
- if(meta=="Futures"):
253
- selected_key = next((key for key in payload["expiryDatesByInstrument"] if "futures" in key.lower()), None)
254
- if(meta=="Options"):
255
- selected_key = next((key for key in payload["expiryDatesByInstrument"] if "options" in key.lower()), None)
256
-
257
- expiry_dates=payload["expiryDatesByInstrument"][selected_key]
258
- expiry_dates = [datetime.datetime.strptime(date, "%d-%b-%Y").date() for date in expiry_dates]
259
- expiry_dates = [date.strftime("%d-%b-%Y") for date in expiry_dates if date >= datetime.datetime.now().date()]
260
- if(expiryDate=="latest"): expiryDate=expiry_dates[0]
261
- if(expiryDate=="next"): expiryDate=expiry_dates[1]
262
-
263
-
264
- if(optionType!="-"):
265
- for i in payload['stocks']:
266
- if meta in i['metadata']['instrumentType']:
267
- #print(i['metadata'])
268
- if(optionType=="Fut"):
269
- if(i['metadata']['expiryDate']==expiryDate):
270
- lastPrice = i['metadata']['lastPrice']
271
-
272
- if((optionType=="Put")or(optionType=="Call")):
273
- if (i['metadata']["expiryDate"]==expiryDate):
274
- if (i['metadata']["optionType"]==optionType):
275
- if (i['metadata']["strikePrice"]==strikePrice):
276
- #print(i['metadata'])
277
- lastPrice = i['metadata']['lastPrice']
278
-
279
- if(optionType=="-"):
280
- lastPrice = payload['underlyingValue']
281
-
282
- return lastPrice
283
-
284
- # print(nse_quote_ltp("RELIANCE"))
285
- # print(nse_quote_ltp("RELIANCE","latest","Fut"))
286
- # print(nse_quote_ltp("RELIANCE","next","Fut"))
287
- # print(nse_quote_ltp("BANKNIFTY","latest","PE",32000))
288
- # print(nse_quote_ltp("BANKNIFTY","next","PE",32000))
289
- # print(nse_quote_ltp("BANKNIFTY","10-Jun-2021","PE",32000))
290
- # print(nse_quote_ltp("BANKNIFTY","17-Jun-2021","PE",32000))
291
- # print(nse_quote_ltp("RELIANCE","latest","PE",2300))
292
- # print(nse_quote_ltp("RELIANCE","next","PE",2300))
293
-
294
- def nse_quote_meta(symbol,expiryDate="latest",optionType="-",strikePrice=0):
295
- payload = nse_quote(symbol)
296
- #https://stackoverflow.com/questions/7961363/removing-duplicates-in-lists
297
- #https://stackoverflow.com/questions/19199984/sort-a-list-in-python
298
-
299
- #BankNIFTY and NIFTY has weekly options. Using this Jugaad which has primary base of assumption that Reliance will not step out of FNO.
300
- #forum.unofficed.com/t/unable-to-find-nse-quote-meta-api/702/4
301
- if((symbol in indices) and (optionType=="Fut")):
302
- dates = expiry_list("RELIANCE","list")
303
- if(expiryDate=="latest"): expiryDate=dates[0]
304
- if(expiryDate=="next"): expiryDate=dates[1]
305
-
306
- if(expiryDate=="latest") or (expiryDate=="next"):
307
- dates=list(set((payload["expiryDates"])))
308
- dates.sort(key = lambda date: datetime.datetime.strptime(date, '%d-%b-%Y'))
309
- if(expiryDate=="latest"): expiryDate=dates[0]
310
- if(expiryDate=="next"): expiryDate=dates[1]
311
-
312
- meta = "Options"
313
- if(optionType=="Fut"): meta = "Futures"
314
- if(optionType=="PE"):optionType="Put"
315
- if(optionType=="CE"):optionType="Call"
316
-
317
- if(optionType!="-"):
318
- for i in payload['stocks']:
319
- if meta in i['metadata']['instrumentType']:
320
- #print(i['metadata'])
321
- if(optionType=="Fut"):
322
- if(i['metadata']['expiryDate']==expiryDate):
323
- metadata = i['metadata']
324
-
325
- if((optionType=="Put")or(optionType=="Call")):
326
- if (i['metadata']["expiryDate"]==expiryDate):
327
- if (i['metadata']["optionType"]==optionType):
328
- if (i['metadata']["strikePrice"]==strikePrice):
329
- #print(i['metadata'])
330
- metadata = i['metadata']
331
-
332
- if(optionType=="-"):
333
- metadata = i['metadata']
334
-
335
- return metadata
336
-
337
- def nse_optionchain_ltp(payload,strikePrice,optionType,inp=0,intent=""):
338
- expiry_dates = payload['records']['expiryDates']
339
- expiry_dates = [datetime.datetime.strptime(date, "%d-%b-%Y").date() for date in expiry_dates]
340
- expiry_dates = [date.strftime("%d-%b-%Y") for date in expiry_dates if date >= datetime.datetime.now().date()]
341
- expiryDate=expiry_dates[inp]
342
- for x in range(len(payload['records']['data'])):
343
- if((payload['records']['data'][x]['strikePrice']==strikePrice) & (payload['records']['data'][x]['expiryDate']==expiryDate)):
344
- if(intent==""): return payload['records']['data'][x][optionType]['lastPrice']
345
- if(intent=="sell"): return payload['records']['data'][x][optionType]['bidprice']
346
- if(intent=="buy"): return payload['records']['data'][x][optionType]['askPrice']
347
-
348
- def nse_eq(symbol):
349
- symbol = nsesymbolpurify(symbol)
350
- try:
351
- payload = nsefetch('https://www.nseindia.com/api/quote-equity?symbol='+symbol)
352
- try:
353
- if(payload['error']=={}):
354
- print("Please use nse_fno() function to reduce latency.")
355
- payload = nsefetch('https://www.nseindia.com/api/quote-derivative?symbol='+symbol)
356
- except:
357
- pass
358
- except KeyError:
359
- print("Getting Error While Fetching.")
360
- return payload
361
-
362
-
363
- def nse_fno(symbol):
364
- symbol = nsesymbolpurify(symbol)
365
- try:
366
- payload = nsefetch('https://www.nseindia.com/api/quote-derivative?symbol='+symbol)
367
- try:
368
- if(payload['error']=={}):
369
- print("Please use nse_eq() function to reduce latency.")
370
- payload = nsefetch('https://www.nseindia.com/api/quote-equity?symbol='+symbol)
371
- except KeyError:
372
- pass
373
- except KeyError:
374
- print("Getting Error While Fetching.")
375
- return payload
376
-
377
- def quote_equity(symbol):
378
- return nse_eq(symbol)
379
-
380
- def quote_derivative(symbol):
381
- return nse_fno(symbol)
382
-
383
- def option_chain(symbol):
384
- return nse_optionchain_scrapper(symbol)
385
-
386
- def nse_holidays(type="trading"):
387
- if(type=="clearing"):
388
- payload = nsefetch('https://www.nseindia.com/api/holiday-master?type=clearing')
389
- if(type=="trading"):
390
- payload = nsefetch('https://www.nseindia.com/api/holiday-master?type=trading')
391
- return payload
392
-
393
- def holiday_master(type="trading"):
394
- return nse_holidays(type)
395
-
396
- def nse_results(index="equities",period="Quarterly"):
397
- if(index=="equities") or (index=="debt") or (index=="sme"):
398
- if(period=="Quarterly") or (period=="Annual")or (period=="Half-Yearly")or (period=="Others"):
399
- payload = nsefetch('https://www.nseindia.com/api/corporates-financial-results?index='+index+'&period='+period)
400
- return pd.json_normalize(payload)
401
- else:
402
- print("Give Correct Period Input")
403
- else:
404
- print("Give Correct Index Input")
405
-
406
- def nse_events():
407
- output = nsefetch('https://www.nseindia.com/api/event-calendar')
408
- return pd.json_normalize(output)
409
-
410
- def nse_past_results(symbol):
411
- symbol = nsesymbolpurify(symbol)
412
- return nsefetch('https://www.nseindia.com/api/results-comparision?symbol='+symbol)
413
-
414
- def expiry_list(symbol,type="list"):
415
- logging.info("Getting Expiry List of: "+ symbol)
416
-
417
- if(type!="list"):
418
- payload = nse_optionchain_scrapper(symbol)
419
- payload = pd.DataFrame({'Date':payload['records']['expiryDates']})
420
- return payload
421
-
422
- if(type=="list"):
423
- payload = nse_quote(symbol)
424
- dates=list(set((payload["expiryDates"])))
425
- dates.sort(key = lambda date: datetime.datetime.strptime(date, '%d-%b-%Y'))
426
- return dates
427
-
428
-
429
- def nse_custom_function_secfno(symbol,attribute="lastPrice"):
430
- positions = nsefetch('https://www.nseindia.com/api/equity-stockIndices?index=SECURITIES%20IN%20F%26O')
431
- endp = len(positions['data'])
432
- for x in range(0, endp):
433
- if(positions['data'][x]['symbol']==symbol.upper()):
434
- return positions['data'][x][attribute]
435
-
436
- def nse_blockdeal():
437
- payload = nsefetch('https://nseindia.com/api/block-deal')
438
- return payload
439
-
440
- def nse_marketStatus():
441
- payload = nsefetch('https://nseindia.com/api/marketStatus')
442
- return payload
443
-
444
- def nse_circular(mode="latest"):
445
- if(mode=="latest"):
446
- payload = nsefetch('https://nseindia.com/api/latest-circular')
447
- else:
448
- payload = nsefetch('https://www.nseindia.com/api/circulars')
449
- return payload
450
-
451
- def nse_fiidii(mode="pandas"):
452
- try:
453
- if(mode=="pandas"):
454
- return pd.DataFrame(nsefetch('https://www.nseindia.com/api/fiidiiTradeReact'))
455
- else:
456
- return nsefetch('https://www.nseindia.com/api/fiidiiTradeReact')
457
- except:
458
- logger.info("Pandas is not working for some reason.")
459
- return nsefetch('https://www.nseindia.com/api/fiidiiTradeReact')
460
-
461
- def nsetools_get_quote(symbol):
462
- payload = nsefetch('https://www.nseindia.com/api/equity-stockIndices?index=SECURITIES%20IN%20F%26O')
463
- for m in range(len(payload['data'])):
464
- if(payload['data'][m]['symbol']==symbol.upper()):
465
- return payload['data'][m]
466
-
467
-
468
- def nse_index():
469
- payload = nsefetch('https://iislliveblob.niftyindices.com/jsonfiles/LiveIndicesWatch.json')
470
- payload = pd.DataFrame(payload["data"])
471
- return payload
472
-
473
- def nse_get_index_list():
474
- payload = nsefetch('https://iislliveblob.niftyindices.com/jsonfiles/LiveIndicesWatch.json')
475
- payload = pd.DataFrame(payload["data"])
476
- return payload["indexName"].tolist()
477
-
478
- def nse_get_index_quote(index):
479
- payload = nsefetch('https://iislliveblob.niftyindices.com/jsonfiles/LiveIndicesWatch.json')
480
- for m in range(len(payload['data'])):
481
- if(payload['data'][m]["indexName"] == index.upper()):
482
- return payload['data'][m]
483
-
484
- def nse_get_advances_declines(mode="pandas"):
485
- try:
486
- if(mode=="pandas"):
487
- positions = nsefetch('https://www.nseindia.com/api/equity-stockIndices?index=SECURITIES%20IN%20F%26O')
488
- return pd.DataFrame(positions['data'])
489
- else:
490
- return nsefetch('https://www.nseindia.com/api/equity-stockIndices?index=SECURITIES%20IN%20F%26O')
491
- except:
492
- logger.info("Pandas is not working for some reason.")
493
- return nsefetch('https://www.nseindia.com/api/equity-stockIndices?index=SECURITIES%20IN%20F%26O')
494
-
495
- def nse_get_top_losers():
496
- positions = nsefetch('https://www.nseindia.com/api/equity-stockIndices?index=SECURITIES%20IN%20F%26O')
497
- df = pd.DataFrame(positions['data'])
498
- df = df.sort_values(by="pChange")
499
- return df.head(5)
500
-
501
- def nse_get_top_gainers():
502
- positions = nsefetch('https://www.nseindia.com/api/equity-stockIndices?index=SECURITIES%20IN%20F%26O')
503
- df = pd.DataFrame(positions['data'])
504
- df = df.sort_values(by="pChange" , ascending = False)
505
- return df.head(5)
506
-
507
- def nse_get_fno_lot_sizes(symbol="all",mode="list"):
508
- url="https://archives.nseindia.com/content/fo/fo_mktlots.csv"
509
-
510
- if(mode=="list"):
511
- s=requests.get(url).text
512
- res_dict = {}
513
- for line in s.split('\n'):
514
- if line != '' and re.search(',', line) and (line.casefold().find('symbol') == -1):
515
- (code, name) = [x.strip() for x in line.split(',')[1:3]]
516
- res_dict[code] = int(name)
517
- if(symbol=="all"):
518
- return res_dict
519
- if(symbol!=""):
520
- return res_dict[symbol.upper()]
521
-
522
- if(mode=="pandas"):
523
- payload = pd.read_csv(url)
524
- if(symbol=="all"):
525
- return payload
526
- else:
527
- payload = payload[(payload.iloc[:, 1] == symbol.upper())]
528
- return payload
529
-
530
- def whoistheboss():
531
- return "subhash"
532
-
533
- def indiavix():
534
- payload = nsefetch("https://www.nseindia.com/api/allIndices")
535
- for x in range(0, len(payload["data"])):
536
- if(payload["data"][x]["index"]=="INDIA VIX"):
537
- return payload["data"][x]["last"]
538
-
539
- def index_info(index):
540
- payload = nsefetch("https://www.nseindia.com/api/allIndices")
541
- for x in range(0, len(payload["data"])):
542
- if(payload["data"][x]["index"]==index):
543
- return payload["data"][x]
544
-
545
- import math
546
- from scipy.stats import norm
547
-
548
- def black_scholes_dexter(S0,X,t,σ="",r=10,q=0.0,td=365):
549
-
550
- if(σ==""):σ =indiavix()
551
-
552
- S0,X,σ,r,q,t = float(S0),float(X),float(σ/100),float(r/100),float(q/100),float(t/td)
553
- #https://unofficed.com/black-scholes-model-options-calculator-google-sheet/
554
-
555
- d1 = (math.log(S0/X)+(r-q+0.5*σ**2)*t)/(σ*math.sqrt(t))
556
- #stackoverflow.com/questions/34258537/python-typeerror-unsupported-operand-types-for-float-and-int
557
-
558
- #stackoverflow.com/questions/809362/how-to-calculate-cumulative-normal-distribution
559
- Nd1 = (math.exp((-d1**2)/2))/math.sqrt(2*math.pi)
560
- d2 = d1-σ*math.sqrt(t)
561
- Nd2 = norm.cdf(d2)
562
- call_theta =(-((S0*σ*math.exp(-q*t))/(2*math.sqrt(t))*(1/(math.sqrt(2*math.pi)))*math.exp(-(d1*d1)/2))-(r*X*math.exp(-r*t)*norm.cdf(d2))+(q*math.exp(-q*t)*S0*norm.cdf(d1)))/td
563
- put_theta =(-((S0*σ*math.exp(-q*t))/(2*math.sqrt(t))*(1/(math.sqrt(2*math.pi)))*math.exp(-(d1*d1)/2))+(r*X*math.exp(-r*t)*norm.cdf(-d2))-(q*math.exp(-q*t)*S0*norm.cdf(-d1)))/td
564
- call_premium =math.exp(-q*t)*S0*norm.cdf(d1)-X*math.exp(-r*t)*norm.cdf(d1-σ*math.sqrt(t))
565
- put_premium =X*math.exp(-r*t)*norm.cdf(-d2)-math.exp(-q*t)*S0*norm.cdf(-d1)
566
- call_delta =math.exp(-q*t)*norm.cdf(d1)
567
- put_delta =math.exp(-q*t)*(norm.cdf(d1)-1)
568
- gamma =(math.exp(-r*t)/(S0*σ*math.sqrt(t)))*(1/(math.sqrt(2*math.pi)))*math.exp(-(d1*d1)/2)
569
- vega = ((1/100)*S0*math.exp(-r*t)*math.sqrt(t))*(1/(math.sqrt(2*math.pi))*math.exp(-(d1*d1)/2))
570
- call_rho =(1/100)*X*t*math.exp(-r*t)*norm.cdf(d2)
571
- put_rho =(-1/100)*X*t*math.exp(-r*t)*norm.cdf(-d2)
572
-
573
- return call_theta,put_theta,call_premium,put_premium,call_delta,put_delta,gamma,vega,call_rho,put_rho
574
-
575
- def equity_history_virgin(symbol,series,start_date,end_date):
576
- #url="https://www.nseindia.com/api/historical/cm/equity?symbol="+symbol+"&series=[%22"+series+"%22]&from="+str(start_date)+"&to="+str(end_date)+""
577
- url = 'https://www.nseindia.com/api/historical/cm/equity?symbol=' + symbol + '&series=["' + series + '"]&from=' + start_date + '&to=' + end_date
578
-
579
- payload = nsefetch(url)
580
- return pd.DataFrame.from_records(payload["data"])
581
-
582
- # You shall see beautiful use the logger function.
583
- def equity_history(symbol,series,start_date,end_date):
584
- #We are getting the input in text. So it is being converted to Datetime object from String.
585
- start_date = datetime.datetime.strptime(start_date, "%d-%m-%Y")
586
- end_date = datetime.datetime.strptime(end_date, "%d-%m-%Y")
587
- logging.info("Starting Date: "+str(start_date))
588
- logging.info("Ending Date: "+str(end_date))
589
-
590
- #We are calculating the difference between the days
591
- diff = end_date-start_date
592
- logging.info("Total Number of Days: "+str(diff.days))
593
- logging.info("Total FOR Loops in the program: "+str(int(diff.days/40)))
594
- logging.info("Remainder Loop: " + str(diff.days-(int(diff.days/40)*40)))
595
-
596
-
597
- total=pd.DataFrame()
598
- for i in range (0,int(diff.days/40)):
599
-
600
- temp_date = (start_date+datetime.timedelta(days=(40))).strftime("%d-%m-%Y")
601
- start_date = datetime.datetime.strftime(start_date, "%d-%m-%Y")
602
-
603
- logging.info("Loop = "+str(i))
604
- logging.info("====")
605
- logging.info("Starting Date: "+str(start_date))
606
- logging.info("Ending Date: "+str(temp_date))
607
- logging.info("====")
608
-
609
- #total=total.append(equity_history_virgin(symbol,series,start_date,temp_date))
610
- #total=total.concat(equity_history_virgin(symbol,series,start_date,temp_date))
611
- total = pd.concat([total, equity_history_virgin(symbol, series, start_date, temp_date)])
612
-
613
-
614
- logging.info("Length of the Table: "+ str(len(total)))
615
-
616
- #Preparation for the next loop
617
- start_date = datetime.datetime.strptime(temp_date, "%d-%m-%Y")
618
-
619
-
620
- start_date = datetime.datetime.strftime(start_date, "%d-%m-%Y")
621
- end_date = datetime.datetime.strftime(end_date, "%d-%m-%Y")
622
-
623
- logging.info("End Loop")
624
- logging.info("====")
625
- logging.info("Starting Date: "+str(start_date))
626
- logging.info("Ending Date: "+str(end_date))
627
- logging.info("====")
628
-
629
- #total=total.append(equity_history_virgin(symbol,series,start_date,end_date))
630
- #total=total.concat(equity_history_virgin(symbol,series,start_date,end_date))
631
- total = pd.concat([total, equity_history_virgin(symbol, series, start_date, end_date)])
632
-
633
-
634
- logging.info("Finale")
635
- logging.info("Length of the Total Dataset: "+ str(len(total)))
636
- payload = total.iloc[::-1].reset_index(drop=True)
637
- return payload
638
-
639
- def derivative_history_virgin(symbol,start_date,end_date,instrumentType,expiry_date,strikePrice="",optionType=""):
640
-
641
- instrumentType = instrumentType.lower()
642
-
643
- if(instrumentType=="options"):
644
- instrumentType="OPTSTK"
645
- if("NIFTY" in symbol): instrumentType="OPTIDX"
646
-
647
- if(instrumentType=="futures"):
648
- instrumentType="FUTSTK"
649
- if("NIFTY" in symbol): instrumentType="FUTIDX"
650
-
651
-
652
- #if(((instrumentType=="OPTIDX")or (instrumentType=="OPTSTK")) and (expiry_date!="")):
653
- if(strikePrice!=""):
654
- strikePrice = "%.2f" % strikePrice
655
- strikePrice = str(strikePrice)
656
-
657
- nsefetch_url = "https://www.nseindia.com/api/historical/fo/derivatives?&from="+str(start_date)+"&to="+str(end_date)+"&optionType="+optionType+"&strikePrice="+strikePrice+"&expiryDate="+expiry_date+"&instrumentType="+instrumentType+"&symbol="+symbol+""
658
- payload = nsefetch(nsefetch_url)
659
- logging.info(nsefetch_url)
660
- logging.info(payload)
661
- return pd.DataFrame.from_records(payload["data"])
662
-
663
- def derivative_history(symbol,start_date,end_date,instrumentType,expiry_date,strikePrice="",optionType=""):
664
- #We are getting the input in text. So it is being converted to Datetime object from String.
665
- start_date = datetime.datetime.strptime(start_date, "%d-%m-%Y")
666
- end_date = datetime.datetime.strptime(end_date, "%d-%m-%Y")
667
- logging.info("Starting Date: "+str(start_date))
668
- logging.info("Ending Date: "+str(end_date))
669
-
670
- #We are calculating the difference between the days
671
- diff = end_date-start_date
672
- logging.info("Total Number of Days: "+str(diff.days))
673
- logging.info("Total FOR Loops in the program: "+str(int(diff.days/40)))
674
- logging.info("Remainder Loop: " + str(diff.days-(int(diff.days/40)*40)))
675
-
676
-
677
- total=pd.DataFrame()
678
- for i in range (0,int(diff.days/40)):
679
-
680
- temp_date = (start_date+datetime.timedelta(days=(40))).strftime("%d-%m-%Y")
681
- start_date = datetime.datetime.strftime(start_date, "%d-%m-%Y")
682
-
683
- logging.info("Loop = "+str(i))
684
- logging.info("====")
685
- logging.info("Starting Date: "+str(start_date))
686
- logging.info("Ending Date: "+str(temp_date))
687
- logging.info("====")
688
-
689
- #total=total.append(derivative_history_virgin(symbol,start_date,temp_date,instrumentType,expiry_date,strikePrice,optionType))
690
- #total=total.concat([total, derivative_history_virgin(symbol,start_date,temp_date,instrumentType,expiry_date,strikePrice,optionType)])
691
- total = pd.concat([total, derivative_history_virgin(symbol, start_date, temp_date, instrumentType, expiry_date, strikePrice, optionType)])
692
-
693
-
694
- logging.info("Length of the Table: "+ str(len(total)))
695
-
696
- #Preparation for the next loop
697
- start_date = datetime.datetime.strptime(temp_date, "%d-%m-%Y")
698
-
699
-
700
- start_date = datetime.datetime.strftime(start_date, "%d-%m-%Y")
701
- end_date = datetime.datetime.strftime(end_date, "%d-%m-%Y")
702
-
703
- logging.info("End Loop")
704
- logging.info("====")
705
- logging.info("Starting Date: "+str(start_date))
706
- logging.info("Ending Date: "+str(end_date))
707
- logging.info("====")
708
-
709
- #total=total.append(derivative_history_virgin(symbol,start_date,end_date,instrumentType,expiry_date,strikePrice,optionType))
710
- #total = total.concat([total, derivative_history_virgin(symbol,start_date,end_date,instrumentType,expiry_date,strikePrice,optionType)])
711
- total = pd.concat([total, derivative_history_virgin(symbol, start_date, end_date, instrumentType, expiry_date, strikePrice, optionType)])
712
-
713
-
714
-
715
- logging.info("Finale")
716
- logging.info("Length of the Total Dataset: "+ str(len(total)))
717
- payload = total.iloc[::-1].reset_index(drop=True)
718
- return payload
719
-
720
-
721
- def expiry_history(symbol,start_date="",end_date="",type="options"):
722
- if(end_date==""):end_date=end_date
723
- nsefetch_url = "https://www.nseindia.com/api/historical/fo/derivatives/meta?&from="+start_date+"&to="+end_date+"&symbol="+symbol+""
724
- payload = nsefetch(nsefetch_url)
725
-
726
- #print(payload)
727
-
728
- for key, value in payload['expiryDatesByInstrument'].items():
729
- if type.lower() == "options" and "OPT" in key:
730
- payload_data = payload['expiryDatesByInstrument'][key]
731
- break
732
- elif type.lower() == "futures" and "FUT" in key:
733
- payload_data = payload['expiryDatesByInstrument'][key]
734
- break
735
-
736
- # Convert start_date and end_date to datetime objects
737
- start_date = datetime.datetime.strptime(start_date, "%d-%m-%Y")
738
- end_date = datetime.datetime.strptime(end_date, "%d-%m-%Y")
739
-
740
- # Initialize an empty list to store filtered dates
741
- filtered_date_payload = []
742
-
743
- # Initialize a flag to check if the first date after end_date has been added
744
- added_after_end_date = False
745
-
746
- # Iterate through date_payload and filter dates within the range
747
- for date_str in payload_data:
748
- date_obj = datetime.datetime.strptime(date_str, "%d-%b-%Y")
749
- if start_date <= date_obj <= end_date:
750
- filtered_date_payload.append(date_str)
751
- elif date_obj > end_date and not added_after_end_date:
752
- filtered_date_payload.append(date_str)
753
- added_after_end_date = True
754
-
755
- return filtered_date_payload
756
-
757
- # # Nifty Indicies Site
758
-
759
- niftyindices_headers = {
760
- 'Connection': 'keep-alive',
761
- 'sec-ch-ua': '" Not;A Brand";v="99", "Google Chrome";v="91", "Chromium";v="91"',
762
- 'Accept': 'application/json, text/javascript, */*; q=0.01',
763
- 'DNT': '1',
764
- 'X-Requested-With': 'XMLHttpRequest',
765
- 'sec-ch-ua-mobile': '?0',
766
- 'User-Agent': 'Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/91.0.4472.77 Safari/537.36',
767
- 'Content-Type': 'application/json; charset=UTF-8',
768
- 'Origin': 'https://niftyindices.com',
769
- 'Sec-Fetch-Site': 'same-origin',
770
- 'Sec-Fetch-Mode': 'cors',
771
- 'Sec-Fetch-Dest': 'empty',
772
- 'Referer': 'https://niftyindices.com/reports/historical-data',
773
- 'Accept-Language': 'en-US,en;q=0.9,hi;q=0.8',
774
- }
775
-
776
- def index_history(symbol,start_date,end_date):
777
- data = {'cinfo': "{'name':'" + symbol + "','startDate':'" + start_date + "','endDate':'" + end_date + "','indexName':'" + symbol + "'}"}
778
- payload = requests.post('https://niftyindices.com/Backpage.aspx/getHistoricaldatatabletoString', headers=niftyindices_headers, json=data).json()
779
- payload = json.loads(payload["d"])
780
- payload=pd.DataFrame.from_records(payload)
781
- return payload
782
-
783
- def index_pe_pb_div(symbol,start_date,end_date):
784
- data = {'cinfo': "{'name':'" + symbol + "','startDate':'" + start_date + "','endDate':'" + end_date + "','indexName':'" + symbol + "'}"}
785
- payload = requests.post('https://niftyindices.com/Backpage.aspx/getpepbHistoricaldataDBtoString', headers=niftyindices_headers, json=data).json()
786
- payload = json.loads(payload["d"])
787
- payload=pd.DataFrame.from_records(payload)
788
- return payload
789
-
790
- def index_total_returns(symbol,start_date,end_date):
791
- data = {'cinfo': "{'name':'" + symbol + "','startDate':'" + start_date + "','endDate':'" + end_date + "','indexName':'" + symbol + "'}"}
792
- payload = requests.post('https://niftyindices.com/Backpage.aspx/getTotalReturnIndexString', headers=niftyindices_headers, json=data).json()
793
- payload = json.loads(payload["d"])
794
- payload=pd.DataFrame.from_records(payload)
795
- return payload
796
-
797
- def get_bhavcopy(date):
798
- date = date.replace("-","")
799
- payload=pd.read_csv("https://archives.nseindia.com/products/content/sec_bhavdata_full_"+date+".csv")
800
- return payload
801
-
802
- def get_bulkdeals():
803
- payload=pd.read_csv("https://archives.nseindia.com/content/equities/bulk.csv")
804
- return payload
805
-
806
- def get_blockdeals():
807
- payload=pd.read_csv("https://archives.nseindia.com/content/equities/block.csv")
808
- return payload
809
-
810
- #Request from subhash
811
- ## https://unofficed.com/how-to-find-the-beta-of-indian-stocks-using-python/
812
- def get_beta_df_maker(symbol,days):
813
- if("NIFTY" in symbol):
814
- end_date = datetime.datetime.now().strftime("%d-%b-%Y")
815
- end_date = str(end_date)
816
-
817
- start_date = (datetime.datetime.now()- datetime.timedelta(days=days)).strftime("%d-%b-%Y")
818
- start_date = str(start_date)
819
-
820
- df2=index_history(symbol,start_date,end_date)
821
- df2["daily_change"]=df2["CLOSE"].astype(float).pct_change()
822
- df2=df2[['HistoricalDate','daily_change']]
823
- df2 = df2.iloc[1: , :]
824
- return df2
825
- else:
826
- end_date = datetime.datetime.now().strftime("%d-%m-%Y")
827
- end_date = str(end_date)
828
-
829
- start_date = (datetime.datetime.now()- datetime.timedelta(days=days)).strftime("%d-%m-%Y")
830
- start_date = str(start_date)
831
-
832
- df = equity_history(symbol,"EQ",start_date,end_date)
833
-
834
- df["daily_change"]=df["CH_CLOSING_PRICE"].pct_change()
835
- df=df[['CH_TIMESTAMP','daily_change']]
836
- df = df.iloc[1: , :] #thispointer.com/drop-first-row-of-pandas-dataframe-3-ways/
837
- return df
838
-
839
- def getbeta(symbol,days=365,symbol2="NIFTY 50"):
840
- return get_beta(symbol,days,symbol2)
841
-
842
- def get_beta(symbol,days=365,symbol2="NIFTY 50"):
843
- #Default is 248 days. (Input of Subhash)
844
- df = get_beta_df_maker(symbol,days)
845
- df2 = get_beta_df_maker(symbol2,days)
846
-
847
- x=df["daily_change"].tolist()
848
- y=df2["daily_change"].tolist()
849
- #stackoverflow.com/questions/42670055/is-there-any-better-way-to-calculate-the-covariance-of-two-lists-than-this
850
- mean_x = sum(x) / len(x)
851
- mean_y = sum(y) / len(y)
852
- covariance = sum((a - mean_x) * (b - mean_y) for (a,b) in zip(x,y)) / len(x)
853
-
854
- mean = sum(y) / len(y)
855
- variance = sum((i - mean) ** 2 for i in y) / len(y)
856
-
857
- beta = covariance/variance
858
- return round(beta,3)
859
-
860
- def nse_preopen(key="NIFTY",type="pandas"):
861
- payload = nsefetch("https://www.nseindia.com/api/market-data-pre-open?key="+key+"")
862
- if(type=="pandas"):
863
- payload = pd.DataFrame(payload['data'])
864
- payload = pd.json_normalize(payload['metadata'])
865
- return payload
866
- else:
867
- return payload
868
-
869
- #By Avinash https://forum.unofficed.com/t/nsepython-documentation/376/102?u=dexter
870
- def nse_preopen_movers(key="FO",filter=1.5):
871
- preOpen_gainer=nse_preopen(key)
872
- return preOpen_gainer[preOpen_gainer['pChange'] >1.5],preOpen_gainer[preOpen_gainer['pChange'] <-1.5]
873
-
874
- # type = "securities"
875
- # type = "etf"
876
- # type = "sme"
877
- #
878
- # sort = "volume"
879
- # sort = "value"
880
-
881
- def nse_most_active(type="securities",sort="value"):
882
- payload = nsefetch("https://www.nseindia.com/api/live-analysis-most-active-"+type+"?index="+sort+"")
883
- payload = pd.DataFrame(payload["data"])
884
- return payload
885
-
886
-
887
- def nse_eq_symbols():
888
- #https://forum.unofficed.com/t/feature-request-stocklist-api/1073/11
889
- eq_list_pd = pd.read_csv('https://archives.nseindia.com/content/equities/EQUITY_L.csv')
890
- return eq_list_pd['SYMBOL'].tolist()
891
-
892
- def nse_price_band_hitters(bandtype="both",view="AllSec"):
893
- payload = nsefetch("https://www.nseindia.com/api/live-analysis-price-band-hitter")
894
-
895
- #bandtype can be upper, lower, both
896
- #view can be AllSec,SecGtr20,SecLwr20
897
- return pd.DataFrame(payload[bandtype][view]["data"])
898
-
899
- def nse_largedeals(mode="bulk_deals"):
900
- payload = nsefetch('https://www.nseindia.com/api/snapshot-capital-market-largedeal')
901
- if(mode=="bulk_deals"):
902
- return pd.DataFrame(payload["BULK_DEALS_DATA"])
903
- if(mode=="short_deals"):
904
- return pd.DataFrame(payload["SHORT_DEALS_DATA"])
905
- if(mode=="block_deals"):
906
- return pd.DataFrame(payload["BLOCK_DEALS_DATA"])
907
-
908
- def nse_largedeals_historical(from_date, to_date, mode="bulk_deals"):
909
- if mode == "bulk_deals":
910
- mode = "bulk-deals"
911
- elif mode == "short_deals":
912
- mode = "short-selling"
913
- elif mode == "block_deals":
914
- mode = "block-deals"
915
-
916
- url='https://www.nseindia.com/api/historical/' + mode + '?from=' + from_date + '&to=' + to_date
917
- logging.info("Fetching " + str(url))
918
- payload = nsefetch(url)
919
- return pd.DataFrame(payload["data"])
920
-
921
- #https://forum.unofficed.com/t/feature-request-nse-fno-participant-wise-oi/1179/7
922
- #print(get_fao_participant_oi("04-06-2021"))
923
- def get_fao_participant_oi(date):
924
- date = date.replace("-","")
925
- payload=pd.read_csv("https://archives.nseindia.com/content/nsccl/fao_participant_oi_"+date+".csv")
926
- return payload
927
-
928
- #https://forum.unofficed.com/t/how-to-check-if-the-market-is-open-today-or-not/1268/1
929
- def is_market_open(segment = "FO"): #COM,CD,CB,CMOT,COM,FO,IRD,MF,NDM,NTRP,SLBS
930
-
931
- holiday_json = nse_holidays()[segment]
932
-
933
- # Get today's date in the format 'dd-Mon-yyyy'
934
- today_date = datetime.date.today().strftime('%d-%b-%Y')
935
-
936
- # Check if today's date is in the holiday_json
937
- for holiday in holiday_json:
938
- if holiday['tradingDate'] != today_date:
939
- print("FNO Market is open today. Have a Nice Trade!")
940
- return True
941
- if holiday['tradingDate'] == today_date:
942
- print(f"Market is closed today because of {holiday['description']}")
943
- return False
944
-
945
- def nse_expirydetails_by_symbol(symbol,meta ="Futures",i=0):
946
- payload = nse_quote(symbol)
947
-
948
- if(meta=="Futures"):
949
- selected_key = next((key for key in payload["expiryDatesByInstrument"] if "futures" in key.lower()), None)
950
- if(meta=="Options"):
951
- selected_key = next((key for key in payload["expiryDatesByInstrument"] if "options" in key.lower()), None)
952
-
953
- expiry_dates=payload["expiryDatesByInstrument"][selected_key]
954
- expiry_dates = [datetime.datetime.strptime(date, "%d-%b-%Y").date() for date in expiry_dates]
955
- expiry_dates = [date.strftime("%d-%b-%Y") for date in expiry_dates if date >= datetime.datetime.now().date()]
956
-
957
- currentExpiry=expiry_dates[i]
958
- currentExpiry = datetime.datetime.strptime(currentExpiry,'%d-%b-%Y').date()
959
- dte = (currentExpiry - datetime.datetime.now().date()).days
960
- return currentExpiry,dte
961
-
962
- def security_wise_archive(from_date, to_date, symbol, series="ALL"):
963
- base_url = "https://www.nseindia.com/api/historical/securityArchives"
964
- url = f"{base_url}?from={from_date}&to={to_date}&symbol={symbol.upper()}&dataType=priceVolumeDeliverable&series={series.upper()}"
965
- payload = nsefetch(url)
966
- return pd.DataFrame(payload['data'])
1
+ import os,sys
2
+ #os.chdir(os.path.dirname(os.path.abspath(__file__)))
3
+ #sys.path.insert(1, os.path.join(sys.path[0], '..'))
4
+
5
+ import requests
6
+ import pandas as pd
7
+ import json
8
+ import random
9
+ import datetime,time
10
+ import logging
11
+ import re
12
+ import io
13
+ import urllib.parse
14
+
15
+ mode ='local'
16
+
17
+ # ---------------------------------------------------------------------------
18
+ # Transport
19
+ #
20
+ # NSE's site is fronted by Akamai Bot Manager, which fingerprints the TLS/JA3
21
+ # handshake of the client. A plain `requests.Session()` (or a shelled-out
22
+ # plain `curl`) gets blocked outright (HTTP 403 on the homepage itself) --
23
+ # this is NOT "requests is blocked in India", it's a bot-detection block that
24
+ # has nothing to do with geography. curl_cffi is a requests-compatible
25
+ # Session that impersonates a real Chrome TLS fingerprint, which clears this
26
+ # wall while remaining a pure Python HTTP client (no shell-out, no browser).
27
+ #
28
+ # curl_cffi is therefore the one and only transport nsefetch() uses now. It
29
+ # is a hard dependency (see requirements.txt/setup.py) because it's what
30
+ # makes the large majority of this library's functions work at all against
31
+ # the live site today.
32
+ # ---------------------------------------------------------------------------
33
+
34
+ try:
35
+ from curl_cffi.requests import Session as _CurlSession
36
+ _CURL_CFFI_OK = True
37
+ except ImportError:
38
+ _CURL_CFFI_OK = False
39
+
40
+
41
+ class NSEEndpointError(Exception):
42
+ """Raised by nsefetch() when NSE's site cannot be reached, or responds
43
+ with something other than usable JSON (blocked, retired endpoint, rate
44
+ limited, server error, etc).
45
+
46
+ Older versions of this library silently swallowed these failures and
47
+ returned `{}`, which just pushed the problem one level down into a
48
+ confusing `KeyError`/`AttributeError` in whatever function called
49
+ nsefetch() (see github.com/aeron7/nsepython issues #74, #75, and
50
+ nsepythonserver #6). Raising a descriptive exception here instead makes
51
+ the real failure visible immediately instead of as a downstream KeyError.
52
+ """
53
+ pass
54
+
55
+
56
+ _nse_session = None
57
+ _nse_warmed = False
58
+
59
+
60
+ def _get_nse_session():
61
+ """Return the shared, warmed-up curl_cffi session used by nsefetch().
62
+
63
+ The warm-up (visiting the homepage, then the option-chain page) is what
64
+ gets NSE's Akamai Bot Manager to hand out the `nsit`/`_abck`/`ak_bmsc`/
65
+ `bm_sv` cookies that most JSON API calls expect to see on the request.
66
+ """
67
+ global _nse_session, _nse_warmed
68
+
69
+ if not _CURL_CFFI_OK:
70
+ raise ImportError(
71
+ "nsepython needs curl_cffi to talk to the real nseindia.com site. "
72
+ "A plain `requests` session (and plain `curl`) gets blocked by "
73
+ "NSE's Akamai Bot Manager purely on TLS fingerprint, regardless "
74
+ "of where you are. Install it with: pip install curl_cffi"
75
+ )
76
+
77
+ if _nse_session is None:
78
+ _nse_session = _CurlSession(impersonate="chrome124")
79
+
80
+ if not _nse_warmed:
81
+ try:
82
+ _nse_session.get("https://www.nseindia.com", headers=headers, timeout=20)
83
+ time.sleep(1.2)
84
+ _nse_session.get("https://www.nseindia.com/option-chain", headers=headers, timeout=20)
85
+ time.sleep(0.8)
86
+ _nse_warmed = True
87
+ except Exception as e:
88
+ logging.warning("NSE session warm-up failed/partial: %s", e)
89
+
90
+ return _nse_session
91
+
92
+
93
+ def _equity_stockindices_fallback(session, api_headers):
94
+ """`/api/equity-stockIndices?index=SECURITIES IN F%26O` -- the F&O
95
+ securities list used by fnolist()/nsetools_get_quote()/
96
+ nse_get_advances_declines()/nse_get_top_losers()/nse_get_top_gainers()/
97
+ nse_custom_function_secfno() -- is a retired route on the live site
98
+ (confirmed HTTP 404, NSE's own "Resource not found" page, with or
99
+ without a fully browser-solved Akamai cookie jar).
100
+
101
+ `/api/market-data-pre-open?key=FO` carries the same per-symbol pChange/
102
+ lastPrice/etc information for the F&O universe, so we transparently
103
+ rewrite the request to that endpoint and reshape its response back into
104
+ the old `{"data": [{"symbol":..., "pChange":..., ...}]}` shape every
105
+ existing caller above already expects -- they keep working unchanged.
106
+ """
107
+ r = session.get(
108
+ "https://www.nseindia.com/api/market-data-pre-open?key=FO",
109
+ headers=api_headers, timeout=30,
110
+ )
111
+ if r.status_code != 200:
112
+ raise NSEEndpointError(
113
+ f"equity-stockIndices fallback (market-data-pre-open) failed: HTTP {r.status_code}"
114
+ )
115
+ try:
116
+ raw = r.json()
117
+ except ValueError:
118
+ raise NSEEndpointError("equity-stockIndices fallback returned a non-JSON body")
119
+
120
+ reshaped = []
121
+ for item in raw.get("data", []):
122
+ m = item.get("metadata", {}) or {}
123
+ if not m.get("symbol"):
124
+ continue
125
+ reshaped.append({
126
+ "symbol": m.get("symbol", ""),
127
+ "pChange": m.get("pChange", 0),
128
+ "lastPrice": m.get("lastPrice", 0),
129
+ "change": m.get("change", 0),
130
+ "previousClose": m.get("previousClose", 0),
131
+ "yearHigh": m.get("yearHigh", 0),
132
+ "yearLow": m.get("yearLow", 0),
133
+ "totalTradedValue": m.get("totalTurnover", 0),
134
+ "totalTradedVolume": m.get("finalQuantity", 0),
135
+ })
136
+ return {"data": reshaped}
137
+
138
+
139
+ def nsefetch(payload: str):
140
+ """Fetch a nseindia.com JSON API URL through a warmed-up curl_cffi
141
+ session, retrying once with a fresh warm-up if the first attempt looks
142
+ blocked (stale/expired Akamai cookies), and raising NSEEndpointError
143
+ (instead of silently returning `{}`) if it still can't get real JSON
144
+ back. `mode` is kept only for backwards compatibility with older
145
+ versions of this file; both 'local' and 'vpn' use this same transport
146
+ now, since the previous mode='vpn' plain-curl/os.popen() implementation
147
+ was both broken against the current Akamai wall *and* a command-injection
148
+ risk (see github.com/aeron7/nsepython issue #73).
149
+ """
150
+ global _nse_warmed
151
+
152
+ session = _get_nse_session()
153
+ api_headers = dict(headers)
154
+ api_headers.update({
155
+ "Accept": "application/json, text/plain, */*",
156
+ "Referer": "https://www.nseindia.com/option-chain",
157
+ })
158
+
159
+ if "equity-stockIndices" in payload and "SECURITIES" in payload:
160
+ return _equity_stockindices_fallback(session, api_headers)
161
+
162
+ try:
163
+ r = session.get(payload, headers=api_headers, timeout=30)
164
+ if r.status_code in (401, 403, 404, 429, 503):
165
+ # Could just be a stale/expired Akamai cookie jar -- re-warm once
166
+ # and retry before giving up.
167
+ _nse_warmed = False
168
+ session = _get_nse_session()
169
+ r = session.get(payload, headers=api_headers, timeout=30)
170
+
171
+ if r.status_code != 200:
172
+ raise NSEEndpointError(f"nsefetch: HTTP {r.status_code} for {payload}")
173
+
174
+ try:
175
+ return r.json()
176
+ except ValueError:
177
+ raise NSEEndpointError(
178
+ f"nsefetch: NSE returned a non-JSON body (length={len(r.text)}) for {payload}"
179
+ )
180
+ except NSEEndpointError:
181
+ raise
182
+ except Exception as e:
183
+ raise NSEEndpointError(f"nsefetch: request failed for {payload}: {e}")
184
+
185
+
186
+ # ---------------------------------------------------------------------------
187
+ # Optional, lazily-imported Playwright cookie-harvest fallback.
188
+ #
189
+ # For most of the library, curl_cffi's TLS impersonation + the warm-up above
190
+ # is all that's needed -- it is NOT the same as "requests is blocked", and it
191
+ # is NOT, in practice, gated behind a real JS-solved Akamai sensor challenge
192
+ # for the endpoints this library actually calls today (verified live: a
193
+ # fully browser-solved cookie jar makes zero difference to the handful of
194
+ # genuinely-retired routes like /api/quote-equity or /api/equity-stockIndices
195
+ # -- they are simply dead/404, not JS-walled).
196
+ #
197
+ # This helper exists as a best-effort escape hatch for the rarer case where
198
+ # NSE *does* flip an endpoint to require a cookie only a real browser's JS
199
+ # engine can produce -- curl_cffi never executes JavaScript, so it cannot
200
+ # solve that kind of challenge itself. It is intentionally NOT imported at
201
+ # module load time and NOT wired automatically into nsefetch(): it is slow
202
+ # (it launches a real headless browser), and for the specific endpoints this
203
+ # library has found still blocked as of this writing (the historical
204
+ # bulk/block/short-deals and securityArchives routes), the block looks like a
205
+ # server-side 503/retirement rather than a missing-JS-cookie problem, so
206
+ # there's no evidence a browser visit would fix them either. Call
207
+ # nse_harvest_playwright_cookies() yourself, once, near the start of your
208
+ # script if you want to try it against an endpoint you believe is genuinely
209
+ # JS-walled; it injects the solved cookies into the same shared session
210
+ # nsefetch() uses for every call after that.
211
+ # ---------------------------------------------------------------------------
212
+
213
+ def nse_harvest_playwright_cookies(url="https://www.nseindia.com/option-chain", timeout_ms=45000):
214
+ """Launch a real headless Chromium (via Playwright), let it naturally
215
+ pass NSE's Akamai Bot Manager JS sensor challenge by visiting `url`, then
216
+ copy its solved cookie jar into the shared curl_cffi session nsefetch()
217
+ uses. Optional, best-effort, and NOT required for the vast majority of
218
+ this library's functions.
219
+
220
+ Requires: pip install playwright && playwright install chromium
221
+ """
222
+ try:
223
+ from playwright.sync_api import sync_playwright
224
+ except ImportError as e:
225
+ raise ImportError(
226
+ "nse_harvest_playwright_cookies() needs Playwright to drive a "
227
+ "real browser. Install it with: pip install playwright && "
228
+ "playwright install chromium"
229
+ ) from e
230
+
231
+ session = _get_nse_session()
232
+ harvested = {}
233
+ with sync_playwright() as p:
234
+ browser = p.chromium.launch(headless=True)
235
+ try:
236
+ page = browser.new_page(user_agent=headers["User-Agent"])
237
+ page.goto("https://www.nseindia.com", timeout=timeout_ms)
238
+ page.wait_for_timeout(2000)
239
+ page.goto(url, timeout=timeout_ms)
240
+ page.wait_for_timeout(2000)
241
+ for c in page.context.cookies():
242
+ harvested[c["name"]] = c["value"]
243
+ finally:
244
+ browser.close()
245
+
246
+ for name, value in harvested.items():
247
+ try:
248
+ session.cookies.set(name, value, domain=".nseindia.com")
249
+ except Exception:
250
+ pass
251
+
252
+ global _nse_warmed
253
+ _nse_warmed = True # don't let the next nsefetch() stomp these with a plain re-warm
254
+ return harvested
255
+
256
+
257
+ def _nse_fetch_csv_text(url: str) -> str:
258
+ """Fetch a plain-text/CSV archive file through the shared curl_cffi
259
+ session (so these also benefit from the TLS-impersonation fix and don't
260
+ rely on plain `requests`/`pd.read_csv`'s bare urllib fetch, which
261
+ confirmed-live testing shows just hangs/times out against
262
+ nsearchives.nseindia.com, and is the less reliable of the two archive
263
+ hosts generally as NSE tightens Akamai enforcement over time)."""
264
+ session = _get_nse_session()
265
+ r = session.get(url, headers=headers, timeout=30)
266
+ if r.status_code != 200:
267
+ raise NSEEndpointError(f"nsefetch (csv): HTTP {r.status_code} for {url}")
268
+ return r.text
269
+
270
+
271
+ headers = {
272
+ 'Connection': 'keep-alive',
273
+ 'Cache-Control': 'max-age=0',
274
+ 'DNT': '1',
275
+ 'Upgrade-Insecure-Requests': '1',
276
+ 'User-Agent': 'Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/79.0.3945.79 Safari/537.36',
277
+ 'Sec-Fetch-User': '?1',
278
+ 'Accept': 'text/html,application/xhtml+xml,application/xml;q=0.9,image/webp,image/apng,*/*;q=0.8,application/signed-exchange;v=b3;q=0.9',
279
+ 'Sec-Fetch-Site': 'none',
280
+ 'Sec-Fetch-Mode': 'navigate',
281
+ 'Accept-Encoding': 'gzip, deflate, br',
282
+ 'Accept-Language': 'en-US,en;q=0.9,hi;q=0.8',
283
+ }
284
+
285
+ #Curl headers
286
+ curl_headers = ''' -H "authority: beta.nseindia.com" -H "cache-control: max-age=0" -H "dnt: 1" -H "upgrade-insecure-requests: 1" -H "user-agent: Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/79.0.3945.117 Safari/537.36" -H "sec-fetch-user: ?1" -H "accept: text/html,application/xhtml+xml,application/xml;q=0.9,image/webp,image/apng,*/*;q=0.8,application/signed-exchange;v=b3;q=0.9" -H "sec-fetch-site: none" -H "sec-fetch-mode: navigate" -H "accept-encoding: gzip, deflate, br" -H "accept-language: en-US,en;q=0.9,hi;q=0.8" --compressed'''
287
+
288
+ run_time=datetime.datetime.now()
289
+
290
+ #Constants
291
+ indices = ['NIFTY','FINNIFTY','BANKNIFTY']
292
+
293
+ def running_status():
294
+ start_now=datetime.datetime.now().replace(hour=9, minute=15, second=0, microsecond=0)
295
+ end_now=datetime.datetime.now().replace(hour=15, minute=30, second=0, microsecond=0)
296
+ return start_now<datetime.datetime.now()<end_now
297
+
298
+ #Getting FNO Symboles
299
+ def fnolist():
300
+ positions = nsefetch('https://www.nseindia.com/api/equity-stockIndices?index=SECURITIES%20IN%20F%26O')
301
+ nselist = indices.copy()
302
+ for x in range(len(positions['data'])):
303
+ nselist.append(positions['data'][x]['symbol'])
304
+ return nselist
305
+
306
+ def nsesymbolpurify(symbol):
307
+ symbol = symbol.replace('&','%26') #URL Parse for Stocks Like M&M Finance
308
+ return symbol
309
+
310
+ def nse_optionchain_scrapper(symbol):
311
+ symbol = nsesymbolpurify(symbol)
312
+ # Using getSymbolDerivativesData as it provides all expiries and strikes in one go
313
+ url = f'https://www.nseindia.com/api/NextApi/apiClient/GetQuoteApi?functionName=getSymbolDerivativesData&symbol={symbol}'
314
+ payload = nsefetch(url)
315
+
316
+ # Transformation to match the "data" structure expected by pcr and other functions
317
+ if payload and 'data' in payload:
318
+ new_data = []
319
+ # Group by strikePrice and expiryDate to create a combined CE/PE structure if possible,
320
+ # or just provide the raw list if the consumers can handle it.
321
+ # The current pcr() handles a list of entries where each has CE/PE keys OR is the entry itself.
322
+
323
+ # Actually, let's restructure it to be more compatible with the expected 'data' format:
324
+ # a list of dictionaries, each having 'strikePrice', 'expiryDate', 'CE', 'PE'.
325
+ combined = {}
326
+ for entry in payload['data']:
327
+ sp = entry.get('strikePrice')
328
+ ed = entry.get('expiryDate')
329
+ ot = entry.get('optionType')
330
+ if not sp or not ed or ot == 'XX': continue
331
+
332
+ key = (sp, ed)
333
+ if key not in combined:
334
+ combined[key] = {'strikePrice': sp, 'expiryDate': ed, 'CE': None, 'PE': None}
335
+
336
+ combined[key][ot] = entry
337
+
338
+ payload['data'] = list(combined.values())
339
+
340
+ return payload
341
+
342
+
343
+ def oi_chain_builder(symbol,expiry="latest",oi_mode="full"):
344
+
345
+ if expiry == "latest":
346
+ dates = expiry_list(symbol, type="list")
347
+ if dates:
348
+ expiry = dates[0]
349
+ else:
350
+ return pd.DataFrame(), 0.0, ""
351
+
352
+ payload = nse_optionchain_scrapper(symbol)
353
+
354
+ if(oi_mode=='compact'):
355
+ col_names = ['CALLS_OI','CALLS_Chng in OI','CALLS_Volume','CALLS_IV','CALLS_LTP','CALLS_Net Chng','Strike Price','PUTS_OI','PUTS_Chng in OI','PUTS_Volume','PUTS_IV','PUTS_LTP','PUTS_Net Chng']
356
+ if(oi_mode=='full'):
357
+ col_names = ['CALLS_Chart','CALLS_OI','CALLS_Chng in OI','CALLS_Volume','CALLS_IV','CALLS_LTP','CALLS_Net Chng','CALLS_Bid Qty','CALLS_Bid Price','CALLS_Ask Price','CALLS_Ask Qty','Strike Price','PUTS_Bid Qty','PUTS_Bid Price','PUTS_Ask Price','PUTS_Ask Qty','PUTS_Net Chng','PUTS_LTP','PUTS_IV','PUTS_Volume','PUTS_Chng in OI','PUTS_OI','PUTS_Chart']
358
+ oi_data = pd.DataFrame(columns = col_names)
359
+
360
+ # We will populate these dynamically
361
+ rows_list = []
362
+
363
+ if 'expiryDates' not in payload:
364
+ # Fallback for new API structure
365
+ if(expiry=="latest"):
366
+ expiry = expiry_list(symbol, type="list")[0]
367
+ data_list = payload['data']
368
+ else:
369
+ # Legacy structure support
370
+ if(expiry=="latest"):
371
+ expiry = payload['records']['expiryDates'][0]
372
+ data_list = payload['records']['data']
373
+
374
+ for m in range(len(data_list)):
375
+ current_expiry_str = data_list[m].get('expiryDates') or data_list[m].get('expiryDate')
376
+ try:
377
+ # Convert both to date objects for robust comparison
378
+ if "-" in current_expiry_str:
379
+ parts = current_expiry_str.split("-")
380
+ if parts[1].isdigit(): fmt = "%d-%m-%Y"
381
+ else: fmt = "%d-%b-%Y"
382
+ curr_date = datetime.datetime.strptime(current_expiry_str, fmt).date()
383
+
384
+ parts_exp = expiry.split("-")
385
+ if parts_exp[1].isdigit(): fmt_exp = "%d-%m-%Y"
386
+ else: fmt_exp = "%d-%b-%Y"
387
+ exp_date = datetime.datetime.strptime(expiry, fmt_exp).date()
388
+ match = (curr_date == exp_date)
389
+ else:
390
+ match = (current_expiry_str == expiry)
391
+ except:
392
+ match = (current_expiry_str == expiry)
393
+
394
+ if match:
395
+ oi_row = {col: 0 for col in col_names}
396
+ oi_row['Strike Price'] = data_list[m]['strikePrice']
397
+
398
+ for side in ['CE', 'PE']:
399
+ prefix = f"{'CALLS' if side == 'CE' else 'PUTS'}_"
400
+ if side in data_list[m] and data_list[m][side] is not None:
401
+ d = data_list[m][side]
402
+ oi_row[prefix + 'OI'] = d.get('openInterest', 0)
403
+ oi_row[prefix + 'Chng in OI'] = d.get('changeinOpenInterest', 0)
404
+ oi_row[prefix + 'Volume'] = d.get('totalTradedVolume', 0)
405
+ oi_row[prefix + 'IV'] = d.get('impliedVolatility', 0)
406
+ oi_row[prefix + 'LTP'] = d.get('lastPrice', 0)
407
+ oi_row[prefix + 'Net Chng'] = d.get('change', 0)
408
+
409
+ if oi_mode == 'full':
410
+ # New API key mapping
411
+ oi_row[prefix + 'Bid Qty'] = d.get('buyQuantity1', d.get('bidQty', 0))
412
+ oi_row[prefix + 'Bid Price'] = d.get('buyPrice1', d.get('bidprice', 0))
413
+ oi_row[prefix + 'Ask Price'] = d.get('sellPrice1', d.get('askPrice', 0))
414
+ oi_row[prefix + 'Ask Qty'] = d.get('sellQuantity1', d.get('askQty', 0))
415
+ oi_row[prefix + 'Chart'] = 0
416
+
417
+ rows_list.append(oi_row)
418
+
419
+ oi_data = pd.DataFrame(rows_list)
420
+ timestamp = payload.get('timestamp', payload.get('records', {}).get('timestamp', ''))
421
+ underlyingValue = payload.get('underlyingValue', payload.get('records', {}).get('underlyingValue', 0))
422
+
423
+ # github.com/aeron7/nsepython issue #80: the current getSymbolDerivativesData
424
+ # payload carries no top-level (or 'records') underlyingValue at all -- it
425
+ # only lives inside each individual CE/PE leaf record. Dig it out of there
426
+ # if the top-level lookup above came back empty.
427
+ if not underlyingValue and data_list:
428
+ for entry in data_list:
429
+ for side in ('CE', 'PE'):
430
+ leaf = entry.get(side)
431
+ if leaf and leaf.get('underlyingValue'):
432
+ underlyingValue = leaf['underlyingValue']
433
+ break
434
+ if underlyingValue:
435
+ break
436
+
437
+ oi_data['time_stamp'] = timestamp
438
+ return oi_data, float(underlyingValue or 0), timestamp
439
+
440
+
441
+ def nse_quote_derivatives(symbol):
442
+ symbol = nsesymbolpurify(symbol)
443
+ if symbol.upper() in fnolist():
444
+ payload = nsefetch('https://www.nseindia.com/api/NextApi/apiClient/GetQuoteApi?functionName=getSymbolDerivativesData&symbol='+symbol)
445
+ return payload
446
+ else:
447
+ return {"error": f"{symbol} is not in derivatives list."}
448
+
449
+ def nse_quote(symbol,section=""):
450
+ #https://forum.unofficed.com/t/nsetools-get-quote-is-not-fetching-delivery-data-and-delivery-can-you-include-this-as-part-of-feature-request/1115/4
451
+ symbol = nsesymbolpurify(symbol)
452
+
453
+ if(section==""):
454
+ if any(x in symbol for x in indices):
455
+ payload = nsefetch('https://www.nseindia.com/api/NextApi/apiClient/GetQuoteApi?functionName=getSymbolDerivativesData&symbol='+symbol)
456
+ else:
457
+ payload = nsefetch('https://www.nseindia.com/api/NextApi/apiClient/GetQuoteApi?functionName=getSymbolData&marketType=N&series=EQ&symbol='+symbol)
458
+ return payload
459
+
460
+ if(section=="trade_info"):
461
+ # The old /api/quote-equity?section=trade_info route is dead on the
462
+ # live site (confirmed HTTP 403, even through the fully-warmed
463
+ # curl_cffi session round 1 built). But every category of data the
464
+ # old endpoint used to return is already present, just reshuffled,
465
+ # inside the NEW working GetQuoteApi?functionName=getSymbolData
466
+ # response this function's section=="" branch already fetches --
467
+ # confirmed field-by-field against the real, documented old
468
+ # response shape (EquityTradeInfo: marketDeptOrderBook.{bid,ask,
469
+ # tradeInfo,valueAtRisk} + securityWiseDP), so this is a pure
470
+ # remap/slice of data already being fetched, not a new network call.
471
+ #
472
+ # Two small fidelity gaps versus the old route, both because the
473
+ # source data for them no longer exists anywhere in the new
474
+ # response (not a mapping oversight):
475
+ # - noBlockDeals/bulkBlockDeals: the new endpoint carries no
476
+ # block-deal info at all -> defaulted to True/[] (i.e. "no
477
+ # block deals known"), not derived from a live block-deal
478
+ # check. Use nse_blockdeal()/get_blockdeals() directly if you
479
+ # need real block-deal data.
480
+ # - securityWiseDP.seriesRemarks: no equivalent field exists in
481
+ # the new response -> always None, same as it is for most
482
+ # symbols on the old route anyway.
483
+ payload = nsefetch('https://www.nseindia.com/api/NextApi/apiClient/GetQuoteApi?functionName=getSymbolData&marketType=N&series=EQ&symbol='+symbol)
484
+ eq = payload['equityResponse'][0]
485
+ ob = eq.get('orderBook', {})
486
+ md = eq.get('metaData', {})
487
+ ti = eq.get('tradeInfo', {})
488
+ pi = eq.get('priceInfo', {})
489
+ si = eq.get('secInfo', {})
490
+
491
+ bid = [{"price": ob.get(f"buyPrice{i}"), "quantity": ob.get(f"buyQuantity{i}")} for i in range(1, 6)]
492
+ ask = [{"price": ob.get(f"sellPrice{i}"), "quantity": ob.get(f"sellQuantity{i}")} for i in range(1, 6)]
493
+
494
+ return {
495
+ "noBlockDeals": True,
496
+ "bulkBlockDeals": [],
497
+ "marketDeptOrderBook": {
498
+ "totalBuyQuantity": ob.get("totalBuyQuantity"),
499
+ "totalSellQuantity": ob.get("totalSellQuantity"),
500
+ "open": md.get("open"),
501
+ "bid": bid,
502
+ "ask": ask,
503
+ "tradeInfo": {
504
+ "totalTradedVolume": ti.get("totalTradedVolume"),
505
+ "totalTradedValue": ti.get("totalTradedValue"),
506
+ "totalMarketCap": ti.get("totalMarketCap"),
507
+ "ffmc": ti.get("ffmc"),
508
+ "impactCost": ti.get("impactCost"),
509
+ "cmDailyVolatility": pi.get("cmDailyVolatility"),
510
+ "cmAnnualVolatility": pi.get("cmAnnualVolatility"),
511
+ "marketLot": ti.get("marketLot"),
512
+ "activeSeries": ti.get("series"),
513
+ },
514
+ "valueAtRisk": {
515
+ "securityVar": si.get("securityvar"),
516
+ "indexVar": si.get("indexvar"),
517
+ "varMargin": si.get("varMargin"),
518
+ "extremeLossMargin": si.get("extremelossMargin"),
519
+ "adhocMargin": si.get("adhocMargin"),
520
+ "applicableMargin": si.get("applicableMargin"),
521
+ },
522
+ },
523
+ "securityWiseDP": {
524
+ "quantityTraded": ti.get("quantitytraded"),
525
+ "deliveryQuantity": ti.get("deliveryquantity"),
526
+ "deliveryToTradedQuantity": ti.get("deliveryToTradedQuantity"),
527
+ "seriesRemarks": None,
528
+ "secWiseDelPosDate": ti.get("secwisedelposdate"),
529
+ },
530
+ }
531
+
532
+ if(section!=""):
533
+ # Any other section value (e.g. the old "preOpenMarket") still hits
534
+ # the dead /api/quote-equity&section= route -- not yet remapped to
535
+ # a working source. Only trade_info was confirmed+fixed this round.
536
+ payload = nsefetch('https://www.nseindia.com/api/quote-equity?symbol='+symbol+'&section='+section)
537
+ return payload
538
+ def nse_expirydetails(payload, i=0, symbol=None):
539
+ expiry_dates = []
540
+ if 'records' in payload:
541
+ expiry_dates = payload['records']['expiryDates']
542
+ elif 'expiryDates' in payload:
543
+ expiry_dates = payload['expiryDates']
544
+ elif 'data' in payload:
545
+ unique_dates = set()
546
+ for entry in payload['data']:
547
+ if 'expiryDate' in entry:
548
+ unique_dates.add(entry['expiryDate'])
549
+ expiry_dates = sorted(list(unique_dates), key=lambda x: datetime.datetime.strptime(x, "%d-%b-%Y"))
550
+
551
+ # Filter future dates
552
+ future_expiry_dates = []
553
+ if expiry_dates:
554
+ temp_dates = [datetime.datetime.strptime(date, "%d-%b-%Y").date() for date in expiry_dates]
555
+ future_expiry_dates = sorted([date.strftime("%d-%b-%Y") for date in temp_dates if date >= datetime.datetime.now().date()], key=lambda x: datetime.datetime.strptime(x, "%d-%b-%Y"))
556
+
557
+ # Fallback to expiry_list if i is out of range and we can determine the symbol
558
+ if i >= len(future_expiry_dates):
559
+ if not symbol and 'data' in payload and len(payload['data']) > 0:
560
+ # Try to extract symbol from payload data
561
+ first_entry = payload['data'][0]
562
+ symbol = first_entry.get('symbol')
563
+ if not symbol:
564
+ if 'CE' in first_entry and first_entry['CE']:
565
+ symbol = first_entry['CE'].get('underlying')
566
+ elif 'PE' in first_entry and first_entry['PE']:
567
+ symbol = first_entry['PE'].get('underlying')
568
+
569
+ if symbol:
570
+ dates = expiry_list(symbol, type="list")
571
+ if dates:
572
+ # Filter future dates from expiry_list as well
573
+ temp_dates = [datetime.datetime.strptime(date, "%d-%b-%Y").date() for date in dates]
574
+ future_expiry_dates = sorted([date.strftime("%d-%b-%Y") for date in temp_dates if date >= datetime.datetime.now().date()], key=lambda x: datetime.datetime.strptime(x, "%d-%b-%Y"))
575
+
576
+ if i >= len(future_expiry_dates):
577
+ return None, None
578
+
579
+ currentExpiry = future_expiry_dates[i]
580
+ currentExpiry_dt = datetime.datetime.strptime(currentExpiry, '%d-%b-%Y').date()
581
+ date_today = run_time.date()
582
+ dte = (currentExpiry_dt - date_today).days
583
+ return currentExpiry_dt, dte
584
+ def pcr(payload, inp=0):
585
+ ce_oi = 0
586
+ pe_oi = 0
587
+
588
+ # Identify the data and expiry dates based on structure
589
+ if 'records' in payload:
590
+ # Legacy structure
591
+ data_list = payload['records']['data']
592
+ expiry_dates = payload['records']['expiryDates']
593
+ elif 'data' in payload:
594
+ # New structure
595
+ data_list = payload['data']
596
+ # Extract unique sorted expiry dates from data
597
+ unique_dates = set()
598
+ for entry in data_list:
599
+ ed = entry.get('expiryDate') or entry.get('expiryDates')
600
+ if ed:
601
+ unique_dates.add(ed)
602
+ expiry_dates = sorted(list(unique_dates), key=lambda x: datetime.datetime.strptime(x, "%d-%m-%Y") if "-" in x and x.split("-")[1].isdigit() else datetime.datetime.strptime(x, "%d-%b-%Y"))
603
+ else:
604
+ # If payload is empty or unknown, we can't proceed without fetching
605
+ # But we need a symbol. Try to get it from payload if possible.
606
+ return 0.0
607
+
608
+ if not expiry_dates or inp >= len(expiry_dates):
609
+ # Requested index is outside the current payload's scope.
610
+ # Check if we can fetch more data for this specific symbol.
611
+ symbol = payload.get('symbol') or payload.get('records', {}).get('symbol')
612
+ if not symbol and 'data' in payload and len(payload['data']) > 0:
613
+ first = payload['data'][0]
614
+ symbol = first.get('symbol') or (first.get('CE') and first['CE'].get('underlying'))
615
+
616
+ if symbol and inp > 0:
617
+ # Fetch all expiries to find the target one
618
+ all_expiries = expiry_list(symbol, type="list")
619
+ if inp < len(all_expiries):
620
+ target = all_expiries[inp]
621
+ # Fetch specific expiry data using getOptionChainData
622
+ url = f'https://www.nseindia.com/api/NextApi/apiClient/GetQuoteApi?functionName=getOptionChainData&symbol={nsesymbolpurify(symbol)}&params=expiryDate={target}'
623
+ new_payload = nsefetch(url)
624
+ if new_payload and 'data' in new_payload:
625
+ for entry in new_payload['data']:
626
+ ce_oi += entry.get('CE', {}).get('openInterest', 0) if entry.get('CE') else 0
627
+ pe_oi += entry.get('PE', {}).get('openInterest', 0) if entry.get('PE') else 0
628
+ if ce_oi > 0: return pe_oi / ce_oi
629
+ return 0.0
630
+
631
+ target_expiry = expiry_dates[inp]
632
+
633
+ found_data = False
634
+ for i in data_list:
635
+ curr_exp = i.get('expiryDate') or i.get('expiryDates')
636
+ if curr_exp == target_expiry:
637
+ found_data = True
638
+ try:
639
+ if 'CE' in i and i['CE']:
640
+ ce_oi += i['CE'].get('openInterest', 0)
641
+ if 'PE' in i and i['PE']:
642
+ pe_oi += i['PE'].get('openInterest', 0)
643
+ except (KeyError, TypeError):
644
+ pass
645
+
646
+ # If we didn't find any data for the target expiry in the payload,
647
+ # it means the payload was filtered (e.g. by the scrapper). Fetch it now.
648
+ if not found_data:
649
+ symbol = payload.get('symbol') or payload.get('records', {}).get('symbol')
650
+ if symbol:
651
+ url = f'https://www.nseindia.com/api/NextApi/apiClient/GetQuoteApi?functionName=getOptionChainData&symbol={nsesymbolpurify(symbol)}&params=expiryDate={target_expiry}'
652
+ new_payload = nsefetch(url)
653
+ if new_payload and 'data' in new_payload:
654
+ for entry in new_payload['data']:
655
+ ce_oi += entry.get('CE', {}).get('openInterest', 0) if entry.get('CE') else 0
656
+ pe_oi += entry.get('PE', {}).get('openInterest', 0) if entry.get('PE') else 0
657
+
658
+ if ce_oi == 0:
659
+ return 0.0
660
+
661
+ return pe_oi / ce_oi
662
+
663
+ #forum.unofficed.com/t/unable-to-find-nse-quote-meta-api/702/4
664
+ #Refer https://forum.unofficed.com/t/changed-the-nse-quote-ltp-function/1276
665
+ def nse_quote_ltp(symbol,expiryDate="latest",optionType="-",strikePrice=0):
666
+ if(optionType!="-"):
667
+ payload = nse_quote_derivatives(symbol)
668
+ else:
669
+ if any(x in symbol for x in indices):
670
+ payload = nse_quote_derivatives(symbol)
671
+ else:
672
+ payload = nsefetch('https://www.nseindia.com/api/NextApi/apiClient/GetQuoteApi?functionName=getSymbolData&marketType=N&series=EQ&symbol='+symbol)
673
+
674
+ lastPrice = 0
675
+
676
+ if(optionType=="-"):
677
+ if 'equityResponse' in payload and len(payload['equityResponse']) > 0:
678
+ lastPrice = payload['equityResponse'][0]['orderBook']['lastPrice']
679
+ elif 'data' in payload and len(payload['data']) > 0:
680
+ # For indices, underlyingValue in derivative payload is the current index LTP
681
+ lastPrice = payload['data'][0].get('underlyingValue')
682
+ return lastPrice
683
+
684
+ meta = "Options"
685
+ if(optionType=="Fut"): meta = "Futures"
686
+ if(optionType=="PE"):optionType="Put"
687
+ if(optionType=="CE"):optionType="Call"
688
+
689
+ if(expiryDate=="latest") or (expiryDate=="next"):
690
+ i = 0 if expiryDate=="latest" else 1
691
+ expiry_dates = []
692
+
693
+ # Extract from new FNO payload structure
694
+ if 'data' in payload:
695
+ unique_dates = set()
696
+ for entry in payload['data']:
697
+ if 'expiryDate' in entry:
698
+ it = entry.get('instrumentType', '')
699
+ if (meta == "Futures" and "FUT" in it) or (meta == "Options" and "OPT" in it):
700
+ unique_dates.add(entry['expiryDate'])
701
+ expiry_dates = sorted(list(unique_dates), key=lambda x: datetime.datetime.strptime(x, "%d-%b-%Y"))
702
+
703
+ # Filter future dates
704
+ future_expiry_dates = []
705
+ if expiry_dates:
706
+ temp_dates = [datetime.datetime.strptime(date, "%d-%b-%Y").date() for date in expiry_dates]
707
+ future_expiry_dates = sorted([date.strftime("%d-%b-%Y") for date in temp_dates if date >= datetime.datetime.now().date()], key=lambda x: datetime.datetime.strptime(x, "%d-%b-%Y"))
708
+
709
+ # Fallback to expiry_list
710
+ if i >= len(future_expiry_dates):
711
+ dates = expiry_list(symbol, type="list")
712
+ if dates:
713
+ temp_dates = [datetime.datetime.strptime(date, "%d-%b-%Y").date() for date in dates]
714
+ future_expiry_dates = sorted([date.strftime("%d-%b-%Y") for date in temp_dates if date >= datetime.datetime.now().date()], key=lambda x: datetime.datetime.strptime(x, "%d-%b-%Y"))
715
+
716
+ if i < len(future_expiry_dates):
717
+ expiryDate = future_expiry_dates[i]
718
+
719
+
720
+ if(optionType!="-"):
721
+ data_list = payload.get('data', [])
722
+ for i in data_list:
723
+ # Check instrument type in identifier or metadata if present
724
+ if meta == "Futures":
725
+ is_match = "FUT" in i.get('instrumentType', '')
726
+ else:
727
+ is_match = "OPT" in i.get('instrumentType', '')
728
+
729
+ if is_match:
730
+ if(optionType=="Fut"):
731
+ if(i.get('expiryDate')==expiryDate):
732
+ lastPrice = i.get('lastPrice')
733
+ break
734
+
735
+ if((optionType=="Put")or(optionType=="Call")):
736
+ # Some APIs have optionType as 'PE'/'CE' or 'Put'/'Call'
737
+ p_opt_type = i.get('optionType')
738
+ if p_opt_type == "PE": p_opt_type = "Put"
739
+ if p_opt_type == "CE": p_opt_type = "Call"
740
+
741
+ if (i.get("expiryDate")==expiryDate):
742
+ if (p_opt_type==optionType):
743
+ # strikePrice in payload is often string with padding
744
+ try:
745
+ p_strike = float(str(i.get("strikePrice")).strip())
746
+ except:
747
+ p_strike = 0
748
+
749
+ if (p_strike==float(strikePrice)):
750
+ lastPrice = i.get('lastPrice')
751
+ break
752
+
753
+ return lastPrice
754
+
755
+ # print(nse_quote_ltp("RELIANCE"))
756
+ # print(nse_quote_ltp("RELIANCE","latest","Fut"))
757
+ # print(nse_quote_ltp("RELIANCE","next","Fut"))
758
+ # print(nse_quote_ltp("BANKNIFTY","latest","PE",32000))
759
+ # print(nse_quote_ltp("BANKNIFTY","next","PE",32000))
760
+ # print(nse_quote_ltp("BANKNIFTY","10-Jun-2021","PE",32000))
761
+ # print(nse_quote_ltp("BANKNIFTY","17-Jun-2021","PE",32000))
762
+ # print(nse_quote_ltp("RELIANCE","latest","PE",2300))
763
+ # print(nse_quote_ltp("RELIANCE","next","PE",2300))
764
+
765
+ def nse_quote_meta(symbol,expiryDate="latest",optionType="-",strikePrice=0):
766
+ if(optionType!="-"):
767
+ payload = nse_quote_derivatives(symbol)
768
+ else:
769
+ if any(x in symbol for x in indices):
770
+ payload = nse_quote_derivatives(symbol)
771
+ else:
772
+ payload = nsefetch('https://www.nseindia.com/api/NextApi/apiClient/GetQuoteApi?functionName=getSymbolData&marketType=N&series=EQ&symbol='+symbol)
773
+
774
+ metadata = {}
775
+
776
+ if(optionType=="-"):
777
+ if 'equityResponse' in payload and len(payload['equityResponse']) > 0:
778
+ metadata = payload['equityResponse'][0].get('metaData', {})
779
+ return metadata
780
+
781
+ meta = "Options"
782
+ if(optionType=="Fut"): meta = "Futures"
783
+ if(optionType=="PE"):optionType="Put"
784
+ if(optionType=="CE"):optionType="Call"
785
+
786
+ if(expiryDate=="latest") or (expiryDate=="next"):
787
+ i = 0 if expiryDate=="latest" else 1
788
+ expiry_dates = []
789
+ if 'data' in payload:
790
+ unique_dates = set()
791
+ for entry in payload['data']:
792
+ if 'expiryDate' in entry:
793
+ it = entry.get('instrumentType', '')
794
+ if (meta == "Futures" and "FUT" in it) or (meta == "Options" and "OPT" in it):
795
+ unique_dates.add(entry['expiryDate'])
796
+ expiry_dates = sorted(list(unique_dates), key=lambda x: datetime.datetime.strptime(x, "%d-%b-%Y"))
797
+
798
+ future_expiry_dates = []
799
+ if expiry_dates:
800
+ temp_dates = [datetime.datetime.strptime(date, "%d-%b-%Y").date() for date in expiry_dates]
801
+ future_expiry_dates = sorted([date.strftime("%d-%b-%Y") for date in temp_dates if date >= datetime.datetime.now().date()], key=lambda x: datetime.datetime.strptime(x, "%d-%b-%Y"))
802
+
803
+ if i >= len(future_expiry_dates):
804
+ dates = expiry_list(symbol, type="list")
805
+ if dates:
806
+ temp_dates = [datetime.datetime.strptime(date, "%d-%b-%Y").date() for date in dates]
807
+ future_expiry_dates = sorted([date.strftime("%d-%b-%Y") for date in temp_dates if date >= datetime.datetime.now().date()], key=lambda x: datetime.datetime.strptime(x, "%d-%b-%Y"))
808
+
809
+ if i < len(future_expiry_dates):
810
+ expiryDate = future_expiry_dates[i]
811
+
812
+ # print(f"DEBUG: Calculated expiryDate={expiryDate}, meta={meta}, optionType={optionType}")
813
+
814
+ if(optionType!="-"):
815
+ data_list = payload.get('data', [])
816
+ # print(f"DEBUG: Searching in {len(data_list)} items")
817
+ for i in data_list:
818
+ if meta == "Futures":
819
+ is_match = "FUT" in i.get('instrumentType', '')
820
+ else:
821
+ is_match = "OPT" in i.get('instrumentType', '')
822
+
823
+ if is_match:
824
+ if(optionType=="Fut"):
825
+ if(i.get('expiryDate')==expiryDate):
826
+ metadata = i
827
+ break
828
+
829
+ if((optionType=="Put")or(optionType=="Call")):
830
+ p_opt_type = i.get('optionType')
831
+ if p_opt_type == "PE": p_opt_type = "Put"
832
+ if p_opt_type == "CE": p_opt_type = "Call"
833
+
834
+ if (i.get("expiryDate")==expiryDate):
835
+ if (p_opt_type==optionType):
836
+ try:
837
+ p_strike = float(str(i.get("strikePrice")).strip())
838
+ except:
839
+ p_strike = 0
840
+
841
+ if (p_strike==float(strikePrice)):
842
+ metadata = i
843
+ break
844
+
845
+ return metadata
846
+
847
+ def nse_optionchain_ltp(payload,strikePrice,optionType,inp=0,intent=""):
848
+ expiry_dates = payload['records']['expiryDates']
849
+ expiry_dates = [datetime.datetime.strptime(date, "%d-%b-%Y").date() for date in expiry_dates]
850
+ expiry_dates = [date.strftime("%d-%b-%Y") for date in expiry_dates if date >= datetime.datetime.now().date()]
851
+ expiryDate=expiry_dates[inp]
852
+ for x in range(len(payload['records']['data'])):
853
+ if((payload['records']['data'][x]['strikePrice']==strikePrice) & (payload['records']['data'][x]['expiryDate']==expiryDate)):
854
+ if(intent==""): return payload['records']['data'][x][optionType]['lastPrice']
855
+ if(intent=="sell"): return payload['records']['data'][x][optionType]['bidprice']
856
+ if(intent=="buy"): return payload['records']['data'][x][optionType]['askPrice']
857
+
858
+ def nse_eq(symbol):
859
+ symbol = nsesymbolpurify(symbol)
860
+ try:
861
+ payload = nsefetch('https://www.nseindia.com/api/quote-equity?symbol='+symbol)
862
+ try:
863
+ if(payload['error']=={}):
864
+ print("Please use nse_fno() function to reduce latency.")
865
+ payload = nsefetch('https://www.nseindia.com/api/quote-derivative?symbol='+symbol)
866
+ except:
867
+ pass
868
+ except (KeyError, NSEEndpointError):
869
+ # /api/quote-equity is retired on the live site (confirmed HTTP 403,
870
+ # Akamai/WAF "Access Denied" page, as of 2026) with no indication it
871
+ # is coming back. The newer NextApi GetQuoteApi endpoint carries the
872
+ # same underlying data (just in a different JSON shape - data lives
873
+ # under payload['equityResponse'][0] instead of payload['priceInfo']/
874
+ # payload['info']) so we fall back to that instead of returning {}.
875
+ logging.warning(
876
+ "nse_eq(%s): /api/quote-equity is retired; returning data from "
877
+ "the newer NextApi quote endpoint instead (see nse_quote() - the "
878
+ "JSON shape differs from the old quote-equity response).",
879
+ symbol,
880
+ )
881
+ payload = nse_quote(symbol)
882
+ return payload
883
+
884
+
885
+ def nse_fno(symbol):
886
+ symbol = nsesymbolpurify(symbol)
887
+ try:
888
+ payload = nsefetch('https://www.nseindia.com/api/quote-derivative?symbol='+symbol)
889
+ try:
890
+ if(payload['error']=={}):
891
+ print("Please use nse_eq() function to reduce latency.")
892
+ payload = nsefetch('https://www.nseindia.com/api/quote-equity?symbol='+symbol)
893
+ except KeyError:
894
+ pass
895
+ except (KeyError, NSEEndpointError):
896
+ # /api/quote-derivative is likewise retired (confirmed HTTP 404 on
897
+ # the live site). getSymbolDerivativesData via nse_quote_derivatives()
898
+ # is the working replacement (different JSON shape: a flat 'data'
899
+ # list of per-strike CE/PE records instead of records/underlyingValue).
900
+ logging.warning(
901
+ "nse_fno(%s): /api/quote-derivative is retired; returning data "
902
+ "from the newer NextApi derivatives endpoint instead (see "
903
+ "nse_quote_derivatives() - the JSON shape differs).",
904
+ symbol,
905
+ )
906
+ payload = nse_quote_derivatives(symbol)
907
+ return payload
908
+
909
+ def quote_equity(symbol):
910
+ return nse_eq(symbol)
911
+
912
+ def quote_derivative(symbol):
913
+ return nse_fno(symbol)
914
+
915
+ def option_chain(symbol):
916
+ return nse_optionchain_scrapper(symbol)
917
+
918
+ def nse_holidays(type="trading"):
919
+ if(type=="clearing"):
920
+ payload = nsefetch('https://www.nseindia.com/api/holiday-master?type=clearing')
921
+ if(type=="trading"):
922
+ payload = nsefetch('https://www.nseindia.com/api/holiday-master?type=trading')
923
+ return payload
924
+
925
+ def holiday_master(type="trading"):
926
+ return nse_holidays(type)
927
+
928
+ def nse_results(index="equities",period="Quarterly"):
929
+ if(index=="equities") or (index=="debt") or (index=="sme"):
930
+ if(period=="Quarterly") or (period=="Annual")or (period=="Half-Yearly")or (period=="Others"):
931
+ payload = nsefetch('https://www.nseindia.com/api/corporates-financial-results?index='+index+'&period='+period)
932
+ return pd.json_normalize(payload)
933
+ else:
934
+ print("Give Correct Period Input")
935
+ else:
936
+ print("Give Correct Index Input")
937
+
938
+ def nse_events():
939
+ output = nsefetch('https://www.nseindia.com/api/event-calendar')
940
+ return pd.json_normalize(output)
941
+
942
+ def nse_past_results(symbol):
943
+ symbol = nsesymbolpurify(symbol)
944
+ return nsefetch('https://www.nseindia.com/api/results-comparision?symbol='+symbol)
945
+
946
+ def expiry_list(symbol, type=""):
947
+ logging.info("Getting Expiry List of: " + symbol)
948
+ symbol = nsesymbolpurify(symbol)
949
+ url = f'https://www.nseindia.com/api/NextApi/apiClient/GetQuoteApi?functionName=getOptionChainDropdown&symbol={symbol}'
950
+ payload = nsefetch(url)
951
+
952
+ if not payload or 'expiryDates' not in payload:
953
+ return [] if type == "list" else pd.DataFrame()
954
+
955
+ expiry_dates = payload['expiryDates']
956
+
957
+ # Format dates from DD-MM-YYYY to DD-Mon-YYYY
958
+ formatted_dates = []
959
+ for d in expiry_dates:
960
+ try:
961
+ dt = datetime.datetime.strptime(d, "%d-%m-%Y")
962
+ formatted_dates.append(dt.strftime("%d-%b-%Y"))
963
+ except:
964
+ formatted_dates.append(d)
965
+
966
+ if type == "list":
967
+ return formatted_dates
968
+ else:
969
+ # If anything other than "list" is provided (like "df", "pandas", or default), return DataFrame
970
+ return pd.DataFrame({'Date': formatted_dates})
971
+
972
+
973
+ def nse_custom_function_secfno(symbol,attribute="lastPrice"):
974
+ positions = nsefetch('https://www.nseindia.com/api/equity-stockIndices?index=SECURITIES%20IN%20F%26O')
975
+ endp = len(positions['data'])
976
+ for x in range(0, endp):
977
+ if(positions['data'][x]['symbol']==symbol.upper()):
978
+ return positions['data'][x][attribute]
979
+
980
+ def nse_blockdeal():
981
+ payload = nsefetch('https://nseindia.com/api/block-deal')
982
+ return payload
983
+
984
+ def nse_marketStatus():
985
+ payload = nsefetch('https://nseindia.com/api/marketStatus')
986
+ return payload
987
+
988
+ def nse_circular(mode="latest"):
989
+ # The old mode="latest" path (https://nseindia.com/api/latest-circular,
990
+ # no `www.`) is dead on the live site: it returns HTTP 200 but a bare
991
+ # {'error': True, 'status': 500} JSON body -- confirmed this is NOT an
992
+ # Akamai bot-challenge (no injected script, no 403/503), just NSE's own
993
+ # "this route doesn't exist" response. NSE renamed the circulars page
994
+ # itself from /resources/circulars to
995
+ # /resources/exchange-communication-circulars, and a Playwright network
996
+ # capture on that live page shows it calling
997
+ # https://www.nseindia.com/api/circulars?fromDate=DD-MM-YYYY&toDate=DD-MM-YYYY
998
+ # (with `www.`) -- the SAME URL this function's own mode!="latest"
999
+ # branch already used and which was independently confirmed live
1000
+ # (zero params defaults to NSE's own last-7-days/150-record window).
1001
+ # Fix: route "latest" to that same working endpoint too, instead of the
1002
+ # dead no-www path.
1003
+ if(mode=="latest"):
1004
+ payload = nsefetch('https://www.nseindia.com/api/circulars')
1005
+ else:
1006
+ payload = nsefetch('https://www.nseindia.com/api/circulars')
1007
+ return payload
1008
+
1009
+ def nse_fiidii(mode="pandas"):
1010
+ try:
1011
+ if(mode=="pandas"):
1012
+ return pd.DataFrame(nsefetch('https://www.nseindia.com/api/fiidiiTradeReact'))
1013
+ else:
1014
+ return nsefetch('https://www.nseindia.com/api/fiidiiTradeReact')
1015
+ except:
1016
+ logger.info("Pandas is not working for some reason.")
1017
+ return nsefetch('https://www.nseindia.com/api/fiidiiTradeReact')
1018
+
1019
+ def nsetools_get_quote(symbol):
1020
+ payload = nsefetch('https://www.nseindia.com/api/equity-stockIndices?index=SECURITIES%20IN%20F%26O')
1021
+ for m in range(len(payload['data'])):
1022
+ if(payload['data'][m]['symbol']==symbol.upper()):
1023
+ return payload['data'][m]
1024
+
1025
+
1026
+ def _nse_index_data():
1027
+ # iislliveblob.niftyindices.com is a dead host (confirmed live: NXDOMAIN,
1028
+ # twice). /api/allIndices on the main site carries the same live index
1029
+ # quotes (139 indices as of this writing, including pe/pb/dy per index).
1030
+ # Its per-row key is 'index' (e.g. "NIFTY 50"), not the old 'indexName' --
1031
+ # alias it so nse_get_index_list()/nse_get_index_quote() below (and any
1032
+ # external code doing the same lookup) keep working unchanged.
1033
+ payload = nsefetch("https://www.nseindia.com/api/allIndices")
1034
+ rows = payload.get("data", [])
1035
+ for row in rows:
1036
+ row.setdefault("indexName", row.get("index"))
1037
+ return rows
1038
+
1039
+
1040
+ def nse_index():
1041
+ return pd.DataFrame(_nse_index_data())
1042
+
1043
+ def nse_get_index_list():
1044
+ return pd.DataFrame(_nse_index_data())["indexName"].tolist()
1045
+
1046
+ def nse_get_index_quote(index):
1047
+ for row in _nse_index_data():
1048
+ if row["indexName"] == index.upper():
1049
+ return row
1050
+
1051
+ def nse_get_advances_declines(mode="pandas"):
1052
+ try:
1053
+ if(mode=="pandas"):
1054
+ positions = nsefetch('https://www.nseindia.com/api/equity-stockIndices?index=SECURITIES%20IN%20F%26O')
1055
+ return pd.DataFrame(positions['data'])
1056
+ else:
1057
+ return nsefetch('https://www.nseindia.com/api/equity-stockIndices?index=SECURITIES%20IN%20F%26O')
1058
+ except:
1059
+ logger.info("Pandas is not working for some reason.")
1060
+ return nsefetch('https://www.nseindia.com/api/equity-stockIndices?index=SECURITIES%20IN%20F%26O')
1061
+
1062
+ def nse_get_top_losers():
1063
+ positions = nsefetch('https://www.nseindia.com/api/equity-stockIndices?index=SECURITIES%20IN%20F%26O')
1064
+ df = pd.DataFrame(positions['data'])
1065
+ df = df.sort_values(by="pChange")
1066
+ return df.head(5)
1067
+
1068
+ def nse_get_top_gainers():
1069
+ positions = nsefetch('https://www.nseindia.com/api/equity-stockIndices?index=SECURITIES%20IN%20F%26O')
1070
+ df = pd.DataFrame(positions['data'])
1071
+ df = df.sort_values(by="pChange" , ascending = False)
1072
+ return df.head(5)
1073
+
1074
+ def nse_get_fno_lot_sizes(symbol="all",mode="list"):
1075
+ # github.com/aeron7/nsepythonserver issue #4 ("lot sizes not working"):
1076
+ # two stacked bugs, confirmed live. (1) archives.nseindia.com silently
1077
+ # redirects this specific file to an unrelated PDF circular these days
1078
+ # (NSE's archives -> nsearchives host migration left a stale redirect on
1079
+ # just this path) -- nsearchives.nseindia.com/content/fo/fo_mktlots.csv
1080
+ # is the real, current location, confirmed live with the exact same CSV
1081
+ # shape. (2) plain `requests.get()` against nsearchives.nseindia.com
1082
+ # hangs to a read-timeout (confirmed live) -- it needs the same
1083
+ # curl_cffi TLS impersonation as the rest of the site now.
1084
+ url="https://nsearchives.nseindia.com/content/fo/fo_mktlots.csv"
1085
+
1086
+ if(mode=="list"):
1087
+ s = _nse_fetch_csv_text(url)
1088
+ res_dict = {}
1089
+ for line in s.split('\n'):
1090
+ if line != '' and re.search(',', line) and (line.casefold().find('symbol') == -1):
1091
+ (code, name) = [x.strip() for x in line.split(',')[1:3]]
1092
+ res_dict[code] = int(name)
1093
+ if(symbol=="all"):
1094
+ return res_dict
1095
+ if(symbol!=""):
1096
+ return res_dict[symbol.upper()]
1097
+
1098
+ if(mode=="pandas"):
1099
+ payload = pd.read_csv(io.StringIO(_nse_fetch_csv_text(url)))
1100
+ if(symbol=="all"):
1101
+ return payload
1102
+ else:
1103
+ payload = payload[(payload.iloc[:, 1] == symbol.upper())]
1104
+ return payload
1105
+
1106
+ def whoistheboss():
1107
+ return "subhash"
1108
+
1109
+ def indiavix():
1110
+ payload = nsefetch("https://www.nseindia.com/api/allIndices")
1111
+ for x in range(0, len(payload["data"])):
1112
+ if(payload["data"][x]["index"]=="INDIA VIX"):
1113
+ return payload["data"][x]["last"]
1114
+
1115
+ def index_info(index):
1116
+ payload = nsefetch("https://www.nseindia.com/api/allIndices")
1117
+ for x in range(0, len(payload["data"])):
1118
+ if(payload["data"][x]["index"]==index):
1119
+ return payload["data"][x]
1120
+
1121
+ import math
1122
+ from scipy.stats import norm
1123
+
1124
+ def black_scholes_dexter(S0,X,t,σ="",r=10,q=0.0,td=365):
1125
+
1126
+ if(σ==""):σ =indiavix()
1127
+
1128
+ S0,X,σ,r,q,t = float(S0),float(X),float(σ/100),float(r/100),float(q/100),float(t/td)
1129
+ #https://unofficed.com/black-scholes-model-options-calculator-google-sheet/
1130
+
1131
+ d1 = (math.log(S0/X)+(r-q+0.5*σ**2)*t)/(σ*math.sqrt(t))
1132
+ #stackoverflow.com/questions/34258537/python-typeerror-unsupported-operand-types-for-float-and-int
1133
+
1134
+ #stackoverflow.com/questions/809362/how-to-calculate-cumulative-normal-distribution
1135
+ Nd1 = (math.exp((-d1**2)/2))/math.sqrt(2*math.pi)
1136
+ d2 = d1-σ*math.sqrt(t)
1137
+ Nd2 = norm.cdf(d2)
1138
+ call_theta =(-((S0*σ*math.exp(-q*t))/(2*math.sqrt(t))*(1/(math.sqrt(2*math.pi)))*math.exp(-(d1*d1)/2))-(r*X*math.exp(-r*t)*norm.cdf(d2))+(q*math.exp(-q*t)*S0*norm.cdf(d1)))/td
1139
+ put_theta =(-((S0*σ*math.exp(-q*t))/(2*math.sqrt(t))*(1/(math.sqrt(2*math.pi)))*math.exp(-(d1*d1)/2))+(r*X*math.exp(-r*t)*norm.cdf(-d2))-(q*math.exp(-q*t)*S0*norm.cdf(-d1)))/td
1140
+ call_premium =math.exp(-q*t)*S0*norm.cdf(d1)-X*math.exp(-r*t)*norm.cdf(d1-σ*math.sqrt(t))
1141
+ put_premium =X*math.exp(-r*t)*norm.cdf(-d2)-math.exp(-q*t)*S0*norm.cdf(-d1)
1142
+ call_delta =math.exp(-q*t)*norm.cdf(d1)
1143
+ put_delta =math.exp(-q*t)*(norm.cdf(d1)-1)
1144
+ gamma =(math.exp(-r*t)/(S0*σ*math.sqrt(t)))*(1/(math.sqrt(2*math.pi)))*math.exp(-(d1*d1)/2)
1145
+ vega = ((1/100)*S0*math.exp(-r*t)*math.sqrt(t))*(1/(math.sqrt(2*math.pi))*math.exp(-(d1*d1)/2))
1146
+ call_rho =(1/100)*X*t*math.exp(-r*t)*norm.cdf(d2)
1147
+ put_rho =(-1/100)*X*t*math.exp(-r*t)*norm.cdf(-d2)
1148
+
1149
+ return call_theta,put_theta,call_premium,put_premium,call_delta,put_delta,gamma,vega,call_rho,put_rho
1150
+
1151
+ def equity_history_virgin(symbol,series,start_date,end_date):
1152
+ #url="https://www.nseindia.com/api/historical/cm/equity?symbol="+symbol+"&series=[%22"+series+"%22]&from="+str(start_date)+"&to="+str(end_date)+""
1153
+ # NOTE: the original /api/historical/cm/equity route is retired on the
1154
+ # live site (confirmed HTTP 503 as of 2026, even via curl_cffi). NSE's
1155
+ # replacement is /api/historicalOR/cm/equity -- same query params, same
1156
+ # response shape (payload['data'] records with CH_TIMESTAMP/
1157
+ # CH_CLOSING_PRICE/etc), confirmed live, so this is a plain host-path
1158
+ # swap with no downstream parsing changes needed.
1159
+ url = 'https://www.nseindia.com/api/historicalOR/cm/equity?symbol=' + symbol + '&series=["' + series + '"]&from=' + start_date + '&to=' + end_date
1160
+
1161
+ payload = nsefetch(url)
1162
+ return pd.DataFrame.from_records(payload["data"])
1163
+
1164
+ # You shall see beautiful use the logger function.
1165
+ def equity_history(symbol,series,start_date,end_date):
1166
+ #We are getting the input in text. So it is being converted to Datetime object from String.
1167
+ start_date = datetime.datetime.strptime(start_date, "%d-%m-%Y")
1168
+ end_date = datetime.datetime.strptime(end_date, "%d-%m-%Y")
1169
+ logging.info("Starting Date: "+str(start_date))
1170
+ logging.info("Ending Date: "+str(end_date))
1171
+
1172
+ #We are calculating the difference between the days
1173
+ diff = end_date-start_date
1174
+ logging.info("Total Number of Days: "+str(diff.days))
1175
+ logging.info("Total FOR Loops in the program: "+str(int(diff.days/40)))
1176
+ logging.info("Remainder Loop: " + str(diff.days-(int(diff.days/40)*40)))
1177
+
1178
+
1179
+ total=pd.DataFrame()
1180
+ for i in range (0,int(diff.days/40)):
1181
+
1182
+ temp_date = (start_date+datetime.timedelta(days=(40))).strftime("%d-%m-%Y")
1183
+ start_date = datetime.datetime.strftime(start_date, "%d-%m-%Y")
1184
+
1185
+ logging.info("Loop = "+str(i))
1186
+ logging.info("====")
1187
+ logging.info("Starting Date: "+str(start_date))
1188
+ logging.info("Ending Date: "+str(temp_date))
1189
+ logging.info("====")
1190
+
1191
+ #total=total.append(equity_history_virgin(symbol,series,start_date,temp_date))
1192
+ #total=total.concat(equity_history_virgin(symbol,series,start_date,temp_date))
1193
+ total = pd.concat([total, equity_history_virgin(symbol, series, start_date, temp_date)])
1194
+
1195
+
1196
+ logging.info("Length of the Table: "+ str(len(total)))
1197
+
1198
+ #Preparation for the next loop
1199
+ start_date = datetime.datetime.strptime(temp_date, "%d-%m-%Y")
1200
+
1201
+
1202
+ start_date = datetime.datetime.strftime(start_date, "%d-%m-%Y")
1203
+ end_date = datetime.datetime.strftime(end_date, "%d-%m-%Y")
1204
+
1205
+ logging.info("End Loop")
1206
+ logging.info("====")
1207
+ logging.info("Starting Date: "+str(start_date))
1208
+ logging.info("Ending Date: "+str(end_date))
1209
+ logging.info("====")
1210
+
1211
+ #total=total.append(equity_history_virgin(symbol,series,start_date,end_date))
1212
+ #total=total.concat(equity_history_virgin(symbol,series,start_date,end_date))
1213
+ total = pd.concat([total, equity_history_virgin(symbol, series, start_date, end_date)])
1214
+
1215
+
1216
+ logging.info("Finale")
1217
+ logging.info("Length of the Total Dataset: "+ str(len(total)))
1218
+ payload = total.iloc[::-1].reset_index(drop=True)
1219
+ return payload
1220
+
1221
+ def derivative_history_virgin(symbol,start_date,end_date,instrumentType,expiry_date,strikePrice="",optionType=""):
1222
+
1223
+ instrumentType = instrumentType.lower()
1224
+
1225
+ if(instrumentType=="options"):
1226
+ instrumentType="OPTSTK"
1227
+ if("NIFTY" in symbol): instrumentType="OPTIDX"
1228
+
1229
+ if(instrumentType=="futures"):
1230
+ instrumentType="FUTSTK"
1231
+ if("NIFTY" in symbol): instrumentType="FUTIDX"
1232
+
1233
+
1234
+ #if(((instrumentType=="OPTIDX")or (instrumentType=="OPTSTK")) and (expiry_date!="")):
1235
+ if(strikePrice!=""):
1236
+ strikePrice = "%.2f" % strikePrice
1237
+ strikePrice = str(strikePrice)
1238
+
1239
+ # /api/historical/fo/derivatives is retired (HTTP 503 live); the
1240
+ # confirmed-working replacement is /api/historicalOR/fo/derivatives with
1241
+ # the same query params and response shape.
1242
+ nsefetch_url = "https://www.nseindia.com/api/historicalOR/fo/derivatives?&from="+str(start_date)+"&to="+str(end_date)+"&optionType="+optionType+"&strikePrice="+strikePrice+"&expiryDate="+expiry_date+"&instrumentType="+instrumentType+"&symbol="+symbol+""
1243
+ payload = nsefetch(nsefetch_url)
1244
+ logging.info(nsefetch_url)
1245
+ logging.info(payload)
1246
+ return pd.DataFrame.from_records(payload["data"])
1247
+
1248
+ def derivative_history(symbol,start_date,end_date,instrumentType,expiry_date,strikePrice="",optionType=""):
1249
+ #We are getting the input in text. So it is being converted to Datetime object from String.
1250
+ start_date = datetime.datetime.strptime(start_date, "%d-%m-%Y")
1251
+ end_date = datetime.datetime.strptime(end_date, "%d-%m-%Y")
1252
+ logging.info("Starting Date: "+str(start_date))
1253
+ logging.info("Ending Date: "+str(end_date))
1254
+
1255
+ #We are calculating the difference between the days
1256
+ diff = end_date-start_date
1257
+ logging.info("Total Number of Days: "+str(diff.days))
1258
+ logging.info("Total FOR Loops in the program: "+str(int(diff.days/40)))
1259
+ logging.info("Remainder Loop: " + str(diff.days-(int(diff.days/40)*40)))
1260
+
1261
+
1262
+ total=pd.DataFrame()
1263
+ for i in range (0,int(diff.days/40)):
1264
+
1265
+ temp_date = (start_date+datetime.timedelta(days=(40))).strftime("%d-%m-%Y")
1266
+ start_date = datetime.datetime.strftime(start_date, "%d-%m-%Y")
1267
+
1268
+ logging.info("Loop = "+str(i))
1269
+ logging.info("====")
1270
+ logging.info("Starting Date: "+str(start_date))
1271
+ logging.info("Ending Date: "+str(temp_date))
1272
+ logging.info("====")
1273
+
1274
+ #total=total.append(derivative_history_virgin(symbol,start_date,temp_date,instrumentType,expiry_date,strikePrice,optionType))
1275
+ #total=total.concat([total, derivative_history_virgin(symbol,start_date,temp_date,instrumentType,expiry_date,strikePrice,optionType)])
1276
+ total = pd.concat([total, derivative_history_virgin(symbol, start_date, temp_date, instrumentType, expiry_date, strikePrice, optionType)])
1277
+
1278
+
1279
+ logging.info("Length of the Table: "+ str(len(total)))
1280
+
1281
+ #Preparation for the next loop
1282
+ start_date = datetime.datetime.strptime(temp_date, "%d-%m-%Y")
1283
+
1284
+
1285
+ start_date = datetime.datetime.strftime(start_date, "%d-%m-%Y")
1286
+ end_date = datetime.datetime.strftime(end_date, "%d-%m-%Y")
1287
+
1288
+ logging.info("End Loop")
1289
+ logging.info("====")
1290
+ logging.info("Starting Date: "+str(start_date))
1291
+ logging.info("Ending Date: "+str(end_date))
1292
+ logging.info("====")
1293
+
1294
+ #total=total.append(derivative_history_virgin(symbol,start_date,end_date,instrumentType,expiry_date,strikePrice,optionType))
1295
+ #total = total.concat([total, derivative_history_virgin(symbol,start_date,end_date,instrumentType,expiry_date,strikePrice,optionType)])
1296
+ total = pd.concat([total, derivative_history_virgin(symbol, start_date, end_date, instrumentType, expiry_date, strikePrice, optionType)])
1297
+
1298
+
1299
+
1300
+ logging.info("Finale")
1301
+ logging.info("Length of the Total Dataset: "+ str(len(total)))
1302
+ payload = total.iloc[::-1].reset_index(drop=True)
1303
+ return payload
1304
+
1305
+
1306
+ def expiry_history(symbol,start_date="",end_date="",type="options"):
1307
+ if(end_date==""):end_date=end_date
1308
+ # Same retirement as derivative_history_virgin()/equity_history_virgin()
1309
+ # above -- /api/historical/* is gone, /api/historicalOR/* is the working
1310
+ # replacement with an identical response shape.
1311
+ nsefetch_url = "https://www.nseindia.com/api/historicalOR/fo/derivatives/meta?&from="+start_date+"&to="+end_date+"&symbol="+symbol+""
1312
+ payload = nsefetch(nsefetch_url)
1313
+
1314
+ #print(payload)
1315
+
1316
+ for key, value in payload['expiryDatesByInstrument'].items():
1317
+ if type.lower() == "options" and "OPT" in key:
1318
+ payload_data = payload['expiryDatesByInstrument'][key]
1319
+ break
1320
+ elif type.lower() == "futures" and "FUT" in key:
1321
+ payload_data = payload['expiryDatesByInstrument'][key]
1322
+ break
1323
+
1324
+ # Convert start_date and end_date to datetime objects
1325
+ start_date = datetime.datetime.strptime(start_date, "%d-%m-%Y")
1326
+ end_date = datetime.datetime.strptime(end_date, "%d-%m-%Y")
1327
+
1328
+ # Initialize an empty list to store filtered dates
1329
+ filtered_date_payload = []
1330
+
1331
+ # Initialize a flag to check if the first date after end_date has been added
1332
+ added_after_end_date = False
1333
+
1334
+ # Iterate through date_payload and filter dates within the range
1335
+ for date_str in payload_data:
1336
+ date_obj = datetime.datetime.strptime(date_str, "%d-%b-%Y")
1337
+ if start_date <= date_obj <= end_date:
1338
+ filtered_date_payload.append(date_str)
1339
+ elif date_obj > end_date and not added_after_end_date:
1340
+ filtered_date_payload.append(date_str)
1341
+ added_after_end_date = True
1342
+
1343
+ return filtered_date_payload
1344
+
1345
+ # # Nifty Indicies Site
1346
+ #
1347
+ # niftyindices.com is a completely separate host/site from nseindia.com (no
1348
+ # Akamai Bot Manager symptoms observed here) -- but it was fully redesigned
1349
+ # onto a different CMS at some point: the old ASP.NET WebMethods under
1350
+ # `niftyindices.com/Backpage.aspx/*` (returning `{"d": "<json string>"}`) are
1351
+ # gone, and POSTing to them now just returns the site's homepage HTML, which
1352
+ # is exactly github.com/aeron7/nsepython issue #78's
1353
+ # `JSONDecodeError: Expecting value: line 1 column 2 (char 1)`.
1354
+ #
1355
+ # The working replacement (confirmed live) is `www.niftyindices.com/BackPage/*`
1356
+ # (note: `www.` + `BackPage` not `Backpage.aspx`), which wants a short session
1357
+ # warm-up first (visiting the historical-data report page) and returns a
1358
+ # direct JSON array rather than the old `{"d": "..."}` wrapper.
1359
+
1360
+ niftyindices_headers = {
1361
+ 'Accept': 'application/json, text/javascript, */*; q=0.01',
1362
+ 'Accept-Language': 'en-US,en;q=0.9,hi;q=0.8',
1363
+ 'Content-Type': 'application/json; charset=UTF-8',
1364
+ 'Origin': 'https://www.niftyindices.com',
1365
+ 'Referer': 'https://www.niftyindices.com/reports/historical-data',
1366
+ 'User-Agent': 'Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/130.0.0.0 Safari/537.36',
1367
+ 'X-Requested-With': 'XMLHttpRequest',
1368
+ 'sec-ch-ua': '"Not;A=Brand";v="8", "Chromium";v="130", "Google Chrome";v="130"',
1369
+ 'sec-ch-ua-mobile': '?0',
1370
+ 'sec-ch-ua-platform': '"Windows"',
1371
+ }
1372
+
1373
+ _niftyindices_session = None
1374
+ _niftyindices_warmed = False
1375
+
1376
+
1377
+ def _get_niftyindices_session():
1378
+ global _niftyindices_session, _niftyindices_warmed
1379
+ if _niftyindices_session is None:
1380
+ _niftyindices_session = requests.Session()
1381
+ if not _niftyindices_warmed:
1382
+ try:
1383
+ _niftyindices_session.get(
1384
+ "https://www.niftyindices.com/reports/historical-data",
1385
+ headers=niftyindices_headers, timeout=15,
1386
+ )
1387
+ _niftyindices_warmed = True
1388
+ except Exception as e:
1389
+ logging.warning("niftyindices.com session warm-up failed/partial: %s", e)
1390
+ return _niftyindices_session
1391
+
1392
+
1393
+ def _niftyindices_fetch(endpoint, symbol, start_date, end_date):
1394
+ session = _get_niftyindices_session()
1395
+ data = {'cinfo': "{'name':'" + symbol + "','startDate':'" + start_date + "','endDate':'" + end_date + "','indexName':'" + symbol + "'}"}
1396
+ response = session.post(
1397
+ f"https://www.niftyindices.com/BackPage/{endpoint}",
1398
+ headers=niftyindices_headers, json=data, timeout=20,
1399
+ )
1400
+ text = response.text.strip()
1401
+ if text.startswith('<!DOCTYPE') or text.startswith('<html') or text == "":
1402
+ raise NSEEndpointError(
1403
+ f"niftyindices.com/BackPage/{endpoint} returned HTML/empty instead of JSON "
1404
+ f"(HTTP {response.status_code}) -- the site may be down or have changed again."
1405
+ )
1406
+ try:
1407
+ payload = response.json()
1408
+ except ValueError:
1409
+ raise NSEEndpointError(
1410
+ f"niftyindices.com/BackPage/{endpoint}: non-JSON body (HTTP {response.status_code})"
1411
+ )
1412
+ # Old API wrapped the payload as {"d": "<json string>"}; the new one
1413
+ # returns the array directly. Support both so this keeps working if
1414
+ # niftyindices.com ever reverts/mixes the two shapes.
1415
+ if isinstance(payload, dict) and "d" in payload:
1416
+ payload = json.loads(payload["d"])
1417
+ return pd.DataFrame.from_records(payload)
1418
+
1419
+
1420
+ def index_history(symbol,start_date,end_date):
1421
+ return _niftyindices_fetch("getHistoricaldatatabletoString", symbol, start_date, end_date)
1422
+
1423
+ def index_pe_pb_div(symbol,start_date,end_date):
1424
+ return _niftyindices_fetch("getpepbHistoricaldataDBtoString", symbol, start_date, end_date)
1425
+
1426
+ def index_total_returns(symbol,start_date,end_date):
1427
+ return _niftyindices_fetch("getTotalReturnIndexString", symbol, start_date, end_date)
1428
+
1429
+ def get_bhavcopy(date):
1430
+ date = date.replace("-","")
1431
+ payload = pd.read_csv(io.StringIO(_nse_fetch_csv_text(
1432
+ "https://archives.nseindia.com/products/content/sec_bhavdata_full_"+date+".csv")))
1433
+ return payload
1434
+
1435
+ def get_bulkdeals():
1436
+ payload = pd.read_csv(io.StringIO(_nse_fetch_csv_text(
1437
+ "https://archives.nseindia.com/content/equities/bulk.csv")))
1438
+ return payload
1439
+
1440
+ def get_blockdeals():
1441
+ payload = pd.read_csv(io.StringIO(_nse_fetch_csv_text(
1442
+ "https://archives.nseindia.com/content/equities/block.csv")))
1443
+ return payload
1444
+
1445
+ def _nse_top_corp_info(symbol):
1446
+ """`/api/top-corp-info?symbol=X&market=equities` bundles a company's
1447
+ latest announcements, corporate actions (bonus/dividend/split/demerger),
1448
+ shareholding pattern history, financial results, and board meetings in
1449
+ one call -- confirmed live and working through curl_cffi+warm-up. This
1450
+ backs both dividend_timeline() and share_holding() below."""
1451
+ symbol = nsesymbolpurify(symbol)
1452
+ return nsefetch(f"https://www.nseindia.com/api/top-corp-info?symbol={symbol}&market=equities")
1453
+
1454
+
1455
+ def dividend_timeline(symbol):
1456
+ """github.com/aeron7/nsepython issue #75: documented on
1457
+ unofficed.com/nse-python/ but never actually implemented in the code
1458
+ (calling it raised `AttributeError: module 'nsepython' has no attribute
1459
+ 'dividend_timeline'`). Implemented here from `/api/top-corp-info`'s
1460
+ `corporate_actions` list, filtered down to the dividend-purpose entries
1461
+ (that list also contains bonuses/splits/demergers/etc, which this
1462
+ function intentionally excludes to match its name)."""
1463
+ data = _nse_top_corp_info(symbol)
1464
+ actions = (data.get("corporate_actions") or {}).get("data") or []
1465
+ dividends = [a for a in actions if "dividend" in (a.get("purpose") or "").lower()]
1466
+ return pd.DataFrame.from_records(dividends)
1467
+
1468
+
1469
+ def share_holding(symbol):
1470
+ """github.com/aeron7/nsepython issue #75: same situation as
1471
+ dividend_timeline() above -- documented but not implemented. Built from
1472
+ `/api/top-corp-info`'s `shareholdings_patterns` data, which is a dict
1473
+ keyed by filing date (e.g. "31-Mar-2026") whose value is a list of
1474
+ {"<category>": "<percent>"} rows (Promoter & Promoter Group / Public /
1475
+ Shares held by Employee Trusts / Total). Flattened here into one row per
1476
+ filing date with a column per category, newest filing first."""
1477
+ data = _nse_top_corp_info(symbol)
1478
+ by_date = (data.get("shareholdings_patterns") or {}).get("data") or {}
1479
+ rows = []
1480
+ for filing_date, categories in by_date.items():
1481
+ row = {"date": filing_date}
1482
+ for entry in categories:
1483
+ for k, v in entry.items():
1484
+ row[k.strip()] = v.strip() if isinstance(v, str) else v
1485
+ rows.append(row)
1486
+ df = pd.DataFrame.from_records(rows)
1487
+ if not df.empty and "date" in df.columns:
1488
+ try:
1489
+ df = df.sort_values(
1490
+ by="date",
1491
+ key=lambda s: pd.to_datetime(s, format="%d-%b-%Y"),
1492
+ ascending=False,
1493
+ ).reset_index(drop=True)
1494
+ except Exception:
1495
+ pass
1496
+ return df
1497
+
1498
+
1499
+ #Request from subhash
1500
+ ## https://unofficed.com/how-to-find-the-beta-of-indian-stocks-using-python/
1501
+ def get_beta_df_maker(symbol,days):
1502
+ if("NIFTY" in symbol):
1503
+ end_date = datetime.datetime.now().strftime("%d-%b-%Y")
1504
+ end_date = str(end_date)
1505
+
1506
+ start_date = (datetime.datetime.now()- datetime.timedelta(days=days)).strftime("%d-%b-%Y")
1507
+ start_date = str(start_date)
1508
+
1509
+ df2=index_history(symbol,start_date,end_date)
1510
+ df2["daily_change"]=df2["CLOSE"].astype(float).pct_change()
1511
+ df2=df2[['HistoricalDate','daily_change']]
1512
+ df2 = df2.iloc[1: , :]
1513
+ return df2
1514
+ else:
1515
+ end_date = datetime.datetime.now().strftime("%d-%m-%Y")
1516
+ end_date = str(end_date)
1517
+
1518
+ start_date = (datetime.datetime.now()- datetime.timedelta(days=days)).strftime("%d-%m-%Y")
1519
+ start_date = str(start_date)
1520
+
1521
+ df = equity_history(symbol,"EQ",start_date,end_date)
1522
+
1523
+ df["daily_change"]=df["CH_CLOSING_PRICE"].pct_change()
1524
+ df=df[['CH_TIMESTAMP','daily_change']]
1525
+ df = df.iloc[1: , :] #thispointer.com/drop-first-row-of-pandas-dataframe-3-ways/
1526
+ return df
1527
+
1528
+ def getbeta(symbol,days=365,symbol2="NIFTY 50"):
1529
+ return get_beta(symbol,days,symbol2)
1530
+
1531
+ def get_beta(symbol,days=365,symbol2="NIFTY 50"):
1532
+ #Default is 248 days. (Input of Subhash)
1533
+ # github.com/aeron7/nsepython issue #75: this used to raise a raw
1534
+ # KeyError('data') because equity_history() silently returned {} on a
1535
+ # blocked/retired endpoint. nsefetch() now raises a descriptive
1536
+ # NSEEndpointError instead of swallowing the failure -- surface that
1537
+ # (plus any other unexpected shape problem) as a clear, named error
1538
+ # instead of a bare KeyError, per the issue reporter's own suggestion.
1539
+ try:
1540
+ df = get_beta_df_maker(symbol,days)
1541
+ df2 = get_beta_df_maker(symbol2,days)
1542
+ except NSEEndpointError:
1543
+ raise
1544
+ except Exception as e:
1545
+ raise NSEEndpointError(
1546
+ f"get_beta({symbol!r}, symbol2={symbol2!r}): could not build the "
1547
+ f"daily-change series needed for beta -- {e}"
1548
+ ) from e
1549
+
1550
+ x=df["daily_change"].tolist()
1551
+ y=df2["daily_change"].tolist()
1552
+
1553
+ if not x or not y:
1554
+ raise NSEEndpointError(
1555
+ f"get_beta({symbol!r}, symbol2={symbol2!r}): got no historical "
1556
+ f"price data back for the requested {days}-day window."
1557
+ )
1558
+
1559
+ #stackoverflow.com/questions/42670055/is-there-any-better-way-to-calculate-the-covariance-of-two-lists-than-this
1560
+ mean_x = sum(x) / len(x)
1561
+ mean_y = sum(y) / len(y)
1562
+ covariance = sum((a - mean_x) * (b - mean_y) for (a,b) in zip(x,y)) / len(x)
1563
+
1564
+ mean = sum(y) / len(y)
1565
+ variance = sum((i - mean) ** 2 for i in y) / len(y)
1566
+
1567
+ if variance == 0:
1568
+ raise NSEEndpointError(
1569
+ f"get_beta({symbol!r}, symbol2={symbol2!r}): symbol2 had zero "
1570
+ f"price variance over this window, beta is undefined."
1571
+ )
1572
+
1573
+ beta = covariance/variance
1574
+ return round(beta,3)
1575
+
1576
+ def nse_preopen(key="NIFTY",type="pandas"):
1577
+ payload = nsefetch("https://www.nseindia.com/api/market-data-pre-open?key="+key+"")
1578
+ if(type=="pandas"):
1579
+ # NSE's pre-open-market window for most `key` values (e.g. "NIFTY")
1580
+ # is only populated for a few minutes each morning; outside that
1581
+ # window `data` is a legitimate empty list ({"data": [], "msg": "No
1582
+ # Data Found"}), which used to raise a confusing KeyError('metadata')
1583
+ # trying to pull a column out of an empty DataFrame. Return an empty
1584
+ # DataFrame instead.
1585
+ if not payload.get('data'):
1586
+ return pd.DataFrame()
1587
+ payload = pd.DataFrame(payload['data'])
1588
+ payload = pd.json_normalize(payload['metadata'])
1589
+ return payload
1590
+ else:
1591
+ return payload
1592
+
1593
+ #By Avinash https://forum.unofficed.com/t/nsepython-documentation/376/102?u=dexter
1594
+ def nse_preopen_movers(key="FO",filter=1.5):
1595
+ preOpen_gainer=nse_preopen(key)
1596
+ return preOpen_gainer[preOpen_gainer['pChange'] >1.5],preOpen_gainer[preOpen_gainer['pChange'] <-1.5]
1597
+
1598
+ # type = "securities"
1599
+ # type = "etf"
1600
+ # type = "sme"
1601
+ #
1602
+ # sort = "volume"
1603
+ # sort = "value"
1604
+
1605
+ def nse_most_active(type="securities",sort="value"):
1606
+ payload = nsefetch("https://www.nseindia.com/api/live-analysis-most-active-"+type+"?index="+sort+"")
1607
+ payload = pd.DataFrame(payload["data"])
1608
+ return payload
1609
+
1610
+
1611
+ def nse_eq_symbols():
1612
+ #https://forum.unofficed.com/t/feature-request-stocklist-api/1073/11
1613
+ eq_list_pd = pd.read_csv(io.StringIO(_nse_fetch_csv_text(
1614
+ 'https://archives.nseindia.com/content/equities/EQUITY_L.csv')))
1615
+ return eq_list_pd['SYMBOL'].tolist()
1616
+
1617
+ def nse_price_band_hitters(bandtype="both",view="AllSec"):
1618
+ payload = nsefetch("https://www.nseindia.com/api/live-analysis-price-band-hitter")
1619
+
1620
+ #bandtype can be upper, lower, both
1621
+ #view can be AllSec,SecGtr20,SecLwr20
1622
+ return pd.DataFrame(payload[bandtype][view]["data"])
1623
+
1624
+ def nse_largedeals(mode="bulk_deals"):
1625
+ payload = nsefetch('https://www.nseindia.com/api/snapshot-capital-market-largedeal')
1626
+ if(mode=="bulk_deals"):
1627
+ return pd.DataFrame(payload["BULK_DEALS_DATA"])
1628
+ if(mode=="short_deals"):
1629
+ return pd.DataFrame(payload["SHORT_DEALS_DATA"])
1630
+ if(mode=="block_deals"):
1631
+ return pd.DataFrame(payload["BLOCK_DEALS_DATA"])
1632
+
1633
+ def nse_largedeals_historical(from_date, to_date, mode="bulk_deals"):
1634
+ # The old /api/historical/{bulk-deals,short-selling,block-deals} family is
1635
+ # retired on the live site (confirmed HTTP 503 straight from NSE's origin
1636
+ # -- not an Akamai bot-challenge: the 503 body is a tiny generic Apache
1637
+ # ErrorDocument page returned with a consistent ~20-30ms *origin* timing
1638
+ # on every single attempt, with or without warm-up/referer variations,
1639
+ # which is the signature of a dead backend route rather than a solvable
1640
+ # JS sensor wall).
1641
+ #
1642
+ # Found the real, current replacement by driving NSE's own "Bulk Deals/
1643
+ # Block Deals/ Short Selling Archives" report page
1644
+ # (https://www.nseindia.com/report-detail/display-bulk-and-block-deals)
1645
+ # with Playwright and capturing what it actually calls when you click
1646
+ # Go: `/api/historicalOR/bulk-block-short-deals?optionType=<mode>&from=
1647
+ # ..&to=..` -- same host-prefix swap pattern as equity/derivatives above,
1648
+ # just a different path and param name (`optionType=`, not a path
1649
+ # segment), confirmed live for all three modes. Response shape is the
1650
+ # same `{"data": [...]}` the old endpoint returned, just with a different
1651
+ # (current) NSE column-name scheme:
1652
+ # bulk_deals/block_deals -> BD_DT_DATE, BD_DT_ORDER, BD_SYMBOL,
1653
+ # BD_SCRIP_NAME, BD_CLIENT_NAME, BD_BUY_SELL,
1654
+ # BD_QTY_TRD, BD_TP_WATP, BD_REMARKS
1655
+ # short_deals -> SS_DATE, SS_DATE_ORDER, SS_SYMBOL, SS_NAME,
1656
+ # SS_QTY
1657
+ if mode == "bulk_deals":
1658
+ option_type = "bulk_deals"
1659
+ elif mode == "short_deals":
1660
+ option_type = "short_selling"
1661
+ elif mode == "block_deals":
1662
+ option_type = "block_deals"
1663
+ else:
1664
+ option_type = mode
1665
+
1666
+ url = ('https://www.nseindia.com/api/historicalOR/bulk-block-short-deals'
1667
+ '?optionType=' + option_type + '&from=' + from_date + '&to=' + to_date)
1668
+ logging.info("Fetching " + str(url))
1669
+ payload = nsefetch(url)
1670
+ return pd.DataFrame(payload["data"])
1671
+
1672
+ #https://forum.unofficed.com/t/feature-request-nse-fno-participant-wise-oi/1179/7
1673
+ #print(get_fao_participant_oi("04-06-2021"))
1674
+ def get_fao_participant_oi(date):
1675
+ date = date.replace("-","")
1676
+ payload = pd.read_csv(io.StringIO(_nse_fetch_csv_text(
1677
+ "https://archives.nseindia.com/content/nsccl/fao_participant_oi_"+date+".csv")))
1678
+ return payload
1679
+
1680
+ #https://forum.unofficed.com/t/how-to-check-if-the-market-is-open-today-or-not/1268/1
1681
+ def is_market_open(segment = "FO"): #COM,CD,CB,CMOT,COM,FO,IRD,MF,NDM,NTRP,SLBS
1682
+ # Bug fix: the previous version returned True/False based only on
1683
+ # holiday_json's *first* entry, so it almost always reported "open"
1684
+ # regardless of today's actual date (today is essentially never the
1685
+ # first holiday in the list). Scan the whole list for a match instead.
1686
+ holiday_json = nse_holidays()[segment]
1687
+
1688
+ # Get today's date in the format 'dd-Mon-yyyy'
1689
+ today_date = datetime.date.today().strftime('%d-%b-%Y')
1690
+
1691
+ for holiday in holiday_json:
1692
+ if holiday.get('tradingDate') == today_date:
1693
+ print(f"Market is closed today because of {holiday.get('description')}")
1694
+ return False
1695
+
1696
+ print("FNO Market is open today. Have a Nice Trade!")
1697
+ return True
1698
+
1699
+ def nse_expirydetails_by_symbol(symbol,meta ="Futures",i=0):
1700
+ payload = nse_quote_derivatives(symbol)
1701
+ expiry_dates = []
1702
+
1703
+ # Extract from new FNO payload structure
1704
+ if 'data' in payload:
1705
+ unique_dates = set()
1706
+ for entry in payload['data']:
1707
+ if 'expiryDate' in entry:
1708
+ # Filter by meta type if possible, though 'data' usually contains all
1709
+ # To be precise, we can check instrumentType
1710
+ it = entry.get('instrumentType', '')
1711
+ if (meta == "Futures" and "FUT" in it) or (meta == "Options" and "OPT" in it):
1712
+ unique_dates.add(entry['expiryDate'])
1713
+ expiry_dates = sorted(list(unique_dates), key=lambda x: datetime.datetime.strptime(x, "%d-%b-%Y"))
1714
+
1715
+ # Filter future dates
1716
+ future_expiry_dates = []
1717
+ if expiry_dates:
1718
+ temp_dates = [datetime.datetime.strptime(date, "%d-%b-%Y").date() for date in expiry_dates]
1719
+ future_expiry_dates = sorted([date.strftime("%d-%b-%Y") for date in temp_dates if date >= datetime.datetime.now().date()], key=lambda x: datetime.datetime.strptime(x, "%d-%b-%Y"))
1720
+
1721
+ # Fallback to expiry_list if i is out of range
1722
+ if i >= len(future_expiry_dates):
1723
+ dates = expiry_list(symbol, type="list")
1724
+ if dates:
1725
+ temp_dates = [datetime.datetime.strptime(date, "%d-%b-%Y").date() for date in dates]
1726
+ future_expiry_dates = sorted([date.strftime("%d-%b-%Y") for date in temp_dates if date >= datetime.datetime.now().date()], key=lambda x: datetime.datetime.strptime(x, "%d-%b-%Y"))
1727
+
1728
+ if i >= len(future_expiry_dates):
1729
+ return None, None
1730
+
1731
+ currentExpiry = future_expiry_dates[i]
1732
+ currentExpiry_dt = datetime.datetime.strptime(currentExpiry, '%d-%b-%Y').date()
1733
+ date_today = run_time.date()
1734
+ dte = (currentExpiry_dt - date_today).days
1735
+ return currentExpiry_dt, dte
1736
+
1737
+ def security_wise_archive(from_date, to_date, symbol, series="ALL"):
1738
+ # The old /api/historical/securityArchives route is retired on the live
1739
+ # site (confirmed HTTP 503 straight from NSE's origin -- same dead-route
1740
+ # signature as nse_largedeals_historical() above, not a solvable Akamai
1741
+ # challenge: tiny generic Apache ErrorDocument body, consistent fast
1742
+ # origin timing on every attempt regardless of warm-up/referer).
1743
+ #
1744
+ # Found the real, current replacement by driving NSE's own "Security-wise
1745
+ # Archives (Equities)" report page
1746
+ # (https://www.nseindia.com/report-detail/eq_security) with Playwright
1747
+ # and capturing what it actually calls when you click Go:
1748
+ # `/api/historicalOR/generateSecurityWiseHistoricalData?from=..&to=..&
1749
+ # symbol=..&type=..&series=..` -- same host-prefix-swap family as
1750
+ # equity_history()/derivative_history() above, just a different path and
1751
+ # `type=` instead of `dataType=`. Confirmed live: response shape is the
1752
+ # same `{"data": [...]}` with the same CH_*/COP_DELIV_* column names the
1753
+ # old endpoint used (cross-checked against equity_history()'s numbers for
1754
+ # the same symbol/dates -- exact match).
1755
+ base_url = "https://www.nseindia.com/api/historicalOR/generateSecurityWiseHistoricalData"
1756
+ url = f"{base_url}?from={from_date}&to={to_date}&symbol={symbol.upper()}&type=priceVolumeDeliverable&series={series.upper()}"
1757
+ payload = nsefetch(url)
1758
+ return pd.DataFrame(payload['data'])