eshan6704 commited on
Commit
63a980c
·
verified ·
1 Parent(s): f2f5f8c

Update app/build_nse_fno.py

Browse files
Files changed (1) hide show
  1. app/build_nse_fno.py +83 -178
app/build_nse_fno.py CHANGED
@@ -1,67 +1,30 @@
1
- # fno.py
2
  import os
3
  import subprocess
4
  import zipfile
5
- import pandas as pd
6
- import datetime as dt
7
  import tempfile
8
- import pickle
9
-
10
- # ============================================================
11
- # CONFIG
12
- # ============================================================
13
- CACHE_DIR = "./cache/fno"
14
- os.makedirs(CACHE_DIR, exist_ok=True)
15
-
16
-
17
- # ============================================================
18
- # CACHE HELPERS (DATE-BASED)
19
- # ============================================================
20
- def _cache_path(key):
21
- return os.path.join(CACHE_DIR, f"{key}.pkl")
22
-
23
-
24
- def exists(key):
25
- return os.path.exists(_cache_path(key))
26
-
27
 
28
- def load(key):
29
- try:
30
- with open(_cache_path(key), "rb") as f:
31
- return pickle.load(f)
32
- except Exception:
33
- return None
34
 
35
 
36
- def save(key, obj):
37
- with open(_cache_path(key), "wb") as f:
38
- pickle.dump(obj, f)
39
 
40
 
41
  # ============================================================
42
- # FETCH FO BHAVCOPY (RAW)
43
  # ============================================================
44
- def fo_bhavcopy(date_input) -> pd.DataFrame:
45
- """
46
- Download NSE F&O bhavcopy for a given date
47
- date_input: dd-mm-yyyy | datetime.date | datetime.datetime
48
- """
49
- if isinstance(date_input, str):
50
- date = dt.datetime.strptime(date_input, "%d-%m-%Y").date()
51
- elif isinstance(date_input, dt.datetime):
52
- date = date_input.date()
53
- elif isinstance(date_input, dt.date):
54
- date = date_input
55
- else:
56
- raise ValueError("Invalid date format. Use dd-mm-yyyy")
57
 
58
- dmy = date.strftime("%d%m%Y")
59
  file_name = f"BhavCopy_NSE_FO_0_0_0_{ymd}_F_0000.csv"
60
  zip_name = f"{file_name}.zip"
61
- url = f"https://nsearchives.nseindia.com/content/fo/{zip_name}"
62
 
63
- with tempfile.TemporaryDirectory() as tmpdir:
64
- zip_path = os.path.join(tmpdir, zip_name)
65
 
66
  cmd = [
67
  "curl", "-L",
@@ -75,26 +38,17 @@ def fo_bhavcopy(date_input) -> pd.DataFrame:
75
  res = subprocess.run(cmd, stdout=subprocess.PIPE, stderr=subprocess.PIPE)
76
 
77
  if res.returncode != 0 or not os.path.exists(zip_path) or os.path.getsize(zip_path) < 1024:
78
- raise RuntimeError("FO Bhavcopy download failed or blocked")
79
 
80
  with zipfile.ZipFile(zip_path) as z:
81
  with z.open(file_name) as f:
82
- df = pd.read_csv(f)
83
-
84
- return df
85
 
86
 
87
  # ============================================================
88
  # OPTION CHAIN BUILDER
89
  # ============================================================
90
- def build_option_chain(opt_df: pd.DataFrame) -> pd.DataFrame:
91
- drop = [
92
- "FininstrmActlXpryDt", "FinInstrmTp", "TckrSymb",
93
- "TtlNbOfTxsExctd", "NewBrdLotQty",
94
- "EXP_DMY", "SttlmPric",
95
- "OpnPric", "HghPric", "LwPric", "TtlTrfVal"
96
- ]
97
-
98
  rename = {
99
  "ClsPric": "close",
100
  "PrvsClsgPric": "pre",
@@ -103,14 +57,10 @@ def build_option_chain(opt_df: pd.DataFrame) -> pd.DataFrame:
103
  "TtlTradgVol": "vol"
104
  }
105
 
106
- opt_df = opt_df.drop(drop, axis=1, errors="ignore").rename(columns=rename)
107
 
108
- ce = opt_df[opt_df["OptnTp"] == "CE"].rename(
109
- columns={c: f"ce_{c}" for c in opt_df.columns}
110
- )
111
- pe = opt_df[opt_df["OptnTp"] == "PE"].rename(
112
- columns={c: f"pe_{c}" for c in opt_df.columns}
113
- )
114
 
115
  chain = pd.merge(
116
  ce, pe,
@@ -121,141 +71,96 @@ def build_option_chain(opt_df: pd.DataFrame) -> pd.DataFrame:
121
 
122
  chain["StrkPric"] = chain["ce_StrkPric"].combine_first(chain["pe_StrkPric"])
123
 
124
- chain.drop(
125
- columns=[
126
- "ce_StrkPric", "pe_StrkPric",
127
- "ce_OptnTp", "pe_OptnTp",
128
- "ce_UndrlygPric", "pe_UndrlygPric"
129
- ],
130
- inplace=True,
131
- errors="ignore"
132
- )
133
-
134
- chain = chain.fillna(0).sort_values("StrkPric").reset_index(drop=True)
135
-
136
- cols = [
137
  "ce_oi", "ce_oi_chg", "ce_vol", "ce_close", "ce_pre",
138
  "StrkPric",
139
  "pe_pre", "pe_close", "pe_vol", "pe_oi_chg", "pe_oi"
140
  ]
141
 
142
- df = chain[cols].copy()
143
 
144
- for c in ["ce_close", "ce_pre", "pe_close", "pe_pre"]:
145
- df[c] = df[c].astype(float).round(2)
146
 
147
- for c in [
148
- "ce_oi", "ce_oi_chg", "ce_vol",
149
- "pe_vol", "pe_oi_chg", "pe_oi", "StrkPric"
150
- ]:
151
- df[c] = df[c].astype(int)
152
-
153
- return df
154
 
155
 
156
  # ============================================================
157
- # HTML TABLE RENDER
158
  # ============================================================
159
- def df_to_html(df: pd.DataFrame, title=None) -> str:
160
- style = """
161
- <style>
162
- table {border-collapse: collapse; width:100%; font-family:Arial;}
163
- th, td {border:1px solid #ddd; padding:6px; text-align:center;}
164
- th {background:#2e7d32; color:white;}
165
- tr:nth-child(even){background:#f2f2f2;}
166
- </style>
167
  """
168
-
169
- html = df.to_html(index=False, escape=False)
170
- if title:
171
- html = f"<h3>{title}</h3>" + html
172
-
173
- return style + html
174
-
175
-
176
- # ============================================================
177
- # MAIN ENTRY (DAILY VALIDITY)
178
- # ============================================================
179
- def nse_fno_html(fo_date: str, symbol: str) -> str:
180
- """
181
- Daily-valid F&O HTML builder
182
- Cache rules:
183
- - HTML cached per (date + symbol)
184
- - FO bhavcopy cached per date
185
  """
186
 
187
- date_key = dt.datetime.strptime(fo_date, "%d-%m-%Y").strftime("%Y%m%d")
188
-
189
- html_key = f"fno_html_{date_key}_{symbol}"
190
- fo_key = f"fno_bhavcopy_{date_key}"
191
 
192
- # ---------------- HTML CACHE FIRST ----------------
193
- if exists(html_key):
194
- html = load(html_key)
195
- if html:
196
- return html
197
 
198
- # ---------------- FO CACHE ----------------
199
- if exists(fo_key):
200
- fo_df = load(fo_key)
201
  else:
202
- fo_df = fo_bhavcopy(fo_date)
203
- save(fo_key, fo_df)
204
-
205
- # ---------------- BUILD DATA ----------------
206
- fo = fo_df.copy().drop(
207
- ["ISIN", "Rmks", "SctySrs", "Rsvd1", "Rsvd2", "Rsvd3", "Rsvd4"],
208
- axis=1,
209
- errors="ignore"
210
- )
211
 
 
 
212
  exp = pd.to_datetime(fo["FininstrmActlXpryDt"], errors="coerce")
213
  today = pd.Timestamp.today().normalize()
214
 
215
- monthly = (
216
- exp[exp >= today]
217
- .groupby([exp.dt.year, exp.dt.month])
218
- .max()
219
- .sort_values()
220
- )
221
-
222
  if monthly.empty:
223
- return "<h3>No valid expiry found</h3>"
224
 
225
  expiry = monthly.iloc[0].strftime("%d-%m-%Y")
 
226
 
227
- fo["EXP_DMY"] = exp.dt.strftime("%d-%m-%Y")
228
-
229
- df = fo[
230
- (fo["TckrSymb"] == symbol) &
231
- (fo["EXP_DMY"] == expiry)
232
- ].copy()
233
-
234
  if df.empty:
235
  return f"<h3>No F&O data for {symbol}</h3>"
236
 
237
- # ---------------- COMMON ----------------
238
- common_cols = [
239
- "TradDt", "BizDt", "Sgmt", "Src", "SsnId",
240
- "FinInstrmId", "XpryDt", "FinInstrmNm", "LastPric"
241
- ]
242
-
243
- common_df = pd.DataFrame([df.iloc[0][common_cols]])
244
- common_df.insert(0, "Expiry", expiry)
245
-
246
- # ---------------- FUTURE + OPTION ----------------
247
- future_df = df[df["FinInstrmTp"].isin(["STF", "IDF"])]
248
- option_df = df[df["FinInstrmTp"].isin(["STO", "IDO"])]
249
-
250
- option_chain_df = build_option_chain(option_df)
251
-
252
- html = (
253
- df_to_html(common_df, "Common Info") + "<br>"
254
- + df_to_html(future_df, "Future Contracts") + "<br>"
255
- + df_to_html(option_chain_df, "Option Chain")
256
- )
257
-
258
- # ---------------- SAVE HTML ----------------
259
- save(html_key, html)
260
-
261
- return html
 
 
 
 
 
 
 
 
 
 
 
 
1
+ # build_nse_fno.py
2
  import os
3
  import subprocess
4
  import zipfile
 
 
5
  import tempfile
6
+ import pandas as pd
7
+ from datetime import datetime as dt
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
8
 
9
+ from .persist import exists, load, save
 
 
 
 
 
10
 
11
 
12
+ NSE_FO_BASE = "https://nsearchives.nseindia.com/content/fo"
 
 
13
 
14
 
15
  # ============================================================
16
+ # FETCH FO BHAVCOPY (RAW DF)
17
  # ============================================================
18
+ def _fetch_fo_bhavcopy(fo_date: str) -> pd.DataFrame:
19
+ date = dt.strptime(fo_date, "%d-%m-%Y").date()
20
+ ymd = date.strftime("%Y%m%d")
 
 
 
 
 
 
 
 
 
 
21
 
 
22
  file_name = f"BhavCopy_NSE_FO_0_0_0_{ymd}_F_0000.csv"
23
  zip_name = f"{file_name}.zip"
24
+ url = f"{NSE_FO_BASE}/{zip_name}"
25
 
26
+ with tempfile.TemporaryDirectory() as tmp:
27
+ zip_path = os.path.join(tmp, zip_name)
28
 
29
  cmd = [
30
  "curl", "-L",
 
38
  res = subprocess.run(cmd, stdout=subprocess.PIPE, stderr=subprocess.PIPE)
39
 
40
  if res.returncode != 0 or not os.path.exists(zip_path) or os.path.getsize(zip_path) < 1024:
41
+ raise RuntimeError("FO bhavcopy download failed")
42
 
43
  with zipfile.ZipFile(zip_path) as z:
44
  with z.open(file_name) as f:
45
+ return pd.read_csv(f)
 
 
46
 
47
 
48
  # ============================================================
49
  # OPTION CHAIN BUILDER
50
  # ============================================================
51
+ def _build_option_chain(df: pd.DataFrame) -> pd.DataFrame:
 
 
 
 
 
 
 
52
  rename = {
53
  "ClsPric": "close",
54
  "PrvsClsgPric": "pre",
 
57
  "TtlTradgVol": "vol"
58
  }
59
 
60
+ df = df.rename(columns=rename)
61
 
62
+ ce = df[df["OptnTp"] == "CE"].rename(columns={c: f"ce_{c}" for c in df.columns})
63
+ pe = df[df["OptnTp"] == "PE"].rename(columns={c: f"pe_{c}" for c in df.columns})
 
 
 
 
64
 
65
  chain = pd.merge(
66
  ce, pe,
 
71
 
72
  chain["StrkPric"] = chain["ce_StrkPric"].combine_first(chain["pe_StrkPric"])
73
 
74
+ keep = [
 
 
 
 
 
 
 
 
 
 
 
 
75
  "ce_oi", "ce_oi_chg", "ce_vol", "ce_close", "ce_pre",
76
  "StrkPric",
77
  "pe_pre", "pe_close", "pe_vol", "pe_oi_chg", "pe_oi"
78
  ]
79
 
80
+ out = chain[keep].fillna(0).sort_values("StrkPric")
81
 
82
+ for c in out.columns:
83
+ out[c] = pd.to_numeric(out[c], errors="coerce").fillna(0)
84
 
85
+ return out.reset_index(drop=True)
 
 
 
 
 
 
86
 
87
 
88
  # ============================================================
89
+ # MAIN HTML BUILDER (STYLE = build_index_live_html)
90
  # ============================================================
91
+ def build_nse_fno_html(fo_date: str, symbol: str) -> str:
 
 
 
 
 
 
 
92
  """
93
+ Daily NSE F&O HTML
94
+ - Bhavcopy cached per DATE
95
+ - HTML cached per DATE + SYMBOL
 
 
 
 
 
 
 
 
 
 
 
 
 
 
96
  """
97
 
98
+ date_key = dt.strptime(fo_date, "%d-%m-%Y").strftime("%Y%m%d")
99
+ cache_html = f"NSE_FNO_HTML_{date_key}_{symbol}"
100
+ cache_df = f"NSE_FNO_BHAVCOPY_{date_key}"
 
101
 
102
+ # ================= HTML CACHE =================
103
+ if exists(cache_html, "html"):
104
+ cached = load(cache_html, "html")
105
+ if isinstance(cached, str):
106
+ return cached
107
 
108
+ # ================= BHAVCOPY CACHE =================
109
+ if exists(cache_df):
110
+ fo_df = load(cache_df)
111
  else:
112
+ fo_df = _fetch_fo_bhavcopy(fo_date)
113
+ save(cache_df, fo_df)
 
 
 
 
 
 
 
114
 
115
+ # ================= PROCESS =================
116
+ fo = fo_df.copy()
117
  exp = pd.to_datetime(fo["FininstrmActlXpryDt"], errors="coerce")
118
  today = pd.Timestamp.today().normalize()
119
 
120
+ monthly = exp[exp >= today].groupby([exp.dt.year, exp.dt.month]).max()
 
 
 
 
 
 
121
  if monthly.empty:
122
+ return "<h3>No valid expiry</h3>"
123
 
124
  expiry = monthly.iloc[0].strftime("%d-%m-%Y")
125
+ fo["EXP"] = exp.dt.strftime("%d-%m-%Y")
126
 
127
+ df = fo[(fo["TckrSymb"] == symbol) & (fo["EXP"] == expiry)]
 
 
 
 
 
 
128
  if df.empty:
129
  return f"<h3>No F&O data for {symbol}</h3>"
130
 
131
+ fut_df = df[df["FinInstrmTp"].isin(["STF", "IDF"])]
132
+ opt_df = df[df["FinInstrmTp"].isin(["STO", "IDO"])]
133
+
134
+ opt_chain = _build_option_chain(opt_df)
135
+
136
+ # ================= HTML =================
137
+ html = f"""
138
+ <!DOCTYPE html>
139
+ <html>
140
+ <head>
141
+ <meta charset="UTF-8">
142
+ <style>
143
+ body {{ font-family: Arial; margin: 12px; background:#f5f5f5; }}
144
+ table {{ border-collapse: collapse; width: 100%; background:white; }}
145
+ th, td {{ border:1px solid #bbb; padding:6px; text-align:center; }}
146
+ th {{ background:#2e7d32; color:white; }}
147
+ </style>
148
+ </head>
149
+ <body>
150
+
151
+ <h2>NSE F&O : {symbol}</h2>
152
+ <h4>Expiry: {expiry}</h4>
153
+
154
+ <h3>Futures</h3>
155
+ {fut_df.to_html(index=False)}
156
+
157
+ <h3>Option Chain</h3>
158
+ {opt_chain.to_html(index=False)}
159
+
160
+ </body>
161
+ </html>
162
+ """
163
+
164
+ # ================= SAVE =================
165
+ save(cache_html, html, "html")
166
+ return html