topsecrettraders commited on
Commit
9cecb90
·
verified ·
1 Parent(s): d3d50f3

Update app.py

Browse files
Files changed (1) hide show
  1. app.py +340 -6
app.py CHANGED
@@ -167,7 +167,8 @@ async def fetch_series(
167
  others = [k for k in instruments if not k.endswith('CE') and not k.endswith('PE')]
168
  ref_instrument = others[0] if others else instruments[0]
169
 
170
- rpc_name = "get_advanced_chart_data"
 
171
  base_params = {
172
  "p_root": root,
173
  "p_expiry": expiry,
@@ -260,6 +261,14 @@ async def fetch_series(
260
  "PS": "last", # Put Sell → last value
261
  }
262
 
 
 
 
 
 
 
 
 
263
  existing_agg = {k: v for k, v in agg_dict.items() if k in df.columns}
264
  resampled = df.resample(panda_tf).agg(existing_agg).ffill().fillna(0)
265
 
@@ -273,6 +282,60 @@ async def fetch_series(
273
  response_data[var_key] = [round(float(v), 2) for v in resampled[var_key].tolist()]
274
  # Omit entirely if not requested — JS handles missing keys gracefully
275
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
276
  return JSONResponse(response_data)
277
 
278
 
@@ -379,7 +442,7 @@ HTML_TEMPLATE = """
379
  width: 560px; max-width: 95%; max-height: 88vh; display: flex; flex-direction: column;
380
  box-shadow: 0 20px 50px rgba(0,0,0,0.5);
381
  }
382
- .modal.wide { width: 820px; }
383
  .modal-head { padding: 16px; border-bottom: 1px solid var(--border); display: flex; justify-content: space-between; font-weight: 700; }
384
  .modal-body { padding: 20px; overflow-y: auto; display: flex; flex-direction: column; gap: 16px; }
385
  .modal-foot { padding: 16px; border-top: 1px solid var(--border); display: flex; justify-content: flex-end; gap: 10px; background: var(--bg-card); }
@@ -458,6 +521,97 @@ HTML_TEMPLATE = """
458
  .val-pos { color: #3fb950; }
459
  .val-neg { color: #f85149; }
460
  .breakdown-scroll { overflow-x: auto; overflow-y: auto; max-height: 55vh; }
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
461
  </style>
462
  </head>
463
  <body>
@@ -551,7 +705,10 @@ HTML_TEMPLATE = """
551
  <label style="color:var(--accent);">ATM (+/-) Strike Range</label>
552
  <input type="number" id="cfgAtmRange" value="5" min="1" max="50" style="width:100%;">
553
  <div style="font-size:10px; color:var(--text-secondary); margin-top:4px;">
554
- Aggregates all CE/PE within ATM ± this many strikes.
 
 
 
555
  </div>
556
  </div>
557
  </div>
@@ -616,8 +773,24 @@ HTML_TEMPLATE = """
616
  </div>
617
  <div class="modal-body" style="padding:12px;">
618
  <div style="font-size:11px; color:var(--text-secondary); margin-bottom:8px;" id="bdMeta"></div>
619
- <div class="breakdown-scroll">
620
- <table class="breakdown-table" id="bdTable"></table>
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
621
  </div>
622
  </div>
623
  </div>
@@ -1174,6 +1347,18 @@ const ChartLogic = {
1174
  // BREAKDOWN TABLE
1175
  // ===================================================
1176
  const Breakdown = {
 
 
 
 
 
 
 
 
 
 
 
 
1177
  open(id) {
1178
  const chart = App.state.charts[id];
1179
  if (!chart || !chart.lastData || !chart.config.series) {
@@ -1184,6 +1369,10 @@ const Breakdown = {
1184
  const data = chart.lastData;
1185
  const cfg = chart.config;
1186
  const series = cfg.series;
 
 
 
 
1187
 
1188
  // Determine which raw variables to show in table
1189
  const allVarKeys = ['P', 'CB', 'CS', 'PB', 'PS', 'B', 'S', 'V', 'OI'];
@@ -1193,6 +1382,9 @@ const Breakdown = {
1193
 
1194
  // Build header
1195
  let thead = '<thead><tr><th>Time</th>';
 
 
 
1196
  presentVars.forEach(v => { thead += `<th>${v}</th>`; });
1197
  series.forEach(s => { thead += `<th>${s.label}</th>`; });
1198
  thead += '</tr></thead>';
@@ -1210,6 +1402,12 @@ const Breakdown = {
1210
 
1211
  tbody += `<tr><td>${lbl}</td>`;
1212
 
 
 
 
 
 
 
1213
  // Raw variable values
1214
  presentVars.forEach(k => {
1215
  const v = vars[k];
@@ -1237,10 +1435,146 @@ const Breakdown = {
1237
  });
1238
  tbody += '</tbody>';
1239
 
1240
- document.getElementById('bdTitle').innerText = `Breakdown: ${cfg.root} ${cfg.expiry} — ${cfg.chartMode === 'advanced' ? 'ATM ± ' + (cfg.atmRange || 5) : (cfg.instrument || '')}`;
1241
  document.getElementById('bdMeta').innerText = `Timeframe: ${cfg.timeframe || '1min'} | Range: ${cfg.start} – ${cfg.end} | ${data.labels.length} bars`;
1242
  document.getElementById('bdTable').innerHTML = thead + tbody;
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1243
  document.getElementById('breakdownModal').style.display = 'flex';
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1244
  }
1245
  };
1246
 
 
167
  others = [k for k in instruments if not k.endswith('CE') and not k.endswith('PE')]
168
  ref_instrument = others[0] if others else instruments[0]
169
 
170
+ # Use the new function with breakdown
171
+ rpc_name = "get_advanced_chart_data_with_breakdown"
172
  base_params = {
173
  "p_root": root,
174
  "p_expiry": expiry,
 
261
  "PS": "last", # Put Sell → last value
262
  }
263
 
264
+ # Handle optional columns from advanced mode
265
+ if 'atm_strike' in df.columns:
266
+ agg_dict['atm_strike'] = 'last'
267
+ if 'strike_details' in df.columns:
268
+ agg_dict['strike_details'] = 'last'
269
+ if 'range_info' in df.columns:
270
+ agg_dict['range_info'] = 'last'
271
+
272
  existing_agg = {k: v for k, v in agg_dict.items() if k in df.columns}
273
  resampled = df.resample(panda_tf).agg(existing_agg).ffill().fillna(0)
274
 
 
282
  response_data[var_key] = [round(float(v), 2) for v in resampled[var_key].tolist()]
283
  # Omit entirely if not requested — JS handles missing keys gracefully
284
 
285
+ # Include breakdown data for advanced mode
286
+ if mode == "advanced":
287
+ # Add ATM strike for each bar
288
+ if 'atm_strike' in resampled.columns:
289
+ response_data['atm_strike'] = [round(float(v), 2) if v else 0 for v in resampled['atm_strike'].tolist()]
290
+
291
+ # Add per-bar breakdown data
292
+ if 'strike_details' in resampled.columns:
293
+ response_data['strike_details'] = resampled['strike_details'].tolist()
294
+
295
+ if 'range_info' in resampled.columns:
296
+ response_data['range_info'] = resampled['range_info'].tolist()
297
+
298
+ # Build comprehensive strike breakdown per minute
299
+ breakdown_per_minute = []
300
+ for idx, row in resampled.iterrows():
301
+ minute_data = {
302
+ "time": idx.strftime('%H:%M'),
303
+ "spot_price": round(float(row.get('P', 0)), 2) if row.get('P') else 0,
304
+ "atm_strike": round(float(row.get('atm_strike', 0)), 2) if row.get('atm_strike') else 0,
305
+ "cb": round(float(row.get('CB', 0)), 2) if row.get('CB') else 0,
306
+ "cs": round(float(row.get('CS', 0)), 2) if row.get('CS') else 0,
307
+ "pb": round(float(row.get('PB', 0)), 2) if row.get('PB') else 0,
308
+ "ps": round(float(row.get('PS', 0)), 2) if row.get('PS') else 0,
309
+ "strikes": []
310
+ }
311
+
312
+ # Process strike details if available
313
+ strike_details = row.get('strike_details')
314
+ if strike_details and isinstance(strike_details, list):
315
+ for sd in strike_details:
316
+ if isinstance(sd, dict):
317
+ minute_data["strikes"].append({
318
+ "strike": sd.get('strike', 0),
319
+ "type": sd.get('type', ''),
320
+ "instrument": sd.get('instrument', ''),
321
+ "buy": sd.get('buy', 0),
322
+ "sell": sd.get('sell', 0),
323
+ "vol": sd.get('vol', 0),
324
+ "oi": sd.get('oi', 0),
325
+ "ltp": sd.get('ltp', 0),
326
+ "call_status": sd.get('call_status'),
327
+ "put_status": sd.get('put_status')
328
+ })
329
+
330
+ breakdown_per_minute.append(minute_data)
331
+
332
+ response_data['breakdown_per_minute'] = breakdown_per_minute
333
+
334
+ # Add range info if available (just the last one as it's constant)
335
+ range_info = resampled['range_info'].iloc[-1] if 'range_info' in resampled.columns and len(resampled) > 0 else None
336
+ if range_info:
337
+ response_data['atm_range_info'] = range_info
338
+
339
  return JSONResponse(response_data)
340
 
341
 
 
442
  width: 560px; max-width: 95%; max-height: 88vh; display: flex; flex-direction: column;
443
  box-shadow: 0 20px 50px rgba(0,0,0,0.5);
444
  }
445
+ .modal.wide { width: 950px; }
446
  .modal-head { padding: 16px; border-bottom: 1px solid var(--border); display: flex; justify-content: space-between; font-weight: 700; }
447
  .modal-body { padding: 20px; overflow-y: auto; display: flex; flex-direction: column; gap: 16px; }
448
  .modal-foot { padding: 16px; border-top: 1px solid var(--border); display: flex; justify-content: flex-end; gap: 10px; background: var(--bg-card); }
 
521
  .val-pos { color: #3fb950; }
522
  .val-neg { color: #f85149; }
523
  .breakdown-scroll { overflow-x: auto; overflow-y: auto; max-height: 55vh; }
524
+
525
+ /* STRIKE BREAKDOWN STYLES */
526
+ .strike-section {
527
+ margin-top: 10px;
528
+ border: 1px solid var(--border);
529
+ border-radius: 8px;
530
+ overflow: hidden;
531
+ }
532
+ .strike-header {
533
+ background: var(--bg-panel);
534
+ padding: 8px 12px;
535
+ font-weight: 700;
536
+ font-size: 11px;
537
+ cursor: pointer;
538
+ display: flex;
539
+ justify-content: space-between;
540
+ align-items: center;
541
+ border-bottom: 1px solid var(--border);
542
+ }
543
+ .strike-header:hover { background: var(--bg-input); }
544
+ .strike-header .arrow { transition: transform 0.2s; }
545
+ .strike-header.collapsed .arrow { transform: rotate(-90deg); }
546
+ .strike-content {
547
+ max-height: 400px;
548
+ overflow-y: auto;
549
+ transition: max-height 0.3s;
550
+ }
551
+ .strike-content.collapsed { max-height: 0; overflow: hidden; }
552
+ .strike-table {
553
+ width: 100%;
554
+ border-collapse: collapse;
555
+ font-family: var(--font-mono);
556
+ font-size: 10px;
557
+ }
558
+ .strike-table th {
559
+ background: var(--bg-body);
560
+ padding: 6px 8px;
561
+ text-align: right;
562
+ border-bottom: 1px solid var(--border);
563
+ color: var(--text-secondary);
564
+ font-weight: 600;
565
+ }
566
+ .strike-table th:first-child { text-align: left; }
567
+ .strike-table td {
568
+ padding: 5px 8px;
569
+ text-align: right;
570
+ border-bottom: 1px solid var(--border);
571
+ }
572
+ .strike-table td:first-child { text-align: left; }
573
+ .strike-table .ce-row { background: rgba(47,129,247,0.05); }
574
+ .strike-table .pe-row { background: rgba(218,54,51,0.05); }
575
+ .strike-table .atm-strike { background: rgba(210,153,34,0.15); font-weight: 700; }
576
+ .strike-table .itm { color: var(--success); }
577
+ .strike-table .otm { color: var(--text-secondary); }
578
+
579
+ .minute-accordion {
580
+ border: 1px solid var(--border);
581
+ border-radius: 6px;
582
+ margin-bottom: 8px;
583
+ overflow: hidden;
584
+ }
585
+ .minute-header {
586
+ background: var(--bg-panel);
587
+ padding: 10px 12px;
588
+ cursor: pointer;
589
+ display: grid;
590
+ grid-template-columns: 60px 1fr 1fr 1fr 1fr 1fr;
591
+ gap: 10px;
592
+ align-items: center;
593
+ font-weight: 600;
594
+ font-size: 11px;
595
+ }
596
+ .minute-header:hover { background: var(--bg-input); }
597
+ .minute-detail {
598
+ background: var(--bg-card);
599
+ padding: 12px;
600
+ display: none;
601
+ border-top: 1px solid var(--border);
602
+ }
603
+ .minute-detail.show { display: block; }
604
+
605
+ .range-info-box {
606
+ background: var(--bg-panel);
607
+ border: 1px solid var(--accent);
608
+ border-radius: 6px;
609
+ padding: 10px 14px;
610
+ margin-bottom: 12px;
611
+ font-size: 11px;
612
+ }
613
+ .range-info-box .label { color: var(--text-secondary); }
614
+ .range-info-box .value { color: var(--accent); font-weight: 700; font-family: var(--font-mono); }
615
  </style>
616
  </head>
617
  <body>
 
705
  <label style="color:var(--accent);">ATM (+/-) Strike Range</label>
706
  <input type="number" id="cfgAtmRange" value="5" min="1" max="50" style="width:100%;">
707
  <div style="font-size:10px; color:var(--text-secondary); margin-top:4px;">
708
+ <strong>Corrected Logic:</strong><br>
709
+ • For Calls: N ITM (below ATM) + N OTM (ATM and above) = 2N strikes<br>
710
+ • For Puts: N ITM (ATM and above) + N OTM (below ATM) = 2N strikes<br>
711
+ • Both CE and PE at same strike are always included together.
712
  </div>
713
  </div>
714
  </div>
 
773
  </div>
774
  <div class="modal-body" style="padding:12px;">
775
  <div style="font-size:11px; color:var(--text-secondary); margin-bottom:8px;" id="bdMeta"></div>
776
+ <div id="bdRangeInfo" style="display:none;"></div>
777
+
778
+ <!-- TABS -->
779
+ <div style="display:flex; gap:8px; margin-bottom:12px;">
780
+ <button id="tabSummary" class="primary" onclick="Breakdown.showTab('summary')">Summary</button>
781
+ <button id="tabStrikes" onclick="Breakdown.showTab('strikes')">Strike Breakdown</button>
782
+ </div>
783
+
784
+ <!-- SUMMARY TAB -->
785
+ <div id="bdSummaryTab">
786
+ <div class="breakdown-scroll">
787
+ <table class="breakdown-table" id="bdTable"></table>
788
+ </div>
789
+ </div>
790
+
791
+ <!-- STRIKES TAB -->
792
+ <div id="bdStrikesTab" style="display:none;">
793
+ <div class="breakdown-scroll" id="bdStrikesContent"></div>
794
  </div>
795
  </div>
796
  </div>
 
1347
  // BREAKDOWN TABLE
1348
  // ===================================================
1349
  const Breakdown = {
1350
+ currentTab: 'summary',
1351
+
1352
+ showTab(tab) {
1353
+ this.currentTab = tab;
1354
+ document.getElementById('tabSummary').classList.toggle('primary', tab === 'summary');
1355
+ document.getElementById('tabStrikes').classList.toggle('primary', tab === 'strikes');
1356
+ document.getElementById('tabSummary').classList.toggle('', tab !== 'summary');
1357
+ document.getElementById('tabStrikes').classList.toggle('', tab !== 'strikes');
1358
+ document.getElementById('bdSummaryTab').style.display = tab === 'summary' ? 'block' : 'none';
1359
+ document.getElementById('bdStrikesTab').style.display = tab === 'strikes' ? 'block' : 'none';
1360
+ },
1361
+
1362
  open(id) {
1363
  const chart = App.state.charts[id];
1364
  if (!chart || !chart.lastData || !chart.config.series) {
 
1369
  const data = chart.lastData;
1370
  const cfg = chart.config;
1371
  const series = cfg.series;
1372
+ const isAdvanced = cfg.chartMode === 'advanced';
1373
+
1374
+ // Reset to summary tab
1375
+ this.showTab('summary');
1376
 
1377
  // Determine which raw variables to show in table
1378
  const allVarKeys = ['P', 'CB', 'CS', 'PB', 'PS', 'B', 'S', 'V', 'OI'];
 
1382
 
1383
  // Build header
1384
  let thead = '<thead><tr><th>Time</th>';
1385
+ if (isAdvanced && data.atm_strike) {
1386
+ thead += '<th>ATM</th>';
1387
+ }
1388
  presentVars.forEach(v => { thead += `<th>${v}</th>`; });
1389
  series.forEach(s => { thead += `<th>${s.label}</th>`; });
1390
  thead += '</tr></thead>';
 
1402
 
1403
  tbody += `<tr><td>${lbl}</td>`;
1404
 
1405
+ // Add ATM strike column for advanced mode
1406
+ if (isAdvanced && data.atm_strike) {
1407
+ const atm = data.atm_strike[i] || 0;
1408
+ tbody += `<td style="color:var(--warning); font-weight:700;">${atm.toFixed(0)}</td>`;
1409
+ }
1410
+
1411
  // Raw variable values
1412
  presentVars.forEach(k => {
1413
  const v = vars[k];
 
1435
  });
1436
  tbody += '</tbody>';
1437
 
1438
+ document.getElementById('bdTitle').innerText = `Breakdown: ${cfg.root} ${cfg.expiry} — ${isAdvanced ? 'ATM ± ' + (cfg.atmRange || 5) : (cfg.instrument || '')}`;
1439
  document.getElementById('bdMeta').innerText = `Timeframe: ${cfg.timeframe || '1min'} | Range: ${cfg.start} – ${cfg.end} | ${data.labels.length} bars`;
1440
  document.getElementById('bdTable').innerHTML = thead + tbody;
1441
+
1442
+ // Build Strike Breakdown Tab (only for advanced mode with breakdown data)
1443
+ if (isAdvanced && data.breakdown_per_minute) {
1444
+ this.buildStrikeBreakdown(data, cfg);
1445
+ document.getElementById('tabStrikes').style.display = 'inline-block';
1446
+ } else {
1447
+ document.getElementById('tabStrikes').style.display = 'none';
1448
+ }
1449
+
1450
+ // Show range info if available
1451
+ if (isAdvanced && data.atm_range_info) {
1452
+ const info = data.atm_range_info;
1453
+ document.getElementById('bdRangeInfo').innerHTML = `
1454
+ <div class="range-info-box">
1455
+ <span class="label">ATM Strike:</span> <span class="value">${info.atm_strike || 'N/A'}</span> &nbsp;
1456
+ <span class="label">Spot Price:</span> <span class="value">${info.spot_price || 'N/A'}</span> &nbsp;
1457
+ <span class="label">Range:</span> <span class="value">±${info.atm_range || cfg.atmRange || 5}</span> &nbsp;
1458
+ <span class="label">Total Strikes:</span> <span class="value">${info.total_strikes_in_range || '2N'}</span>
1459
+ </div>
1460
+ `;
1461
+ document.getElementById('bdRangeInfo').style.display = 'block';
1462
+ } else {
1463
+ document.getElementById('bdRangeInfo').style.display = 'none';
1464
+ }
1465
+
1466
  document.getElementById('breakdownModal').style.display = 'flex';
1467
+ },
1468
+
1469
+ buildStrikeBreakdown(data, cfg) {
1470
+ const breakdown = data.breakdown_per_minute;
1471
+ if (!breakdown || !breakdown.length) {
1472
+ document.getElementById('bdStrikesContent').innerHTML = '<div style="padding:20px; text-align:center; color:var(--text-secondary);">No strike breakdown data available</div>';
1473
+ return;
1474
+ }
1475
+
1476
+ let html = '';
1477
+
1478
+ breakdown.forEach((minuteData, idx) => {
1479
+ const strikes = minuteData.strikes || [];
1480
+ if (!strikes.length) return;
1481
+
1482
+ // Group strikes by strike price
1483
+ const strikeGroups = {};
1484
+ strikes.forEach(s => {
1485
+ if (!strikeGroups[s.strike]) {
1486
+ strikeGroups[s.strike] = { strike: s.strike, ce: null, pe: null };
1487
+ }
1488
+ if (s.type === 'CE') strikeGroups[s.strike].ce = s;
1489
+ else if (s.type === 'PE') strikeGroups[s.strike].pe = s;
1490
+ });
1491
+
1492
+ const sortedStrikes = Object.values(strikeGroups).sort((a, b) => a.strike - b.strike);
1493
+ const atmStrike = minuteData.atm_strike || 0;
1494
+
1495
+ html += `
1496
+ <div class="minute-accordion">
1497
+ <div class="minute-header" onclick="Breakdown.toggleMinute(this)">
1498
+ <span style="font-weight:700; color:var(--accent);">${minuteData.time}</span>
1499
+ <span>ATM: <span style="color:var(--warning);">${atmStrike.toFixed(0)}</span></span>
1500
+ <span>CB: <span class="val-pos">${minuteData.cb.toFixed(0)}</span></span>
1501
+ <span>CS: <span class="val-neg">${minuteData.cs.toFixed(0)}</span></span>
1502
+ <span>PB: <span class="val-pos">${minuteData.pb.toFixed(0)}</span></span>
1503
+ <span>PS: <span class="val-neg">${minuteData.ps.toFixed(0)}</span></span>
1504
+ </div>
1505
+ <div class="minute-detail">
1506
+ <table class="strike-table">
1507
+ <thead>
1508
+ <tr>
1509
+ <th>Strike</th>
1510
+ <th colspan="4" style="text-align:center; background:rgba(47,129,247,0.2);">CALL (CE)</th>
1511
+ <th colspan="4" style="text-align:center; background:rgba(218,54,51,0.2);">PUT (PE)</th>
1512
+ </tr>
1513
+ <tr>
1514
+ <th></th>
1515
+ <th style="background:rgba(47,129,247,0.1);">Buy</th>
1516
+ <th style="background:rgba(47,129,247,0.1);">Sell</th>
1517
+ <th style="background:rgba(47,129,247,0.1);">LTP</th>
1518
+ <th style="background:rgba(47,129,247,0.1);">Status</th>
1519
+ <th style="background:rgba(218,54,51,0.1);">Buy</th>
1520
+ <th style="background:rgba(218,54,51,0.1);">Sell</th>
1521
+ <th style="background:rgba(218,54,51,0.1);">LTP</th>
1522
+ <th style="background:rgba(218,54,51,0.1);">Status</th>
1523
+ </tr>
1524
+ </thead>
1525
+ <tbody>
1526
+ `;
1527
+
1528
+ sortedStrikes.forEach(sg => {
1529
+ const isAtm = Math.abs(sg.strike - atmStrike) < 1;
1530
+ const rowClass = isAtm ? 'atm-strike' : '';
1531
+
1532
+ html += `<tr class="${rowClass}">`;
1533
+ html += `<td style="font-weight:${isAtm ? '700' : '400'}; color:${isAtm ? 'var(--warning)' : 'var(--text-primary)'};">${sg.strike.toFixed(0)}</td>`;
1534
+
1535
+ // CE columns
1536
+ if (sg.ce) {
1537
+ const ceItmClass = sg.ce.call_status === 'ITM' ? 'itm' : 'otm';
1538
+ html += `
1539
+ <td class="val-pos">${sg.ce.buy.toFixed(0)}</td>
1540
+ <td class="val-neg">${sg.ce.sell.toFixed(0)}</td>
1541
+ <td>${sg.ce.ltp.toFixed(2)}</td>
1542
+ <td class="${ceItmClass}">${sg.ce.call_status || '-'}</td>
1543
+ `;
1544
+ } else {
1545
+ html += `<td>-</td><td>-</td><td>-</td><td>-</td>`;
1546
+ }
1547
+
1548
+ // PE columns
1549
+ if (sg.pe) {
1550
+ const peItmClass = sg.pe.put_status === 'ITM' ? 'itm' : 'otm';
1551
+ html += `
1552
+ <td class="val-pos">${sg.pe.buy.toFixed(0)}</td>
1553
+ <td class="val-neg">${sg.pe.sell.toFixed(0)}</td>
1554
+ <td>${sg.pe.ltp.toFixed(2)}</td>
1555
+ <td class="${peItmClass}">${sg.pe.put_status || '-'}</td>
1556
+ `;
1557
+ } else {
1558
+ html += `<td>-</td><td>-</td><td>-</td><td>-</td>`;
1559
+ }
1560
+
1561
+ html += '</tr>';
1562
+ });
1563
+
1564
+ html += `
1565
+ </tbody>
1566
+ </table>
1567
+ </div>
1568
+ </div>
1569
+ `;
1570
+ });
1571
+
1572
+ document.getElementById('bdStrikesContent').innerHTML = html || '<div style="padding:20px; text-align:center; color:var(--text-secondary);">No strike breakdown data available</div>';
1573
+ },
1574
+
1575
+ toggleMinute(header) {
1576
+ const detail = header.nextElementSibling;
1577
+ detail.classList.toggle('show');
1578
  }
1579
  };
1580