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/__init__.py +3 -3
- nsepython/rahu.py +1758 -966
- {nsepython-2.95.dist-info → nsepython-2.98.dist-info}/METADATA +130 -129
- nsepython-2.98.dist-info/RECORD +7 -0
- {nsepython-2.95.dist-info → nsepython-2.98.dist-info}/WHEEL +1 -1
- {nsepython-2.95.dist-info → nsepython-2.98.dist-info}/licenses/LICENSE +674 -674
- nsepython-2.95.dist-info/RECORD +0 -7
- {nsepython-2.95.dist-info → nsepython-2.98.dist-info}/top_level.txt +0 -0
nsepython/rahu.py
CHANGED
|
@@ -1,966 +1,1758 @@
|
|
|
1
|
-
import os,sys
|
|
2
|
-
#
|
|
3
|
-
#
|
|
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
|
|
13
|
-
|
|
14
|
-
|
|
15
|
-
|
|
16
|
-
|
|
17
|
-
|
|
18
|
-
|
|
19
|
-
|
|
20
|
-
|
|
21
|
-
|
|
22
|
-
|
|
23
|
-
|
|
24
|
-
|
|
25
|
-
|
|
26
|
-
|
|
27
|
-
|
|
28
|
-
|
|
29
|
-
|
|
30
|
-
|
|
31
|
-
|
|
32
|
-
|
|
33
|
-
|
|
34
|
-
|
|
35
|
-
|
|
36
|
-
|
|
37
|
-
|
|
38
|
-
|
|
39
|
-
|
|
40
|
-
|
|
41
|
-
|
|
42
|
-
|
|
43
|
-
|
|
44
|
-
|
|
45
|
-
|
|
46
|
-
|
|
47
|
-
|
|
48
|
-
|
|
49
|
-
|
|
50
|
-
|
|
51
|
-
|
|
52
|
-
|
|
53
|
-
|
|
54
|
-
|
|
55
|
-
|
|
56
|
-
|
|
57
|
-
|
|
58
|
-
|
|
59
|
-
|
|
60
|
-
|
|
61
|
-
|
|
62
|
-
|
|
63
|
-
|
|
64
|
-
|
|
65
|
-
|
|
66
|
-
|
|
67
|
-
|
|
68
|
-
|
|
69
|
-
|
|
70
|
-
|
|
71
|
-
|
|
72
|
-
|
|
73
|
-
"
|
|
74
|
-
"
|
|
75
|
-
|
|
76
|
-
|
|
77
|
-
|
|
78
|
-
|
|
79
|
-
|
|
80
|
-
|
|
81
|
-
|
|
82
|
-
"
|
|
83
|
-
|
|
84
|
-
"
|
|
85
|
-
|
|
86
|
-
|
|
87
|
-
|
|
88
|
-
|
|
89
|
-
|
|
90
|
-
|
|
91
|
-
|
|
92
|
-
|
|
93
|
-
|
|
94
|
-
|
|
95
|
-
|
|
96
|
-
|
|
97
|
-
|
|
98
|
-
|
|
99
|
-
|
|
100
|
-
|
|
101
|
-
|
|
102
|
-
|
|
103
|
-
|
|
104
|
-
|
|
105
|
-
|
|
106
|
-
|
|
107
|
-
|
|
108
|
-
|
|
109
|
-
|
|
110
|
-
|
|
111
|
-
|
|
112
|
-
|
|
113
|
-
|
|
114
|
-
|
|
115
|
-
|
|
116
|
-
|
|
117
|
-
|
|
118
|
-
|
|
119
|
-
|
|
120
|
-
|
|
121
|
-
|
|
122
|
-
|
|
123
|
-
|
|
124
|
-
|
|
125
|
-
|
|
126
|
-
|
|
127
|
-
|
|
128
|
-
|
|
129
|
-
|
|
130
|
-
|
|
131
|
-
|
|
132
|
-
|
|
133
|
-
|
|
134
|
-
|
|
135
|
-
|
|
136
|
-
|
|
137
|
-
|
|
138
|
-
|
|
139
|
-
|
|
140
|
-
|
|
141
|
-
|
|
142
|
-
|
|
143
|
-
|
|
144
|
-
|
|
145
|
-
|
|
146
|
-
|
|
147
|
-
|
|
148
|
-
|
|
149
|
-
|
|
150
|
-
|
|
151
|
-
|
|
152
|
-
|
|
153
|
-
|
|
154
|
-
|
|
155
|
-
|
|
156
|
-
|
|
157
|
-
|
|
158
|
-
|
|
159
|
-
|
|
160
|
-
|
|
161
|
-
|
|
162
|
-
|
|
163
|
-
|
|
164
|
-
|
|
165
|
-
|
|
166
|
-
|
|
167
|
-
|
|
168
|
-
|
|
169
|
-
|
|
170
|
-
|
|
171
|
-
|
|
172
|
-
|
|
173
|
-
|
|
174
|
-
|
|
175
|
-
|
|
176
|
-
|
|
177
|
-
|
|
178
|
-
|
|
179
|
-
|
|
180
|
-
|
|
181
|
-
|
|
182
|
-
|
|
183
|
-
|
|
184
|
-
|
|
185
|
-
|
|
186
|
-
|
|
187
|
-
|
|
188
|
-
|
|
189
|
-
|
|
190
|
-
|
|
191
|
-
|
|
192
|
-
|
|
193
|
-
|
|
194
|
-
|
|
195
|
-
|
|
196
|
-
|
|
197
|
-
|
|
198
|
-
|
|
199
|
-
|
|
200
|
-
|
|
201
|
-
|
|
202
|
-
|
|
203
|
-
|
|
204
|
-
|
|
205
|
-
|
|
206
|
-
|
|
207
|
-
|
|
208
|
-
|
|
209
|
-
|
|
210
|
-
|
|
211
|
-
|
|
212
|
-
|
|
213
|
-
|
|
214
|
-
|
|
215
|
-
|
|
216
|
-
|
|
217
|
-
|
|
218
|
-
|
|
219
|
-
|
|
220
|
-
|
|
221
|
-
|
|
222
|
-
|
|
223
|
-
|
|
224
|
-
|
|
225
|
-
|
|
226
|
-
|
|
227
|
-
|
|
228
|
-
|
|
229
|
-
|
|
230
|
-
|
|
231
|
-
|
|
232
|
-
|
|
233
|
-
|
|
234
|
-
|
|
235
|
-
|
|
236
|
-
|
|
237
|
-
|
|
238
|
-
|
|
239
|
-
|
|
240
|
-
|
|
241
|
-
|
|
242
|
-
|
|
243
|
-
|
|
244
|
-
|
|
245
|
-
|
|
246
|
-
|
|
247
|
-
|
|
248
|
-
|
|
249
|
-
|
|
250
|
-
|
|
251
|
-
|
|
252
|
-
|
|
253
|
-
|
|
254
|
-
|
|
255
|
-
|
|
256
|
-
|
|
257
|
-
|
|
258
|
-
|
|
259
|
-
|
|
260
|
-
|
|
261
|
-
|
|
262
|
-
|
|
263
|
-
|
|
264
|
-
|
|
265
|
-
|
|
266
|
-
|
|
267
|
-
|
|
268
|
-
|
|
269
|
-
|
|
270
|
-
|
|
271
|
-
|
|
272
|
-
|
|
273
|
-
|
|
274
|
-
|
|
275
|
-
|
|
276
|
-
|
|
277
|
-
|
|
278
|
-
|
|
279
|
-
|
|
280
|
-
|
|
281
|
-
|
|
282
|
-
|
|
283
|
-
|
|
284
|
-
|
|
285
|
-
#
|
|
286
|
-
|
|
287
|
-
|
|
288
|
-
|
|
289
|
-
|
|
290
|
-
#
|
|
291
|
-
|
|
292
|
-
|
|
293
|
-
|
|
294
|
-
|
|
295
|
-
|
|
296
|
-
|
|
297
|
-
|
|
298
|
-
|
|
299
|
-
|
|
300
|
-
|
|
301
|
-
|
|
302
|
-
|
|
303
|
-
|
|
304
|
-
|
|
305
|
-
|
|
306
|
-
|
|
307
|
-
|
|
308
|
-
|
|
309
|
-
|
|
310
|
-
|
|
311
|
-
|
|
312
|
-
|
|
313
|
-
|
|
314
|
-
|
|
315
|
-
|
|
316
|
-
|
|
317
|
-
|
|
318
|
-
|
|
319
|
-
|
|
320
|
-
|
|
321
|
-
|
|
322
|
-
|
|
323
|
-
|
|
324
|
-
|
|
325
|
-
|
|
326
|
-
|
|
327
|
-
|
|
328
|
-
|
|
329
|
-
|
|
330
|
-
|
|
331
|
-
|
|
332
|
-
|
|
333
|
-
|
|
334
|
-
|
|
335
|
-
|
|
336
|
-
|
|
337
|
-
|
|
338
|
-
|
|
339
|
-
|
|
340
|
-
|
|
341
|
-
|
|
342
|
-
|
|
343
|
-
|
|
344
|
-
|
|
345
|
-
|
|
346
|
-
|
|
347
|
-
|
|
348
|
-
|
|
349
|
-
|
|
350
|
-
|
|
351
|
-
|
|
352
|
-
|
|
353
|
-
|
|
354
|
-
|
|
355
|
-
|
|
356
|
-
|
|
357
|
-
|
|
358
|
-
|
|
359
|
-
|
|
360
|
-
|
|
361
|
-
|
|
362
|
-
|
|
363
|
-
|
|
364
|
-
|
|
365
|
-
|
|
366
|
-
|
|
367
|
-
|
|
368
|
-
|
|
369
|
-
|
|
370
|
-
|
|
371
|
-
|
|
372
|
-
|
|
373
|
-
|
|
374
|
-
|
|
375
|
-
|
|
376
|
-
|
|
377
|
-
|
|
378
|
-
|
|
379
|
-
|
|
380
|
-
|
|
381
|
-
|
|
382
|
-
|
|
383
|
-
|
|
384
|
-
|
|
385
|
-
|
|
386
|
-
|
|
387
|
-
|
|
388
|
-
|
|
389
|
-
|
|
390
|
-
|
|
391
|
-
|
|
392
|
-
|
|
393
|
-
|
|
394
|
-
|
|
395
|
-
|
|
396
|
-
|
|
397
|
-
|
|
398
|
-
|
|
399
|
-
|
|
400
|
-
|
|
401
|
-
|
|
402
|
-
|
|
403
|
-
|
|
404
|
-
|
|
405
|
-
|
|
406
|
-
|
|
407
|
-
|
|
408
|
-
|
|
409
|
-
|
|
410
|
-
|
|
411
|
-
|
|
412
|
-
|
|
413
|
-
|
|
414
|
-
|
|
415
|
-
|
|
416
|
-
|
|
417
|
-
|
|
418
|
-
|
|
419
|
-
|
|
420
|
-
|
|
421
|
-
|
|
422
|
-
|
|
423
|
-
|
|
424
|
-
|
|
425
|
-
|
|
426
|
-
|
|
427
|
-
|
|
428
|
-
|
|
429
|
-
|
|
430
|
-
|
|
431
|
-
|
|
432
|
-
|
|
433
|
-
|
|
434
|
-
|
|
435
|
-
|
|
436
|
-
|
|
437
|
-
|
|
438
|
-
return
|
|
439
|
-
|
|
440
|
-
|
|
441
|
-
|
|
442
|
-
|
|
443
|
-
|
|
444
|
-
|
|
445
|
-
|
|
446
|
-
|
|
447
|
-
|
|
448
|
-
|
|
449
|
-
|
|
450
|
-
|
|
451
|
-
|
|
452
|
-
|
|
453
|
-
|
|
454
|
-
|
|
455
|
-
|
|
456
|
-
|
|
457
|
-
|
|
458
|
-
|
|
459
|
-
|
|
460
|
-
|
|
461
|
-
|
|
462
|
-
|
|
463
|
-
|
|
464
|
-
|
|
465
|
-
|
|
466
|
-
|
|
467
|
-
|
|
468
|
-
|
|
469
|
-
|
|
470
|
-
|
|
471
|
-
|
|
472
|
-
|
|
473
|
-
|
|
474
|
-
|
|
475
|
-
|
|
476
|
-
|
|
477
|
-
|
|
478
|
-
|
|
479
|
-
|
|
480
|
-
|
|
481
|
-
|
|
482
|
-
|
|
483
|
-
|
|
484
|
-
|
|
485
|
-
|
|
486
|
-
|
|
487
|
-
|
|
488
|
-
|
|
489
|
-
|
|
490
|
-
|
|
491
|
-
|
|
492
|
-
|
|
493
|
-
|
|
494
|
-
|
|
495
|
-
|
|
496
|
-
|
|
497
|
-
|
|
498
|
-
|
|
499
|
-
|
|
500
|
-
|
|
501
|
-
|
|
502
|
-
|
|
503
|
-
|
|
504
|
-
|
|
505
|
-
|
|
506
|
-
|
|
507
|
-
|
|
508
|
-
|
|
509
|
-
|
|
510
|
-
|
|
511
|
-
|
|
512
|
-
|
|
513
|
-
|
|
514
|
-
|
|
515
|
-
|
|
516
|
-
|
|
517
|
-
|
|
518
|
-
|
|
519
|
-
|
|
520
|
-
|
|
521
|
-
|
|
522
|
-
|
|
523
|
-
|
|
524
|
-
|
|
525
|
-
|
|
526
|
-
|
|
527
|
-
|
|
528
|
-
|
|
529
|
-
|
|
530
|
-
|
|
531
|
-
|
|
532
|
-
|
|
533
|
-
|
|
534
|
-
|
|
535
|
-
|
|
536
|
-
|
|
537
|
-
|
|
538
|
-
|
|
539
|
-
|
|
540
|
-
|
|
541
|
-
|
|
542
|
-
|
|
543
|
-
|
|
544
|
-
|
|
545
|
-
|
|
546
|
-
|
|
547
|
-
|
|
548
|
-
|
|
549
|
-
|
|
550
|
-
|
|
551
|
-
|
|
552
|
-
|
|
553
|
-
|
|
554
|
-
|
|
555
|
-
|
|
556
|
-
|
|
557
|
-
|
|
558
|
-
|
|
559
|
-
|
|
560
|
-
|
|
561
|
-
|
|
562
|
-
|
|
563
|
-
|
|
564
|
-
|
|
565
|
-
|
|
566
|
-
|
|
567
|
-
|
|
568
|
-
|
|
569
|
-
|
|
570
|
-
|
|
571
|
-
|
|
572
|
-
|
|
573
|
-
|
|
574
|
-
|
|
575
|
-
|
|
576
|
-
|
|
577
|
-
|
|
578
|
-
|
|
579
|
-
|
|
580
|
-
|
|
581
|
-
|
|
582
|
-
|
|
583
|
-
|
|
584
|
-
|
|
585
|
-
|
|
586
|
-
|
|
587
|
-
|
|
588
|
-
|
|
589
|
-
|
|
590
|
-
|
|
591
|
-
|
|
592
|
-
|
|
593
|
-
|
|
594
|
-
|
|
595
|
-
|
|
596
|
-
|
|
597
|
-
|
|
598
|
-
|
|
599
|
-
|
|
600
|
-
|
|
601
|
-
|
|
602
|
-
|
|
603
|
-
|
|
604
|
-
|
|
605
|
-
|
|
606
|
-
|
|
607
|
-
|
|
608
|
-
|
|
609
|
-
#
|
|
610
|
-
#
|
|
611
|
-
|
|
612
|
-
|
|
613
|
-
|
|
614
|
-
|
|
615
|
-
|
|
616
|
-
|
|
617
|
-
|
|
618
|
-
|
|
619
|
-
|
|
620
|
-
|
|
621
|
-
|
|
622
|
-
|
|
623
|
-
|
|
624
|
-
|
|
625
|
-
|
|
626
|
-
|
|
627
|
-
|
|
628
|
-
|
|
629
|
-
|
|
630
|
-
|
|
631
|
-
|
|
632
|
-
|
|
633
|
-
|
|
634
|
-
|
|
635
|
-
|
|
636
|
-
|
|
637
|
-
|
|
638
|
-
|
|
639
|
-
|
|
640
|
-
|
|
641
|
-
|
|
642
|
-
|
|
643
|
-
|
|
644
|
-
|
|
645
|
-
|
|
646
|
-
|
|
647
|
-
|
|
648
|
-
|
|
649
|
-
|
|
650
|
-
|
|
651
|
-
|
|
652
|
-
|
|
653
|
-
|
|
654
|
-
|
|
655
|
-
|
|
656
|
-
|
|
657
|
-
|
|
658
|
-
|
|
659
|
-
|
|
660
|
-
|
|
661
|
-
return
|
|
662
|
-
|
|
663
|
-
|
|
664
|
-
|
|
665
|
-
|
|
666
|
-
|
|
667
|
-
|
|
668
|
-
|
|
669
|
-
|
|
670
|
-
|
|
671
|
-
|
|
672
|
-
|
|
673
|
-
|
|
674
|
-
|
|
675
|
-
|
|
676
|
-
|
|
677
|
-
|
|
678
|
-
|
|
679
|
-
|
|
680
|
-
|
|
681
|
-
|
|
682
|
-
|
|
683
|
-
|
|
684
|
-
|
|
685
|
-
|
|
686
|
-
|
|
687
|
-
|
|
688
|
-
|
|
689
|
-
|
|
690
|
-
|
|
691
|
-
|
|
692
|
-
|
|
693
|
-
|
|
694
|
-
|
|
695
|
-
|
|
696
|
-
|
|
697
|
-
|
|
698
|
-
|
|
699
|
-
|
|
700
|
-
|
|
701
|
-
|
|
702
|
-
|
|
703
|
-
|
|
704
|
-
|
|
705
|
-
|
|
706
|
-
|
|
707
|
-
|
|
708
|
-
|
|
709
|
-
#
|
|
710
|
-
|
|
711
|
-
|
|
712
|
-
|
|
713
|
-
|
|
714
|
-
|
|
715
|
-
|
|
716
|
-
|
|
717
|
-
|
|
718
|
-
|
|
719
|
-
|
|
720
|
-
|
|
721
|
-
|
|
722
|
-
|
|
723
|
-
|
|
724
|
-
|
|
725
|
-
|
|
726
|
-
|
|
727
|
-
|
|
728
|
-
|
|
729
|
-
|
|
730
|
-
|
|
731
|
-
|
|
732
|
-
|
|
733
|
-
|
|
734
|
-
|
|
735
|
-
|
|
736
|
-
|
|
737
|
-
|
|
738
|
-
|
|
739
|
-
|
|
740
|
-
|
|
741
|
-
|
|
742
|
-
|
|
743
|
-
|
|
744
|
-
|
|
745
|
-
|
|
746
|
-
|
|
747
|
-
|
|
748
|
-
|
|
749
|
-
|
|
750
|
-
|
|
751
|
-
|
|
752
|
-
|
|
753
|
-
|
|
754
|
-
|
|
755
|
-
|
|
756
|
-
|
|
757
|
-
#
|
|
758
|
-
|
|
759
|
-
|
|
760
|
-
|
|
761
|
-
|
|
762
|
-
|
|
763
|
-
|
|
764
|
-
|
|
765
|
-
|
|
766
|
-
|
|
767
|
-
|
|
768
|
-
|
|
769
|
-
|
|
770
|
-
|
|
771
|
-
|
|
772
|
-
|
|
773
|
-
|
|
774
|
-
}
|
|
775
|
-
|
|
776
|
-
|
|
777
|
-
|
|
778
|
-
|
|
779
|
-
|
|
780
|
-
|
|
781
|
-
|
|
782
|
-
|
|
783
|
-
|
|
784
|
-
|
|
785
|
-
|
|
786
|
-
|
|
787
|
-
|
|
788
|
-
|
|
789
|
-
|
|
790
|
-
|
|
791
|
-
|
|
792
|
-
|
|
793
|
-
|
|
794
|
-
|
|
795
|
-
|
|
796
|
-
|
|
797
|
-
|
|
798
|
-
|
|
799
|
-
|
|
800
|
-
|
|
801
|
-
|
|
802
|
-
|
|
803
|
-
|
|
804
|
-
|
|
805
|
-
|
|
806
|
-
|
|
807
|
-
|
|
808
|
-
|
|
809
|
-
|
|
810
|
-
|
|
811
|
-
|
|
812
|
-
|
|
813
|
-
|
|
814
|
-
|
|
815
|
-
|
|
816
|
-
|
|
817
|
-
|
|
818
|
-
|
|
819
|
-
|
|
820
|
-
|
|
821
|
-
|
|
822
|
-
|
|
823
|
-
|
|
824
|
-
|
|
825
|
-
|
|
826
|
-
|
|
827
|
-
|
|
828
|
-
|
|
829
|
-
|
|
830
|
-
|
|
831
|
-
|
|
832
|
-
|
|
833
|
-
|
|
834
|
-
|
|
835
|
-
|
|
836
|
-
|
|
837
|
-
|
|
838
|
-
|
|
839
|
-
|
|
840
|
-
|
|
841
|
-
|
|
842
|
-
|
|
843
|
-
|
|
844
|
-
|
|
845
|
-
|
|
846
|
-
|
|
847
|
-
|
|
848
|
-
|
|
849
|
-
|
|
850
|
-
|
|
851
|
-
|
|
852
|
-
|
|
853
|
-
|
|
854
|
-
|
|
855
|
-
|
|
856
|
-
|
|
857
|
-
|
|
858
|
-
|
|
859
|
-
|
|
860
|
-
|
|
861
|
-
|
|
862
|
-
|
|
863
|
-
|
|
864
|
-
|
|
865
|
-
|
|
866
|
-
|
|
867
|
-
|
|
868
|
-
|
|
869
|
-
#
|
|
870
|
-
|
|
871
|
-
|
|
872
|
-
|
|
873
|
-
|
|
874
|
-
#
|
|
875
|
-
|
|
876
|
-
|
|
877
|
-
|
|
878
|
-
|
|
879
|
-
|
|
880
|
-
|
|
881
|
-
|
|
882
|
-
payload
|
|
883
|
-
|
|
884
|
-
|
|
885
|
-
|
|
886
|
-
|
|
887
|
-
|
|
888
|
-
|
|
889
|
-
|
|
890
|
-
|
|
891
|
-
|
|
892
|
-
|
|
893
|
-
|
|
894
|
-
|
|
895
|
-
|
|
896
|
-
|
|
897
|
-
|
|
898
|
-
|
|
899
|
-
|
|
900
|
-
|
|
901
|
-
|
|
902
|
-
|
|
903
|
-
|
|
904
|
-
|
|
905
|
-
|
|
906
|
-
|
|
907
|
-
|
|
908
|
-
|
|
909
|
-
|
|
910
|
-
|
|
911
|
-
|
|
912
|
-
|
|
913
|
-
|
|
914
|
-
|
|
915
|
-
|
|
916
|
-
|
|
917
|
-
|
|
918
|
-
|
|
919
|
-
|
|
920
|
-
|
|
921
|
-
|
|
922
|
-
|
|
923
|
-
|
|
924
|
-
|
|
925
|
-
|
|
926
|
-
return
|
|
927
|
-
|
|
928
|
-
|
|
929
|
-
|
|
930
|
-
|
|
931
|
-
|
|
932
|
-
|
|
933
|
-
|
|
934
|
-
|
|
935
|
-
|
|
936
|
-
|
|
937
|
-
|
|
938
|
-
|
|
939
|
-
|
|
940
|
-
|
|
941
|
-
|
|
942
|
-
|
|
943
|
-
|
|
944
|
-
|
|
945
|
-
|
|
946
|
-
|
|
947
|
-
|
|
948
|
-
|
|
949
|
-
|
|
950
|
-
|
|
951
|
-
|
|
952
|
-
|
|
953
|
-
|
|
954
|
-
|
|
955
|
-
expiry_dates = [
|
|
956
|
-
|
|
957
|
-
|
|
958
|
-
|
|
959
|
-
|
|
960
|
-
|
|
961
|
-
|
|
962
|
-
|
|
963
|
-
|
|
964
|
-
|
|
965
|
-
|
|
966
|
-
|
|
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§ion= 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+'§ion='+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)}¶ms=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)}¶ms=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'])
|