Spaces:
Sleeping
Sleeping
Update build_index_live_html.py
Browse files- build_index_live_html.py +284 -0
build_index_live_html.py
CHANGED
|
@@ -1,3 +1,4 @@
|
|
|
|
|
| 1 |
from nsepython import *
|
| 2 |
import pandas as pd
|
| 3 |
|
|
@@ -240,6 +241,289 @@ th {{
|
|
| 240 |
{metric_tables}
|
| 241 |
</div>
|
| 242 |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 243 |
</body>
|
| 244 |
</html>
|
| 245 |
"""
|
|
|
|
| 1 |
+
'''
|
| 2 |
from nsepython import *
|
| 3 |
import pandas as pd
|
| 4 |
|
|
|
|
| 241 |
{metric_tables}
|
| 242 |
</div>
|
| 243 |
|
| 244 |
+
</body>
|
| 245 |
+
</html>
|
| 246 |
+
"""
|
| 247 |
+
return html
|
| 248 |
+
'''
|
| 249 |
+
from nsepython import *
|
| 250 |
+
import pandas as pd
|
| 251 |
+
|
| 252 |
+
def build_index_live_html(name=""):
|
| 253 |
+
p = nse_index_live(name)
|
| 254 |
+
|
| 255 |
+
full_df = p.get("data", pd.DataFrame())
|
| 256 |
+
rem_df = p.get("rem", pd.DataFrame())
|
| 257 |
+
|
| 258 |
+
if full_df.empty:
|
| 259 |
+
main_df = pd.DataFrame()
|
| 260 |
+
const_df = pd.DataFrame()
|
| 261 |
+
else:
|
| 262 |
+
main_df = full_df.iloc[[0]]
|
| 263 |
+
const_df = full_df.iloc[1:] # Constituents
|
| 264 |
+
if not const_df.empty:
|
| 265 |
+
const_df = const_df.iloc[:, 1:] # Remove first column
|
| 266 |
+
|
| 267 |
+
# Columns to move from constituents to info
|
| 268 |
+
move_to_info = [c for c in ['segment', 'equityTime', 'preOpenTime'] if c in const_df.columns]
|
| 269 |
+
if move_to_info:
|
| 270 |
+
rem_df = pd.concat([rem_df, const_df[move_to_info].iloc[[0]]], axis=1)
|
| 271 |
+
const_df = const_df.drop(columns=move_to_info)
|
| 272 |
+
|
| 273 |
+
# Drop unnecessary columns from Constituents
|
| 274 |
+
drop_cols_const = [
|
| 275 |
+
"identifier", "ffmc", "stockIndClosePrice", "lastUpdateTime",
|
| 276 |
+
"chartTodayPath", "chart30dPath", "chart365dPath", "series",
|
| 277 |
+
"symbol_meta", "activeSeries", "debtSeries", "isFNOSec",
|
| 278 |
+
"isCASec", "isSLBSec", "isDebtSec", "isSuspended",
|
| 279 |
+
"tempSuspendedSeries", "isETFSec", "isDelisted",
|
| 280 |
+
"slb_isin", "isMunicipalBond", "isHybridSymbol", "QuotePreOpenFlag"
|
| 281 |
+
]
|
| 282 |
+
const_df = const_df.drop(columns=[c for c in drop_cols_const if c in const_df.columns])
|
| 283 |
+
|
| 284 |
+
# Drop unnecessary columns from Main Data
|
| 285 |
+
drop_cols_main = [
|
| 286 |
+
"series", "symbol_meta", "companyName", "industry", "activeSeries", "debtSeries",
|
| 287 |
+
"isFNOSec", "isCASec", "isSLBSec", "isDebtSec", "isSuspended", "tempSuspendedSeries",
|
| 288 |
+
"isETFSec", "isDelisted", "isin", "slb_isin", "listingDate", "isMunicipalBond",
|
| 289 |
+
"isHybridSymbol", "segment", "equityTime", "preOpenTime", "QuotePreOpenFlag"
|
| 290 |
+
]
|
| 291 |
+
main_df = main_df.drop(columns=[c for c in drop_cols_main if c in main_df.columns])
|
| 292 |
+
|
| 293 |
+
# Ensure pChange is numeric and sort
|
| 294 |
+
if 'pChange' in const_df.columns:
|
| 295 |
+
const_df['pChange'] = pd.to_numeric(const_df['pChange'], errors='coerce')
|
| 296 |
+
const_df = const_df.sort_values('pChange', ascending=False)
|
| 297 |
+
|
| 298 |
+
# ================= HELPER FUNCTION: COLOR-CODE AND FORMAT NUMERIC =================
|
| 299 |
+
def df_to_html_color(df, metric_col=None):
|
| 300 |
+
df_html = df.copy()
|
| 301 |
+
top3_up = []
|
| 302 |
+
top3_down = []
|
| 303 |
+
if metric_col and metric_col in df_html.columns and pd.api.types.is_numeric_dtype(df_html[metric_col]):
|
| 304 |
+
col_numeric = df_html[metric_col].dropna()
|
| 305 |
+
top3_up = col_numeric.nlargest(3).index.tolist()
|
| 306 |
+
top3_down = col_numeric.nsmallest(3).index.tolist()
|
| 307 |
+
|
| 308 |
+
for idx, row in df_html.iterrows():
|
| 309 |
+
for col in df_html.columns:
|
| 310 |
+
val = row[col]
|
| 311 |
+
style = ""
|
| 312 |
+
if pd.api.types.is_numeric_dtype(type(val)) or isinstance(val, (int, float)):
|
| 313 |
+
val_fmt = f"{val:.2f}"
|
| 314 |
+
if val > 0:
|
| 315 |
+
style = "numeric-positive"
|
| 316 |
+
elif val < 0:
|
| 317 |
+
style = "numeric-negative"
|
| 318 |
+
if metric_col and col == metric_col:
|
| 319 |
+
if idx in top3_up:
|
| 320 |
+
style += " top-up"
|
| 321 |
+
elif idx in top3_down:
|
| 322 |
+
style += " top-down"
|
| 323 |
+
df_html.at[idx, col] = f'<span class="{style.strip()}">{val_fmt}</span>'
|
| 324 |
+
else:
|
| 325 |
+
df_html.at[idx, col] = str(val)
|
| 326 |
+
return df_html.to_html(index=False, escape=False, classes="compact-table")
|
| 327 |
+
|
| 328 |
+
# ================= MERGE INFO AND MAIN KEYS INTO MINI CARDS =================
|
| 329 |
+
def merge_info_main_cards(rem_df, main_df):
|
| 330 |
+
combined = pd.concat([rem_df, main_df], axis=1)
|
| 331 |
+
# Remove duplicate columns
|
| 332 |
+
combined = combined.loc[:, ~combined.columns.duplicated()]
|
| 333 |
+
# Generate mini cards
|
| 334 |
+
cards_html = '<div class="mini-card-container">'
|
| 335 |
+
for col in combined.columns:
|
| 336 |
+
val = combined.at[0, col] if not combined.empty else ""
|
| 337 |
+
cards_html += f'''
|
| 338 |
+
<div class="mini-card">
|
| 339 |
+
<div class="card-key">{col}</div>
|
| 340 |
+
<div class="card-val">{val}</div>
|
| 341 |
+
</div>
|
| 342 |
+
'''
|
| 343 |
+
cards_html += '</div>'
|
| 344 |
+
return cards_html
|
| 345 |
+
|
| 346 |
+
info_cards_html = merge_info_main_cards(rem_df, main_df)
|
| 347 |
+
|
| 348 |
+
cons_html = df_to_html_color(const_df)
|
| 349 |
+
|
| 350 |
+
# ================= METRIC TABLES =================
|
| 351 |
+
metric_cols = [
|
| 352 |
+
"pChange", "totalTradedValue", "nearWKH", "nearWKL",
|
| 353 |
+
"perChange365d", "perChange30d"
|
| 354 |
+
]
|
| 355 |
+
|
| 356 |
+
metric_tables = ""
|
| 357 |
+
for col in metric_cols:
|
| 358 |
+
if col not in const_df.columns:
|
| 359 |
+
continue
|
| 360 |
+
|
| 361 |
+
df_const = const_df.copy()
|
| 362 |
+
df_const[col] = pd.to_numeric(df_const[col], errors="ignore")
|
| 363 |
+
df_const = df_const.sort_values(col, ascending=False)
|
| 364 |
+
df_html = df_to_html_color(df_const[['symbol', col]], metric_col=col)
|
| 365 |
+
|
| 366 |
+
metric_tables += f"""
|
| 367 |
+
<div class="small-table">
|
| 368 |
+
<div class="st-title">{col}</div>
|
| 369 |
+
<div class="st-body">{df_html}</div>
|
| 370 |
+
</div>
|
| 371 |
+
"""
|
| 372 |
+
|
| 373 |
+
# ================= FINAL HTML =================
|
| 374 |
+
html = f"""
|
| 375 |
+
<!DOCTYPE html>
|
| 376 |
+
<html>
|
| 377 |
+
<head>
|
| 378 |
+
<meta charset="UTF-8">
|
| 379 |
+
|
| 380 |
+
<style>
|
| 381 |
+
body {{
|
| 382 |
+
font-family: Arial;
|
| 383 |
+
margin: 12px;
|
| 384 |
+
background: #f5f5f5;
|
| 385 |
+
color: #222;
|
| 386 |
+
font-size: 14px;
|
| 387 |
+
}}
|
| 388 |
+
|
| 389 |
+
h2, h3 {{
|
| 390 |
+
margin: 12px 0 6px 0;
|
| 391 |
+
font-weight: 600;
|
| 392 |
+
}}
|
| 393 |
+
|
| 394 |
+
table {{
|
| 395 |
+
border-collapse: collapse;
|
| 396 |
+
width: 100%;
|
| 397 |
+
table-layout: auto;
|
| 398 |
+
}}
|
| 399 |
+
|
| 400 |
+
th, td {{
|
| 401 |
+
border: 1px solid #bbb;
|
| 402 |
+
padding: 5px 8px;
|
| 403 |
+
text-align: left;
|
| 404 |
+
font-size: 13px;
|
| 405 |
+
}}
|
| 406 |
+
|
| 407 |
+
th {{
|
| 408 |
+
background: #333;
|
| 409 |
+
color: white;
|
| 410 |
+
font-weight: 600;
|
| 411 |
+
}}
|
| 412 |
+
|
| 413 |
+
.compact-table td.numeric-positive {{
|
| 414 |
+
color: green;
|
| 415 |
+
font-weight: bold;
|
| 416 |
+
}}
|
| 417 |
+
.compact-table td.numeric-negative {{
|
| 418 |
+
color: red;
|
| 419 |
+
font-weight: bold;
|
| 420 |
+
}}
|
| 421 |
+
|
| 422 |
+
/* Highlight top 3 gainers / losers */
|
| 423 |
+
.compact-table td.top-up {{
|
| 424 |
+
background: #a8f0a5; /* light green */
|
| 425 |
+
}}
|
| 426 |
+
.compact-table td.top-down {{
|
| 427 |
+
background: #f0a8a8; /* light red */
|
| 428 |
+
}}
|
| 429 |
+
|
| 430 |
+
/* Fixed row height & clipping for Constituent Table */
|
| 431 |
+
#constituents-table tr, #constituents-table td {{
|
| 432 |
+
max-height: 25px;
|
| 433 |
+
height: 25px;
|
| 434 |
+
overflow: hidden;
|
| 435 |
+
white-space: nowrap;
|
| 436 |
+
text-overflow: ellipsis;
|
| 437 |
+
}}
|
| 438 |
+
|
| 439 |
+
.small-table {{
|
| 440 |
+
background: white;
|
| 441 |
+
border-radius: 6px;
|
| 442 |
+
padding: 8px;
|
| 443 |
+
box-shadow: 0px 1px 4px rgba(0,0,0,0.15);
|
| 444 |
+
border: 1px solid #ddd;
|
| 445 |
+
overflow-y: auto;
|
| 446 |
+
}}
|
| 447 |
+
|
| 448 |
+
.st-title {{
|
| 449 |
+
font-size: 14px;
|
| 450 |
+
text-align: center;
|
| 451 |
+
margin-bottom: 6px;
|
| 452 |
+
font-weight: bold;
|
| 453 |
+
background: #222;
|
| 454 |
+
color: white;
|
| 455 |
+
padding: 5px 0;
|
| 456 |
+
border-radius: 4px;
|
| 457 |
+
}}
|
| 458 |
+
|
| 459 |
+
.st-body {{
|
| 460 |
+
max-height: 300px; /* vertical scroll for metric tables */
|
| 461 |
+
overflow-y: auto;
|
| 462 |
+
font-size: 12px;
|
| 463 |
+
}}
|
| 464 |
+
|
| 465 |
+
.compact-section {{
|
| 466 |
+
background: white;
|
| 467 |
+
padding: 8px;
|
| 468 |
+
border-radius: 6px;
|
| 469 |
+
box-shadow: 0 1px 4px rgba(0,0,0,0.12);
|
| 470 |
+
border: 1px solid #ddd;
|
| 471 |
+
margin-bottom: 15px;
|
| 472 |
+
overflow-x: visible;
|
| 473 |
+
}}
|
| 474 |
+
|
| 475 |
+
.grid {{
|
| 476 |
+
display: grid;
|
| 477 |
+
grid-template-columns: repeat(5, 1fr);
|
| 478 |
+
gap: 12px;
|
| 479 |
+
margin-top: 12px;
|
| 480 |
+
}}
|
| 481 |
+
|
| 482 |
+
/* Mini cards for info + main */
|
| 483 |
+
.mini-card-container {{
|
| 484 |
+
display: flex;
|
| 485 |
+
flex-wrap: wrap;
|
| 486 |
+
gap: 10px;
|
| 487 |
+
}}
|
| 488 |
+
.mini-card {{
|
| 489 |
+
background: #fff;
|
| 490 |
+
padding: 8px 10px;
|
| 491 |
+
border-radius: 6px;
|
| 492 |
+
box-shadow: 0 1px 3px rgba(0,0,0,0.12);
|
| 493 |
+
min-width: 120px;
|
| 494 |
+
font-size: 13px;
|
| 495 |
+
}}
|
| 496 |
+
.card-key {{
|
| 497 |
+
font-weight: bold;
|
| 498 |
+
color: #333;
|
| 499 |
+
margin-bottom: 2px;
|
| 500 |
+
}}
|
| 501 |
+
.card-val {{
|
| 502 |
+
color: #222;
|
| 503 |
+
}}
|
| 504 |
+
</style>
|
| 505 |
+
</head>
|
| 506 |
+
<body>
|
| 507 |
+
|
| 508 |
+
<h2>Live Index Data: {name or 'Default Index'}</h2>
|
| 509 |
+
|
| 510 |
+
<div class="compact-section">
|
| 511 |
+
<h3>Index Info + Main Data</h3>
|
| 512 |
+
{info_cards_html}
|
| 513 |
+
</div>
|
| 514 |
+
|
| 515 |
+
<div class="compact-section">
|
| 516 |
+
<h3>Constituents</h3>
|
| 517 |
+
<div id="constituents-table">
|
| 518 |
+
{cons_html}
|
| 519 |
+
</div>
|
| 520 |
+
</div>
|
| 521 |
+
|
| 522 |
+
<h3>Metric Tables (All Symbols)</h3>
|
| 523 |
+
<div class="grid">
|
| 524 |
+
{metric_tables}
|
| 525 |
+
</div>
|
| 526 |
+
|
| 527 |
</body>
|
| 528 |
</html>
|
| 529 |
"""
|