eshan6704 commited on
Commit
8e4c2f7
·
verified ·
1 Parent(s): 4d29f9b

Create app.py

Browse files
Files changed (1) hide show
  1. app.py +376 -0
app.py ADDED
@@ -0,0 +1,376 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ import gradio as gr
2
+ import yfinance as yf
3
+ import plotly.graph_objs as go
4
+ import pandas as pd
5
+ import datetime
6
+ import pandas.api.types as ptypes
7
+
8
+ STYLE_BLOCK = """
9
+ <style>
10
+ .styled-table {
11
+ border-collapse: collapse;
12
+ margin: 10px 0;
13
+ font-size: 0.9em;
14
+ font-family: sans-serif;
15
+ width: 100%;
16
+ box-shadow: 0 0 10px rgba(0,0,0,0.1);
17
+ }
18
+ .styled-table th, .styled-table td {
19
+ padding: 8px 10px;
20
+ border: 1px solid #ddd;
21
+ }
22
+ .styled-table tbody tr:nth-child(even) {
23
+ background-color: #f9f9f9;
24
+ }
25
+ .card {
26
+ display: block; /* Ensures each card is on its own line */
27
+ width: 95%; /* Make card take up most of the width */
28
+ margin: 10px auto; /* Center the cards and add margin */
29
+ padding: 15px;
30
+ border: 1px solid #ddd;
31
+ border-radius: 8px;
32
+ box-shadow: 0 2px 5px rgba(0,0,0,0.1);
33
+ background: #fafafa;
34
+ }
35
+ .card-category-title {
36
+ font-size: 1.1em; /* Slightly larger heading for category */
37
+ color: #222;
38
+ margin: 0 0 8px; /* Adjusted margin */
39
+ border-bottom: 1px solid #eee; /* Add a separator */
40
+ padding-bottom: 5px;
41
+ }
42
+ .card-content-grid {
43
+ display: flex;
44
+ flex-wrap: wrap; /* Allow items to wrap to the next line */
45
+ gap: 15px; /* Space between individual key-value items */
46
+ }
47
+ .key-value-pair {
48
+ flex: 1 1 calc(33% - 15px); /* For 3 items in a row, considering gap */
49
+ box-sizing: border-box; /* Include padding and border in the width */
50
+ min-width: 250px; /* Prevent items from becoming too narrow */
51
+ background: #fff;
52
+ padding: 10px;
53
+ border: 1px solid #e0e0e0;
54
+ border-radius: 5px;
55
+ box-shadow: 0 1px 3px rgba(0,0,0,0.05);
56
+ }
57
+ .key-value-pair h3 {
58
+ font-size: 0.95em; /* Smaller heading for the key */
59
+ color: #444;
60
+ margin: 0 0 5px 0;
61
+ border-bottom: none;
62
+ padding-bottom: 0;
63
+ }
64
+ .key-value-pair p {
65
+ font-size: 0.9em; /* Smaller paragraph for the value */
66
+ color: #555;
67
+ margin: 0;
68
+ font-weight: bold; /* Make values stand out */
69
+ }
70
+ .big-box {
71
+ width:95%;
72
+ margin:20px auto;
73
+ padding:20px;
74
+ border:1px solid #ccc;
75
+ border-radius:8px;
76
+ background:#fff;
77
+ box-shadow:0 2px 8px rgba(0,0,0,0.1);
78
+ font-size:0.95em;
79
+ line-height:1.4em;
80
+ max-height:400px;
81
+ overflow-y:auto;
82
+ }
83
+ </style>
84
+ """
85
+
86
+ def format_large_number(num):
87
+ if not isinstance(num, (int, float)):
88
+ return num # Return as is if not a number
89
+
90
+ sign = '-' if num < 0 else ''
91
+ num = abs(float(num))
92
+
93
+ if num >= 1_000_000_000_000: # Lakh Crore (10^12)
94
+ return f"{sign}{num / 1_000_000_000_000:.2f} LCr"
95
+ elif num >= 10_000_000: # Crore (10^7)
96
+ return f"{sign}{num / 10_000_000:.2f} Cr"
97
+ elif num >= 100_000: # Lakh (10^5)
98
+ return f"{sign}{num / 100_000:.2f} Lac"
99
+ else:
100
+ return f"{sign}{num:,.0f}"
101
+
102
+ def format_timestamp_to_date(timestamp):
103
+ if not isinstance(timestamp, (int, float)) or timestamp <= 0:
104
+ return "N/A"
105
+ try:
106
+ return datetime.datetime.fromtimestamp(timestamp).strftime('%Y-%m-%d')
107
+ except ValueError:
108
+ return "Invalid Date"
109
+
110
+ def fetch_data(symbol, req_type):
111
+ yfsymbol=symbol+".NS"
112
+ try:
113
+ ticker = yf.Ticker(yfsymbol)
114
+
115
+ content_html = ""
116
+
117
+ # Info block as cards + big boxes
118
+ if req_type.lower() == "info":
119
+ info = ticker.info
120
+ if not info:
121
+ content_html = "<h1>No info available</h1>"
122
+ else:
123
+ info_categories = {
124
+ "Company Overview": [
125
+ "longName", "symbol", "exchange", "quoteType", "sector", "industry",
126
+ "fullTimeEmployees", "website", "address1", "city", "state", "zip", "country", "phone"
127
+ ],
128
+ "Valuation Metrics": [
129
+ "marketCap", "enterpriseValue", "trailingPE", "forwardPE", "pegRatio",
130
+ "priceToSalesTrailing12Months", "enterpriseToRevenue", "enterpriseToEbitda"
131
+ ],
132
+ "Key Financials": [
133
+ "fiftyTwoWeekHigh", "fiftyTwoWeekLow", "fiftyDayAverage", "twoHundredDayAverage",
134
+ "trailingAnnualDividendRate", "trailingAnnualDividendYield", "dividendRate", "dividendYield",
135
+ "exDividendDate", "lastSplitFactor", "lastSplitDate", "lastDividendValue", "payoutRatio",
136
+ "beta", "sharesOutstanding", "impliedSharesOutstanding"
137
+ ],
138
+ "Operational Details": [
139
+ "auditRisk", "boardRisk", "compensationRisk", "shareHolderRightsRisk", "overallRisk",
140
+ "governanceEpochDate", "compensationAsOfEpochDate"
141
+ ],
142
+ "Trading Information": [
143
+ "open", "previousClose", "dayLow", "dayHigh", "volume", "averageVolume", "averageVolume10days",
144
+ "fiftyTwoWeekChange", "SandP52WeekChange", "currency", "regularMarketDayLow",
145
+ "regularMarketDayHigh", "regularMarketOpen", "regularMarketPreviousClose",
146
+ "regularMarketPrice", "regularMarketVolume", "regularMarketChange", "regularMarketChangePercent"
147
+ ],
148
+ "Analyst & Target": [
149
+ "targetMeanPrice", "numberOfAnalystOpinions", "recommendationKey", "recommendationMean"
150
+ ]
151
+ }
152
+
153
+ long_summary = info.pop("longBusinessSummary", None)
154
+ officers = info.pop("companyOfficers", None)
155
+
156
+ categorized_html = ""
157
+ for category_name, keys in info_categories.items():
158
+ category_key_value_html = "" # Collect key-value pairs for this category
159
+ for key in keys:
160
+ if key in info and info[key] is not None and info[key] != []:
161
+ value = info[key]
162
+
163
+ # Apply formatting based on key
164
+ if key in ["exDividendDate", "lastSplitDate", "governanceEpochDate", "compensationAsOfEpochDate"]:
165
+ value = format_timestamp_to_date(value)
166
+ elif key in ["marketCap", "enterpriseValue", "fullTimeEmployees", "volume", "averageVolume", "averageVolume10days", "sharesOutstanding", "impliedSharesOutstanding", "regularMarketVolume"]:
167
+ value = format_large_number(value)
168
+ elif isinstance(value, (int, float)):
169
+ if 'percent' in key.lower() or 'ratio' in key.lower() or 'yield' in key.lower() or 'beta' in key.lower() or 'payoutRatio' in key.lower():
170
+ value = f"{value:.2%}" # Format percentages
171
+ elif 'price' in key.lower() or 'dividend' in key.lower() or 'average' in key.lower():
172
+ value = f"{value:.2f}" # Format currency/prices
173
+ else:
174
+ value = f"{value:,.0f}"
175
+
176
+ category_key_value_html += f"<div class='key-value-pair'><h3>{key.replace('_', ' ').title()}</h3><p>{value}</p></div>"
177
+
178
+ if category_key_value_html: # Only add category header and card if there is content in it
179
+ categorized_html += f"<h2>{category_name}</h2><div class='card'><div class='card-content-grid'>{category_key_value_html}</div></div>"
180
+
181
+ extra_sections = ""
182
+ if long_summary:
183
+ extra_sections += f"<div class='big-box'><h2>Business Summary</h2><p>{long_summary}</p></div>"
184
+ if officers:
185
+ officer_rows = "".join(
186
+ f"<tr><td>{o.get('name','')}"f"</td><td>{o.get('title','')}"f"</td><td>{o.get('age','')}"f"</td></tr>"
187
+ for o in officers
188
+ )
189
+ officer_table = f"<table class='styled-table'><tr><th>Name</th><th>Title</th><th>Age</th></tr>{officer_rows}</table>"
190
+ extra_sections += f"<div class='big-box'><h2>Company Officers</h2>{officer_table}</div>"
191
+ content_html = f"{categorized_html}{extra_sections}"
192
+
193
+ # Daily chart
194
+ elif req_type.lower() == "daily":
195
+ df = yf.download(yfsymbol, period="1y", interval="1d").round(2)
196
+ if df.empty:
197
+ content_html = f"<h1>No daily data for {symbol}</h1>"
198
+ else:
199
+ if isinstance(df.columns, pd.MultiIndex):
200
+ df.columns = df.columns.get_level_values(0)
201
+
202
+ low_price = df["Low"].min()
203
+ high_price = df["High"].max()
204
+ price_range = high_price - low_price
205
+ vol_band_min = low_price - (price_range / 5)
206
+ vol_band_max = low_price
207
+ vol_max = df["Volume"].max()
208
+ vol_scale = (vol_band_max - vol_band_min) / vol_max if vol_max > 0 else 1
209
+
210
+ fig = go.Figure()
211
+ fig.add_trace(go.Candlestick(
212
+ x=df.index, open=df["Open"], high=df["High"],
213
+ low=df["Low"], close=df["Close"], name="Price"
214
+ ))
215
+ fig.add_trace(go.Bar(
216
+ x=df.index,
217
+ y=df["Volume"] * vol_scale + vol_band_min,
218
+ name="Volume", marker_color="lightblue",
219
+ customdata=df["Volume"],
220
+ hovertemplate="Volume: %{customdata}<extra></extra>"
221
+ ))
222
+ fig.update_layout(
223
+ xaxis_title="Date", yaxis_title="Price",
224
+ yaxis=dict(range=[vol_band_min, high_price]),
225
+ xaxis_rangeslider_visible=False, height=600
226
+ )
227
+ chart_html = fig.to_html(full_html=False)
228
+ table_html = df.tail(30).to_html(classes="styled-table", border=0)
229
+ content_html = f"{chart_html}<h2>Recent Daily Data (last 30 rows)</h2>{table_html}"
230
+
231
+ # Intraday chart
232
+ elif req_type.lower() == "intraday":
233
+ df = yf.download(yfsymbol, period="1d", interval="5m").round(2)
234
+ if df.empty:
235
+ content_html = f"<h1>No intraday data for {symbol}</h1>"
236
+ else:
237
+ if isinstance(df.columns, pd.MultiIndex):
238
+ df.columns = df.columns.get_level_values(0)
239
+
240
+ low_price = df["Low"].min()
241
+ high_price = df["High"].max()
242
+ price_range = high_price - low_price
243
+ vol_band_min = low_price - (price_range / 5)
244
+ vol_band_max = low_price
245
+ vol_max = df["Volume"].max()
246
+ vol_scale = (vol_band_max - vol_band_min) / vol_max if vol_max > 0 else 1
247
+
248
+ fig = go.Figure()
249
+ fig.add_trace(go.Candlestick(
250
+ x=df.index, open=df["Open"], high=df["High"],
251
+ low=df["Low"], close=df["Close"], name="Price"
252
+ ))
253
+ fig.add_trace(go.Bar(
254
+ x=df.index,
255
+ y=df["Volume"] * vol_scale + vol_band_min,
256
+ name="Volume", marker_color="orange",
257
+ customdata=df["Volume"],
258
+ hovertemplate="Volume: %{customdata}<extra></extra>"
259
+ ))
260
+ fig.update_layout(
261
+ xaxis_title="Time", yaxis_title="Price",
262
+ yaxis=dict(range=[vol_band_min, high_price]),
263
+ xaxis_rangeslider_visible=False, height=600
264
+ )
265
+ chart_html = fig.to_html(full_html=False)
266
+ table_html = df.tail(50).to_html(classes="styled-table", border=0)
267
+ content_html = f"{chart_html}<h2>Recent Intraday Data (last 50 rows)</h2>{table_html}"
268
+
269
+ # Financial sections
270
+ elif req_type.lower() == "qresult":
271
+ df = ticker.quarterly_financials
272
+ if not df.empty:
273
+ for col in df.columns:
274
+ if ptypes.is_numeric_dtype(df[col]):
275
+ df[col] = df[col].apply(format_large_number)
276
+ content_html = f"<h2>Quarterly Results</h2>{df.to_html(classes='styled-table', border=0)}" if not df.empty else "<h1>No quarterly results available</h1>"
277
+
278
+ elif req_type.lower() == "result":
279
+ df = ticker.financials
280
+ if not df.empty:
281
+ for col in df.columns:
282
+ if ptypes.is_numeric_dtype(df[col]):
283
+ df[col] = df[col].apply(format_large_number)
284
+ content_html = f"<h2>Annual Results</h2>{df.to_html(classes='styled-table', border=0)}" if not df.empty else "<h1>No annual results available</h1>"
285
+
286
+ elif req_type.lower() == "balance":
287
+ df = ticker.balance_sheet
288
+ if not df.empty:
289
+ for col in df.columns:
290
+ if ptypes.is_numeric_dtype(df[col]):
291
+ df[col] = df[col].apply(format_large_number)
292
+ content_html = f"<h2>Balance Sheet</h2>{df.to_html(classes='styled-table', border=0)}" if not df.empty else "<h1>No balance sheet available</h1>"
293
+
294
+ elif req_type.lower() == "cashflow":
295
+ df = ticker.cashflow
296
+ if not df.empty:
297
+ for col in df.columns:
298
+ if ptypes.is_numeric_dtype(df[col]):
299
+ df[col] = df[col].apply(format_large_number)
300
+ content_html = f"<h2>Cash Flow</h2>{df.to_html(classes='styled-table', border=0)}" if not df.empty else "<h1>No cashflow available</h1>"
301
+
302
+ elif req_type.lower() == "dividend":
303
+ df = ticker.dividends.to_frame('Dividend')
304
+ if not df.empty:
305
+ for col in df.columns:
306
+ if ptypes.is_numeric_dtype(df[col]):
307
+ df[col] = df[col].apply(format_large_number)
308
+ content_html = f"<h2>Dividend History</h2>{df.to_html(classes='styled-table', border=0)}" if not df.empty else "<h1>No dividend history available</h1>"
309
+
310
+ elif req_type.lower() == "split":
311
+ df = ticker.splits.to_frame('Split')
312
+ if not df.empty:
313
+ for col in df.columns:
314
+ if ptypes.is_numeric_dtype(df[col]):
315
+ df[col] = df[col].apply(format_large_number)
316
+ content_html = f"<h2>Split History</h2>{df.to_html(classes='styled-table', border=0)}" if not df.empty else "<h1>No split history available</h1>"
317
+
318
+ elif req_type.lower() == "other": # This typically handles earnings
319
+ df = ticker.earnings
320
+ if not df.empty:
321
+ for col in df.columns:
322
+ if ptypes.is_numeric_dtype(df[col]):
323
+ df[col] = df[col].apply(format_large_number)
324
+ content_html = f"<h2>Earnings</h2>{df.to_html(classes='styled-table', border=0)}" if not df.empty else "<h1>No earnings data available</h1>"
325
+
326
+ else:
327
+ content_html = f"<h1>No handler for {req_type}</h1>"
328
+
329
+ except Exception as e:
330
+ content_html = f"<h1>Error</h1><p>{str(e)}</p>"
331
+
332
+ # Wrap the content_html in a complete HTML document structure
333
+ full_html_output = f"""
334
+ <!DOCTYPE html>
335
+ <html>
336
+ <head>
337
+ <title>Stock Data for {symbol}</title>
338
+ {STYLE_BLOCK}
339
+ </head>
340
+ <body>
341
+ {content_html}
342
+ </body>
343
+ </html>
344
+ """
345
+ return full_html_output
346
+
347
+
348
+ iface = gr.Interface(
349
+ fn=fetch_data,
350
+ inputs=[
351
+ gr.Textbox(label="Stock Symbol", value="PNB"),
352
+ gr.Dropdown(
353
+ label="Request Type",
354
+ choices=[
355
+ "info",
356
+ "intraday",
357
+ "daily",
358
+ "qresult",
359
+ "result",
360
+ "balance",
361
+ "cashflow",
362
+ "dividend",
363
+ "split",
364
+ "other"
365
+ ],
366
+ value="info"
367
+ )
368
+ ],
369
+ outputs=gr.HTML(label="Full HTML Output"),
370
+ title="Stock Data API (Full)",
371
+ description="Fetch data from NSE and yfinance",
372
+ api_name="fetch_data"
373
+ )
374
+
375
+ if __name__ == "__main__":
376
+ iface.launch(server_name="0.0.0.0", server_port=7860)